[
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: https://zeronet.io/docs/help_zeronet/donate/\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve ZeroNet\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Step 1: Please describe your environment\n\n  * ZeroNet version: _____\n  * Operating system: _____\n  * Web browser: _____\n  * Tor status: not available/always/disabled\n  * Opened port: yes/no\n  * Special configuration: ____\n\n### Step 2: Describe the problem:\n\n#### Steps to reproduce:\n\n  1. _____\n  2. _____\n  3. _____\n\n#### Observed Results:\n\n  * What happened? This could be a screenshot, a description, log output (you can send log/debug.log file to hello@zeronet.io if necessary), etc.\n\n#### Expected Results:\n\n  * What did you expect to happen?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for ZeroNet\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: tests\n\non: [push, pull_request]\n\njobs:\n  test:\n\n    runs-on: ubuntu-16.04\n    strategy:\n      max-parallel: 16\n      matrix:\n        python-version: [3.5, 3.6, 3.7, 3.8, 3.9]\n\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v1\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Prepare for installation\n      run: |\n        python3 -m pip install setuptools\n        python3 -m pip install --upgrade pip wheel\n        python3 -m pip install --upgrade codecov coveralls flake8 mock pytest==4.6.3 pytest-cov selenium\n\n    - name: Install\n      run: |\n        python3 -m pip install --upgrade -r requirements.txt\n        python3 -m pip list\n\n    - name: Prepare for tests\n      run: |\n        openssl version -a\n        echo 0 | sudo tee /proc/sys/net/ipv6/conf/all/disable_ipv6\n\n    - name: Test\n      run: |\n        catchsegv python3 -m pytest src/Test --cov=src --cov-config src/Test/coverage.ini\n        export ZERONET_LOG_DIR=\"log/CryptMessage\"; catchsegv python3 -m pytest -x plugins/CryptMessage/Test\n        export ZERONET_LOG_DIR=\"log/Bigfile\"; catchsegv python3 -m pytest -x plugins/Bigfile/Test\n        export ZERONET_LOG_DIR=\"log/AnnounceLocal\"; catchsegv python3 -m pytest -x plugins/AnnounceLocal/Test\n        export ZERONET_LOG_DIR=\"log/OptionalManager\"; catchsegv python3 -m pytest -x plugins/OptionalManager/Test\n        export ZERONET_LOG_DIR=\"log/Multiuser\"; mv plugins/disabled-Multiuser plugins/Multiuser && catchsegv python -m pytest -x plugins/Multiuser/Test\n        export ZERONET_LOG_DIR=\"log/Bootstrapper\"; mv plugins/disabled-Bootstrapper plugins/Bootstrapper && catchsegv python -m pytest -x plugins/Bootstrapper/Test\n        find src -name \"*.json\" | xargs -n 1 python3 -c \"import json, sys; print(sys.argv[1], end=' '); json.load(open(sys.argv[1])); print('[OK]')\"\n        find plugins -name \"*.json\" | xargs -n 1 python3 -c \"import json, sys; print(sys.argv[1], end=' '); json.load(open(sys.argv[1])); print('[OK]')\"\n        flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pyaes/\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# Log files\n**/*.log\n\n# Hidden files\n.*\n!/.github\n!/.gitignore\n!/.travis.yml\n!/.gitlab-ci.yml\n\n# Temporary files\n*.bak\n\n# Data dir\ndata/*\n*.db\n\n# Virtualenv\nenv/*\n\n# Tor data\ntools/tor/data\n\n# PhantomJS, downloaded manually for unit tests\ntools/phantomjs\n\n# ZeroNet config file\nzeronet.conf\n\n# ZeroNet log files\nlog/*\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "stages:\n  - test\n\n.test_template: &test_template\n  stage: test\n  before_script:\n    - pip install --upgrade pip wheel\n    # Selenium and requests can't be installed without a requests hint on Python 3.4\n    - pip install --upgrade requests>=2.22.0\n    - pip install --upgrade codecov coveralls flake8 mock pytest==4.6.3 pytest-cov selenium\n    - pip install --upgrade -r requirements.txt\n  script:\n    - pip list\n    - openssl version -a\n    - python -m pytest -x plugins/CryptMessage/Test --color=yes\n    - python -m pytest -x plugins/Bigfile/Test --color=yes\n    - python -m pytest -x plugins/AnnounceLocal/Test --color=yes\n    - python -m pytest -x plugins/OptionalManager/Test --color=yes\n    - python -m pytest src/Test --cov=src --cov-config src/Test/coverage.ini --color=yes\n    - mv plugins/disabled-Multiuser plugins/Multiuser\n    - python -m pytest -x plugins/Multiuser/Test --color=yes\n    - mv plugins/disabled-Bootstrapper plugins/Bootstrapper\n    - python -m pytest -x plugins/Bootstrapper/Test --color=yes\n    - flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pyaes/\n\ntest:py3.4:\n  image: python:3.4.3\n  <<: *test_template\n\ntest:py3.5:\n  image: python:3.5.7\n  <<: *test_template\n\ntest:py3.6:\n  image: python:3.6.9\n  <<: *test_template\n\ntest:py3.7-openssl1.1.0:\n  image: python:3.7.0b5\n  <<: *test_template\n\ntest:py3.7-openssl1.1.1:\n  image: python:3.7.4\n  <<: *test_template\n\ntest:py3.8:\n  image: python:3.8.0b3\n  <<: *test_template"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\npython:\n  - 3.4\n  - 3.5\n  - 3.6\n  - 3.7\n  - 3.8\nservices:\n - docker\ncache: pip\nbefore_install:\n  - pip install --upgrade pip wheel\n  - pip install --upgrade codecov coveralls flake8 mock pytest==4.6.3 pytest-cov selenium\n  # - docker build -t zeronet .\n  # - docker run -d -v $PWD:/root/data -p 15441:15441 -p 127.0.0.1:43110:43110 zeronet\ninstall:\n - pip install --upgrade -r requirements.txt\n - pip list\nbefore_script:\n  - openssl version -a\n  # Add an IPv6 config - see the corresponding Travis issue\n  # https://github.com/travis-ci/travis-ci/issues/8361\n  - if [ \"${TRAVIS_OS_NAME}\" == \"linux\" ]; then\n      sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6';\n    fi\nscript:\n - catchsegv python -m pytest src/Test --cov=src --cov-config src/Test/coverage.ini\n - export ZERONET_LOG_DIR=\"log/CryptMessage\"; catchsegv python -m pytest -x plugins/CryptMessage/Test\n - export ZERONET_LOG_DIR=\"log/Bigfile\"; catchsegv python -m pytest -x plugins/Bigfile/Test\n - export ZERONET_LOG_DIR=\"log/AnnounceLocal\"; catchsegv python -m pytest -x plugins/AnnounceLocal/Test\n - export ZERONET_LOG_DIR=\"log/OptionalManager\"; catchsegv python -m pytest -x plugins/OptionalManager/Test\n - export ZERONET_LOG_DIR=\"log/Multiuser\"; mv plugins/disabled-Multiuser plugins/Multiuser && catchsegv python -m pytest -x plugins/Multiuser/Test\n - export ZERONET_LOG_DIR=\"log/Bootstrapper\"; mv plugins/disabled-Bootstrapper plugins/Bootstrapper && catchsegv python -m pytest -x plugins/Bootstrapper/Test\n - find src -name \"*.json\" | xargs -n 1 python3 -c \"import json, sys; print(sys.argv[1], end=' '); json.load(open(sys.argv[1])); print('[OK]')\"\n - find plugins -name \"*.json\" | xargs -n 1 python3 -c \"import json, sys; print(sys.argv[1], end=' '); json.load(open(sys.argv[1])); print('[OK]')\"\n - flake8 . --count --select=E9,F63,F72,F82 --show-source --statistics --exclude=src/lib/pyaes/\nafter_failure:\n  - zip -r log.zip log/\n  - curl --upload-file ./log.zip https://transfer.sh/log.zip \nafter_success:\n  - codecov\n  - coveralls --rcfile=src/Test/coverage.ini\nnotifications:\n  email:\n    recipients:\n      hello@zeronet.io\n    on_success: change\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### ZeroNet 0.7.2 (2020-09-?) Rev4206?\n\n\n\n### ZeroNet 0.7.1 (2019-07-01) Rev4206\n### Added\n - Built-in logging console in the web UI to see what's happening in the background. (pull down top-right 0 button to see it)\n - Display database rebuild errors [Thanks to Lola]\n - New plugin system that allows to install and manage builtin/third party extensions to the ZeroNet client using the web interface.\n - Support multiple trackers_file\n - Add OpenSSL 1.1 support to CryptMessage plugin based on Bitmessage modifications [Thanks to radfish]\n - Display visual error message on startup errors\n - Fix max opened files changing on Windows platform\n - Display TLS1.3 compatibility on /Stats page\n - Add fake SNI and ALPN to peer connections to make it more like standard https connections\n - Hide and ignore tracker_proxy setting in Tor: Always mode as it's going to use Tor anyway.\n - Deny websocket connections from unknown origins\n - Restrict open_browser values to avoid RCE on sandbox escape\n - Offer access web interface by IP address in case of unknown host\n - Link to site's sidebar with \"#ZeroNet:OpenSidebar\" hash\n\n### Changed\n - Allow .. in file names [Thanks to imachug]\n - Change unstable trackers\n - More clean errors on sites.json/users.json load error\n - Various tweaks for tracker rating on unstable connections\n - Use OpenSSL 1.1 dlls from default Python Windows distribution if possible\n - Re-factor domain resolving for easier domain plugins\n - Disable UDP connections if --proxy is used\n - New, decorator-based Websocket API permission system to avoid future typo mistakes\n\n### Fixed\n - Fix parsing config lines that have no value\n - Fix start.py [Thanks to imachug]\n - Allow multiple values of the same key in the config file [Thanks ssdifnskdjfnsdjk for reporting]\n - Fix parsing config file lines that has % in the value [Thanks slrslr for reporting]\n - Fix bootstrapper plugin hash reloads [Thanks geekless for reporting]\n - Fix CryptMessage plugin OpenSSL dll loading on Windows (ZeroMail errors) [Thanks cxgreat2014 for reporting]\n - Fix startup error when using OpenSSL 1.1 [Thanks to imachug]\n - Fix a bug that did not loaded merged site data for 5 sec after the merged site got added\n - Fix typo that allowed to add new plugins in public proxy mode. [Thanks styromaniac for reporting]\n - Fix loading non-big files with \"|all\" postfix [Thanks to krzotr]\n - Fix OpenSSL cert generation error crash by change Windows console encoding to utf8\n\n#### Wrapper html injection vulnerability [Reported by ivanq]\n\nIn ZeroNet before rev4188 the wrapper template variables was rendered incorrectly.\n\nResult: The opened site was able to gain WebSocket connection with unrestricted ADMIN/NOSANDBOX access, change configuration values and possible RCE on client's machine.\n\nFix: Fixed the template rendering code, disallowed WebSocket connections from unknown locations, restricted open_browser configuration values to avoid possible RCE in case of sandbox escape.\n\nNote: The fix is also back ported to ZeroNet Py 2.x version (Rev3870)\n\n\n### ZeroNet 0.7.0 (2019-06-12) Rev4106 (First release targeting Python 3.4+)\n### Added\n - 5-10x faster signature verification by using libsecp256k1 (Thanks to ZeroMux)\n - Generated SSL certificate randomization to avoid protocol filters (Thanks to ValdikSS)\n - Offline mode\n - P2P source code update using ZeroNet protocol\n - ecdsaSign/Verify commands to CryptMessage plugin (Thanks to imachug)\n - Efficient file rename: change file names instead of re-downloading the file.\n - Make redirect optional on site cloning (Thanks to Lola)\n - EccPrivToPub / EccPubToPriv functions (Thanks to imachug)\n - Detect and change dark/light theme based on OS setting (Thanks to filips123)\n\n### Changed\n - Re-factored code to Python3 runtime (compatible with Python 3.4-3.8)\n - More safe database sync mode\n - Removed bundled third-party libraries where it's possible\n - Use lang=en instead of lang={lang} in urls to avoid url encode problems\n - Remove environment details from error page\n - Don't push content.json updates larger than 10kb to significantly reduce bw usage for site with many files\n\n### Fixed\n - Fix sending files with \\0 characters\n - Security fix: Escape error detail to avoid XSS (reported by krzotr)\n - Fix signature verification using libsecp256k1 for compressed addresses (mostly certificates generated in the browser)\n - Fix newsfeed if you have more than 1000 followed topic/post on one site.\n - Fix site download as zip file\n - Fix displaying sites with utf8 title\n - Error message if dbRebuild fails (Thanks to Lola)\n - Fix browser reopen if executing start.py again. (Thanks to imachug)\n\n\n### ZeroNet 0.6.5 (2019-02-16) Rev3851 (Last release targeting Python 2.7.x)\n### Added\n - IPv6 support in peer exchange, bigfiles, optional file finding, tracker sharing, socket listening and connecting (based on tangdou1 modifications)\n - New tracker database format with IPv6 support\n - Display notification if there is an unpublished modification for your site\n - Listen and shut down normally for SIGTERM (Thanks to blurHY)\n - Support tilde `~` in filenames (by d14na)\n - Support map for Namecoin subdomain names (Thanks to lola)\n - Add log level to config page\n - Support `{data}` for data dir variable in trackers_file value\n - Quick check content.db on startup and rebuild if necessary\n - Don't show meek proxy option if the tor client does not supports it\n\n### Changed\n - Refactored port open checking with IPv6 support\n - Consider non-local IPs as external even is the open port check fails (for CJDNS and Yggdrasil support)\n - Add IPv6 tracker and change unstable tracker\n - Don't correct sent local time with the calculated time correction\n - Disable CSP for Edge\n - Only support CREATE commands in dbschema indexes node and SELECT from storage.query\n\n### Fixed\n - Check the length of master seed when executing cryptGetPrivatekey CLI command\n - Only reload source code on file modification / creation\n - Detection and issue warning for latest no-script plugin\n - Fix atomic write of a non-existent file\n - Fix sql queries with lots of variables and sites with lots of content.json\n - Fix multi-line parsing of zeronet.conf\n - Fix site deletion from users.json\n - Fix site cloning before site downloaded (Reported by unsystemizer)\n - Fix queryJson for non-list nodes (Reported by MingchenZhang)\n\n\n## ZeroNet 0.6.4 (2018-10-20) Rev3660\n### Added\n - New plugin: UiConfig. A web interface that allows changing ZeroNet settings.\n - New plugin: AnnounceShare. Share trackers between users, automatically announce client's ip as tracker if Bootstrapper plugin is enabled.\n - Global tracker stats on ZeroHello: Include statistics from all served sites instead of displaying request statistics only for one site.\n - Support custom proxy for trackers. (Configurable with /Config)\n - Adding peers to sites manually using zeronet_peers get parameter\n - Copy site address with peers link on the sidebar.\n - Zip file listing and streaming support for Bigfiles.\n - Tracker statistics on /Stats page\n - Peer reputation save/restore to speed up sync time after startup.\n - Full support fileGet, fileList, dirList calls on tar.gz/zip files.\n - Archived_before support to user content rules to allow deletion of all user files before the specified date\n - Show and manage \"Connecting\" sites on ZeroHello\n - Add theme support to ZeroNet sites\n - Dark theme for ZeroHello, ZeroBlog, ZeroTalk\n\n### Changed\n - Dynamic big file allocation: More efficient storage usage by don't pre-allocate the whole file at the beginning, but expand the size as the content downloads.\n - Reduce the request frequency to unreliable trackers.\n - Only allow 5 concurrent checkSites to run in parallel to reduce load under Tor/slow connection.\n - Stop site downloading if it reached 95% of site limit to avoid download loop for sites out of limit\n - The pinned optional files won't be removed from download queue after 30 retries and won't be deleted even if the site owner removes it.\n - Don't remove incomplete (downloading) sites on startup\n - Remove --pin_bigfile argument as big files are automatically excluded from optional files limit.\n\n### Fixed\n - Trayicon compatibility with latest gevent\n - Request number counting for zero:// trackers\n - Peer reputation boost for zero:// trackers.\n - Blocklist of peers loaded from peerdb (Thanks tangdou1 for report)\n - Sidebar map loading on foreign languages (Thx tangdou1 for report)\n - FileGet on non-existent files (Thanks mcdev for reporting)\n - Peer connecting bug for sites with low amount of peers\n\n#### \"The Vacation\" Sandbox escape bug [Reported by GitCenter / Krixano / ZeroLSTN]\n\nIn ZeroNet 0.6.3 Rev3615 and earlier as a result of invalid file type detection, a malicious site could escape the iframe sandbox.\n\nResult: Browser iframe sandbox escape\n\nApplied fix: Replaced the previous, file extension based file type identification with a proper one.\n\nAffected versions: All versions before ZeroNet Rev3616\n\n\n## ZeroNet 0.6.3 (2018-06-26)\n### Added\n - New plugin: ContentFilter that allows to have shared site and user block list.\n - Support Tor meek proxies to avoid tracker blocking of GFW\n - Detect network level tracker blocking and easy setting meek proxy for tracker connections.\n - Support downloading 2GB+ sites as .zip (Thx to Radtoo)\n - Support ZeroNet as a transparent proxy (Thx to JeremyRand)\n - Allow fileQuery as CORS command (Thx to imachug)\n - Windows distribution includes Tor and meek client by default\n - Download sites as zip link to sidebar\n - File server port randomization\n - Implicit SSL for all connection\n - fileList API command for zip files\n - Auto download bigfiles size limit on sidebar\n - Local peer number to the sidebar\n - Open site directory button in sidebar\n\n### Changed\n - Switched to Azure Tor meek proxy as Amazon one became unavailable\n - Refactored/rewritten tracker connection manager\n - Improved peer discovery for optional files without opened port\n - Also delete Bigfile's piecemap on deletion\n\n### Fixed\n - Important security issue: Iframe sandbox escape [Reported by Ivanq / gitcenter]\n - Local peer discovery when running multiple clients on the same machine\n - Uploading small files with Bigfile plugin\n - Ctrl-c shutdown when running CLI commands\n - High CPU/IO usage when Multiuser plugin enabled\n - Firefox back button\n - Peer discovery on older Linux kernels\n - Optional file handling when multiple files have the same hash_id (first 4 chars of the hash)\n - Msgpack 0.5.5 and 0.5.6 compatibility\n\n## ZeroNet 0.6.2 (2018-02-18)\n\n### Added\n - New plugin: AnnounceLocal to make ZeroNet work without an internet connection on the local network.\n - Allow dbQuey and userGetSettings using the `as` API command on different sites with Cors permission\n - New config option: `--log_level` to reduce log verbosity and IO load\n - Prefer to connect to recent peers from trackers first\n - Mark peers with port 1 is also unconnectable for future fix for trackers that do not support port 0 announce\n\n### Changed\n - Don't keep connection for sites that have not been modified in the last week\n - Change unreliable trackers to new ones\n - Send maximum 10 findhash request in one find optional files round (15sec)\n - Change \"Unique to site\" to \"No certificate\" for default option in cert selection dialog.\n - Dont print warnings if not in debug mode\n - Generalized tracker logging format\n - Only recover sites from sites.json if they had peers\n - Message from local peers does not means internet connection\n - Removed `--debug_gevent` and turned on Gevent block logging by default\n\n### Fixed\n - Limit connections to 512 to avoid reaching 1024 limit on windows\n - Exception when logging foreign operating system socket errors\n - Don't send private (local) IPs on pex\n - Don't connect to private IPs in tor always mode\n - Properly recover data from msgpack unpacker on file stream start\n - Symlinked data directory deletion when deleting site using Windows\n - De-duplicate peers before publishing\n - Bigfile info for non-existing files\n\n\n## ZeroNet 0.6.1 (2018-01-25)\n\n### Added\n - New plugin: Chart\n - Collect and display charts about your contribution to ZeroNet network\n - Allow list as argument replacement in sql queries. (Thanks to imachug)\n - Newsfeed query time statistics (Click on \"From XX sites in X.Xs on ZeroHello)\n - New UiWebsocket API command: As to run commands as other site\n - Ranged ajax queries for big files\n - Filter feed by type and site address\n - FileNeed, Bigfile upload command compatibility with merger sites\n - Send event on port open / tor status change\n - More description on permission request\n\n### Changed\n - Reduce memory usage of sidebar geoip database cache\n - Change unreliable tracker to new one\n - Don't display Cors permission ask if it already granted\n - Avoid UI blocking when rebuilding a merger site\n - Skip listing ignored directories on signing\n - In Multiuser mode show the seed welcome message when adding new certificate instead of first visit\n - Faster async port opening on multiple network interfaces\n - Allow javascript modals\n - Only zoom sidebar globe if mouse button is pressed down\n\n### Fixed\n - Open port checking error reporting (Thanks to imachug)\n - Out-of-range big file requests\n - Don't output errors happened on gevent greenlets twice\n - Newsfeed skip sites with no database\n - Newsfeed queries with multiple params\n - Newsfeed queries with UNION and UNION ALL\n - Fix site clone with sites larger that 10MB\n - Unreliable Websocket connection when requesting files from different sites at the same time\n\n\n## ZeroNet 0.6.0 (2017-10-17)\n\n### Added\n - New plugin: Big file support\n - Automatic pinning on Big file download\n - Enable TCP_NODELAY for supporting sockets\n - actionOptionalFileList API command arguments to list non-downloaded files or only big files\n - serverShowdirectory API command arguments to allow to display site's directory in OS file browser\n - fileNeed API command to initialize optional file downloading\n - wrapperGetAjaxKey API command to request nonce for AJAX request\n - Json.gz support for database files\n - P2P port checking (Thanks for grez911)\n - `--download_optional auto` argument to enable automatic optional file downloading for newly added site\n - Statistics for big files and protocol command requests on /Stats\n - Allow to set user limitation based on auth_address\n\n### Changed\n - More aggressive and frequent connection timeout checking\n - Use out of msgpack context file streaming for files larger than 512KB\n - Allow optional files workers over the worker limit\n - Automatic redirection to wrapper on nonce_error\n - Send websocket event on optional file deletion\n - Optimize sites.json saving\n - Enable faster C-based msgpack packer by default\n - Major optimization on Bootstrapper plugin SQL queries\n - Don't reset bad file counter on restart, to allow easier give up on unreachable files\n - Incoming connection limit changed from 1000 to 500 to avoid reaching socket limit on Windows\n - Changed tracker boot.zeronet.io domain, because zeronet.io got banned in some countries\n\n#### Fixed\n - Sub-directories in user directories\n\n## ZeroNet 0.5.7 (2017-07-19)\n### Added\n - New plugin: CORS to request read permission to other site's content\n - New API command: userSetSettings/userGetSettings to store site's settings in users.json\n - Avoid file download if the file size does not match with the requested one\n - JavaScript and wrapper less file access using /raw/ prefix ([Example](http://127.0.0.1:43110/raw/1AsRLpuRxr3pb9p3TKoMXPSWHzh6i7fMGi/en.tar.gz/index.html))\n - --silent command line option to disable logging to stdout\n\n\n### Changed\n - Better error reporting on sign/verification errors\n - More test for sign and verification process\n - Update to OpenSSL v1.0.2l\n - Limit compressed files to 6MB to avoid zip/tar.gz bomb\n - Allow space, [], () characters in filenames\n - Disable cross-site resource loading to improve privacy. [Reported by Beardog108]\n - Download directly accessed Pdf/Svg/Swf files instead of displaying them to avoid wrapper escape using in JS in SVG file. [Reported by Beardog108]\n - Disallow potentially unsafe regular expressions to avoid ReDoS [Reported by MuxZeroNet]\n\n### Fixed\n - Detecting data directory when running Windows distribution exe [Reported by Plasmmer]\n - OpenSSL loading under Android 6+\n - Error on exiting when no connection server started\n\n\n## ZeroNet 0.5.6 (2017-06-15)\n### Added\n - Callback for certSelect API command\n - More compact list formatting in json\n\n### Changed\n - Remove obsolete auth_key_sha512 and signature format\n - Improved Spanish translation (Thanks to Pupiloho)\n\n### Fixed\n - Opened port checking (Thanks l5h5t7 & saber28 for reporting)\n - Standalone update.py argument parsing (Thanks Zalex for reporting)\n - uPnP crash on startup (Thanks Vertux for reporting)\n - CoffeeScript 1.12.6 compatibility (Thanks kavamaken & imachug)\n - Multi value argument parsing\n - Database error when running from directory that contains special characters (Thanks Pupiloho for reporting)\n - Site lock violation logging\n\n\n#### Proxy bypass during source upgrade [Reported by ZeroMux]\n\nIn ZeroNet before 0.5.6 during the client's built-in source code upgrade mechanism,\nZeroNet did not respect Tor and/or proxy settings.\n\nResult: ZeroNet downloaded the update without using the Tor network and potentially leaked the connections.\n\nFix: Removed the problematic code line from the updater that removed the proxy settings from the socket library.\n\nAffected versions: ZeroNet 0.5.5 and earlier, Fixed in: ZeroNet 0.5.6\n\n\n#### XSS vulnerability using DNS rebinding. [Reported by Beardog108]\n\nIn ZeroNet before 0.5.6 the web interface did not validate the request's Host parameter.\n\nResult: An attacker using a specially crafted DNS entry could have bypassed the browser's cross-site-scripting protection\nand potentially gained access to user's private data stored on site.\n\nFix: By default ZeroNet only accept connections from 127.0.0.1 and localhost hosts.\nIf you bind the ui server to an external interface, then it also adds the first http request's host to the allowed host list\nor you can define it manually using --ui_host.\n\nAffected versions: ZeroNet 0.5.5 and earlier, Fixed in: ZeroNet 0.5.6\n\n\n## ZeroNet 0.5.5 (2017-05-18)\n### Added\n- Outgoing socket binding by --bind parameter\n- Database rebuilding progress bar\n- Protect low traffic site's peers from cleanup closing\n- Local site blacklisting\n- Cloned site source code upgrade from parent\n- Input placeholder support for displayPrompt\n- Alternative interaction for wrapperConfirm\n\n### Changed\n- New file priorities for faster site display on first visit\n- Don't add ? to url if push/replaceState url starts with #\n\n### Fixed\n- PermissionAdd/Remove admin command requirement\n- Multi-line confirmation dialog\n\n\n## ZeroNet 0.5.4 (2017-04-14)\n### Added\n- Major speed and CPU usage enhancements in Tor always mode\n- Send skipped modifications to outdated clients\n\n### Changed\n- Upgrade libs to latest version\n- Faster port opening and closing\n- Deny site limit modification in MultiUser mode\n\n### Fixed\n- Filling database from optional files\n- OpenSSL detection on systems with OpenSSL 1.1\n- Users.json corruption on systems with slow hdd\n- Fix leaking files in data directory by webui\n\n\n## ZeroNet 0.5.3 (2017-02-27)\n### Added\n- Tar.gz/zip packed site support\n- Utf8 filenames in archive files\n- Experimental --db_mode secure database mode to prevent data loss on systems with unreliable power source.\n- Admin user support in MultiUser mode\n- Optional deny adding new sites in MultiUser mode\n\n### Changed\n- Faster update and publish times by new socket sharing algorithm\n\n### Fixed\n- Fix missing json_row errors when using Mute plugin\n\n\n## ZeroNet 0.5.2 (2017-02-09)\n### Added\n- User muting\n- Win/Mac signed exe/.app\n- Signed commits\n\n### Changed\n- Faster site updates after startup\n- New macOS package for 10.10 compatibility\n\n### Fixed\n- Fix \"New version just released\" popup on page first visit\n- Fix disappearing optional files bug (Thanks l5h5t7 for reporting)\n- Fix skipped updates on unreliable connections (Thanks P2P for reporting)\n- Sandbox escape security fix (Thanks Firebox for reporting)\n- Fix error reporting on async websocket functions\n\n\n## ZeroNet 0.5.1 (2016-11-18)\n### Added\n- Multi language interface\n- New plugin: Translation helper for site html and js files\n- Per-site favicon\n\n### Fixed\n- Parallel optional file downloading\n\n\n## ZeroNet 0.5.0 (2016-11-08)\n### Added\n- New Plugin: Allow list/delete/pin/manage files on ZeroHello\n- New API commands to follow user's optional files, and query stats for optional files\n- Set total size limit on optional files.\n- New Plugin: Save peers to database and keep them between restarts to allow more faster optional file search and make it work without trackers\n- Rewritten uPnP port opener + close port on exit (Thanks to sirMackk!)\n- Lower memory usage by lazy PeerHashfield creation\n- Loaded json files statistics and database info at /Stats page\n\n### Changed\n- Separate lock file for better Windows compatibility\n- When executing start.py open browser even if ZeroNet is already running\n- Keep plugin order after reload to allow plugins to extends an another plug-in\n- Only save sites.json if fully loaded to avoid data loss\n- Change aletorrenty tracker to a more reliable one\n- Much lower findhashid CPU usage\n- Pooled downloading of large amount of optional files\n- Lots of other optional file changes to make it better\n- If we have 1000 peers for a site make cleanup more aggressive\n- Use warning instead of error on verification errors\n- Push updates to newer clients first\n- Bad file reset improvements\n\n### Fixed\n- Fix site deletion errors on startup\n- Delay websocket messages until it's connected\n- Fix database import if data file contains extra data\n- Fix big site download\n- Fix diff sending bug (been chasing it for a long time)\n- Fix random publish errors when json file contained [] characters\n- Fix site delete and siteCreate bug\n- Fix file write confirmation dialog\n\n\n## ZeroNet 0.4.1 (2016-09-05)\n### Added\n- Major core changes to allow fast startup and lower memory usage\n- Try to reconnect to Tor on lost connection\n- Sidebar fade-in\n- Try to avoid incomplete data files overwrite\n- Faster database open\n- Display user file sizes in sidebar\n- Concurrent worker number depends on --connection_limit\n\n### Changed\n- Close databases after 5 min idle time\n- Better site size calculation\n- Allow \"-\" character in domains\n- Always try to keep connections for sites\n- Remove merger permission from merged sites\n- Newsfeed scans only last 3 days to speed up database queries\n- Updated ZeroBundle-win to Python 2.7.12\n\n### Fixed\n- Fix for important security problem, which is allowed anyone to publish new content without valid certificate from ID provider. Thanks Kaffie for pointing it out!\n- Fix sidebar error when no certificate provider selected\n- Skip invalid files on database rebuilding\n- Fix random websocket connection error popups\n- Fix new siteCreate command\n- Fix site size calculation\n- Fix port open checking after computer wake up\n- Fix --size_limit parsing from command line\n\n\n## ZeroNet 0.4.0 (2016-08-11)\n### Added\n- Merger site plugin\n- Live source code reloading: Faster core development by allowing me to make changes in ZeroNet source code without restarting it.\n- New json table format for merger sites\n- Database rebuild from sidebar.\n- Allow to store custom data directly in json table: Much simpler and faster SQL queries.\n- User file archiving: Allows the site owner to archive inactive user's content into single file. (Reducing initial sync time/cpu/memory usage)\n- Also trigger onUpdated/update database on file delete.\n- Permission request from ZeroFrame API.\n- Allow to store extra data in content.json using fileWrite API command.\n- Faster optional files downloading\n- Use alternative sources (Gogs, Gitlab) to download updates\n- Track provided sites/connection and prefer to keep the ones with more sites to reduce connection number\n\n### Changed\n- Keep at least 5 connection per site\n- Changed target connection for sites to 10 from 15\n- ZeroHello search function stability/speed improvements\n- Improvements for clients with slower HDD\n\n### Fixed\n- Fix IE11 wrapper nonce errors\n- Fix sidebar on mobile devices\n- Fix site size calculation\n- Fix IE10 compatibility\n- Windows XP ZeroBundle compatibility (THX to people of China)\n\n\n## ZeroNet 0.3.7 (2016-05-27)\n### Changed\n- Patch command to reduce bandwidth usage by transfer only the changed lines\n- Other cpu/memory optimizations\n\n\n## ZeroNet 0.3.6 (2016-05-27)\n### Added\n- New ZeroHello\n- Newsfeed function\n\n### Fixed\n- Security fixes\n\n\n## ZeroNet 0.3.5 (2016-02-02)\n### Added\n- Full Tor support with .onion hidden services\n- Bootstrap using ZeroNet protocol\n\n### Fixed\n- Fix Gevent 1.0.2 compatibility\n\n\n## ZeroNet 0.3.4 (2015-12-28)\n### Added\n- AES, ECIES API function support\n- PushState and ReplaceState url manipulation support in API\n- Multiuser localstorage\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM alpine:3.11 \n\n#Base settings\nENV HOME /root\n\nCOPY requirements.txt /root/requirements.txt\n\n#Install ZeroNet\nRUN apk --update --no-cache --no-progress add python3 python3-dev gcc libffi-dev musl-dev make tor openssl \\\n && pip3 install -r /root/requirements.txt \\\n && apk del python3-dev gcc libffi-dev musl-dev make \\\n && echo \"ControlPort 9051\" >> /etc/tor/torrc \\\n && echo \"CookieAuthentication 1\" >> /etc/tor/torrc\n \nRUN python3 -V \\\n && python3 -m pip list \\\n && tor --version \\\n && openssl version\n\n#Add Zeronet source\nCOPY . /root\nVOLUME /root/data\n\n#Control if Tor proxy is started\nENV ENABLE_TOR false\n\nWORKDIR /root\n\n#Set upstart command\nCMD (! ${ENABLE_TOR} || tor&) && python3 zeronet.py --ui_ip 0.0.0.0 --fileserver_port 26552\n\n#Expose ports\nEXPOSE 43110 26552\n"
  },
  {
    "path": "Dockerfile.arm64v8",
    "content": "FROM alpine:3.12\n\n#Base settings\nENV HOME /root\n\nCOPY requirements.txt /root/requirements.txt\n\n#Install ZeroNet\nRUN apk --update --no-cache --no-progress add python3 python3-dev gcc libffi-dev musl-dev make tor openssl \\\n && pip3 install -r /root/requirements.txt \\\n && apk del python3-dev gcc libffi-dev musl-dev make \\\n && echo \"ControlPort 9051\" >> /etc/tor/torrc \\\n && echo \"CookieAuthentication 1\" >> /etc/tor/torrc\n \nRUN python3 -V \\\n && python3 -m pip list \\\n && tor --version \\\n && openssl version\n\n#Add Zeronet source\nCOPY . /root\nVOLUME /root/data\n\n#Control if Tor proxy is started\nENV ENABLE_TOR false\n\nWORKDIR /root\n\n#Set upstart command\nCMD (! ${ENABLE_TOR} || tor&) && python3 zeronet.py --ui_ip 0.0.0.0 --fileserver_port 26552\n\n#Expose ports\nEXPOSE 43110 26552\n\n"
  },
  {
    "path": "LICENSE",
    "content": "This program is free software: you can redistribute it and/or modify\r\nit under the terms of the GNU General Public License as published by\r\nthe Free Software Foundation, version 3.\r\n\r\nThis program is distributed in the hope that it will be useful,\r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\nGNU General Public License for more details.\r\n\r\nYou should have received a copy of the GNU General Public License\r\nalong with this program. If not, see <https://www.gnu.org/licenses/>.\r\n\r\n\r\nAdditional Conditions :\r\n\r\nContributing to this repo\r\n This repo is governed by GPLv3, same is located at the root of the ZeroNet git repo, \r\n unless specified separately all code is governed by that license, contributions to this repo \r\n are divided into two key types, key contributions and non-key contributions, key contributions \r\n are which, directly affects the code performance, quality and features of software, \r\n non key contributions include things like translation datasets, image, graphic or video \r\n contributions that does not affect the main usability of software but improves the existing \r\n usability of certain thing or feature, these also include tests written with code, since their \r\n purpose is to check, whether something is working or not as intended. All the non-key contributions \r\n are governed by [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/), unless specified \r\n above, a contribution is ruled by the type of contribution if there is a conflict between two \r\n contributing parties of repo in any case.\r\n"
  },
  {
    "path": "README-ru.md",
    "content": "# ZeroNet [![Build Status](https://travis-ci.org/HelloZeroNet/ZeroNet.svg?branch=master)](https://travis-ci.org/HelloZeroNet/ZeroNet) [![Documentation](https://img.shields.io/badge/docs-faq-brightgreen.svg)](https://zeronet.io/docs/faq/) [![Help](https://img.shields.io/badge/keep_this_project_alive-donate-yellow.svg)](https://zeronet.io/docs/help_zeronet/donate/)\n\n[简体中文](./README-zh-cn.md)\n[English](./README.md)\n\nДецентрализованные вебсайты использующие Bitcoin криптографию и BitTorrent сеть - https://zeronet.io\n\n\n## Зачем?\n\n* Мы верим в открытую, свободную, и не отцензуренную сеть и коммуникацию.\n* Нет единой точки отказа: Сайт онлайн пока по крайней мере 1 пир обслуживает его.\n* Никаких затрат на хостинг: Сайты обслуживаются посетителями.\n* Невозможно отключить: Он нигде, потому что он везде.\n* Быстр и работает оффлайн: Вы можете получить доступ к сайту, даже если Интернет недоступен.\n\n\n## Особенности\n * Обновляемые в реальном времени сайты\n * Поддержка Namecoin .bit доменов\n * Лёгок в установке: распаковал & запустил\n * Клонирование вебсайтов в один клик\n * Password-less [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)\n   based authorization: Ваша учетная запись защищена той же криптографией, что и ваш Bitcoin-кошелек\n * Встроенный SQL-сервер с синхронизацией данных P2P: Позволяет упростить разработку сайта и ускорить загрузку страницы\n * Анонимность: Полная поддержка сети Tor с помощью скрытых служб .onion вместо адресов IPv4\n * TLS зашифрованные связи\n * Автоматическое открытие uPnP порта\n * Плагин для поддержки многопользовательской (openproxy)\n * Работает с любыми браузерами и операционными системами\n\n\n## Как это работает?\n\n* После запуска `zeronet.py` вы сможете посетить зайты (zeronet сайты) используя адрес\n  `http://127.0.0.1:43110/{zeronet_address}`\n(например. `http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D`).\n* Когда вы посещаете новый сайт zeronet, он пытается найти пиров с помощью BitTorrent\n  чтобы загрузить файлы сайтов (html, css, js ...) из них.\n* Каждый посещенный зайт также обслуживается вами. (Т.е хранится у вас на компьютере)\n* Каждый сайт содержит файл `content.json`, который содержит все остальные файлы в хэше sha512\n  и подпись, созданную с использованием частного ключа сайта.\n* Если владелец сайта (у которого есть закрытый ключ для адреса сайта) изменяет сайт, то он/она\n  подписывает новый `content.json` и публикует его для пиров. После этого пиры проверяют целостность `content.json`\n  (используя подпись), они загружают измененные файлы и публикуют новый контент для других пиров.\n\n####  [Слайд-шоу о криптографии ZeroNet, обновлениях сайтов, многопользовательских сайтах »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)\n####  [Часто задаваемые вопросы »](https://zeronet.io/docs/faq/)\n\n####  [Документация разработчика ZeroNet »](https://zeronet.io/docs/site_development/getting_started/)\n\n\n## Скриншоты\n\n![Screenshot](https://i.imgur.com/H60OAHY.png)\n![ZeroTalk](https://zeronet.io/docs/img/zerotalk.png)\n\n#### [Больше скриншотов в ZeroNet документации »](https://zeronet.io/docs/using_zeronet/sample_sites/)\n\n\n## Как вступить\n\n* Скачайте ZeroBundle пакет:\n  * [Microsoft Windows](https://github.com/HelloZeroNet/ZeroNet-win/archive/dist/ZeroNet-win.zip)\n  * [Apple macOS](https://github.com/HelloZeroNet/ZeroNet-mac/archive/dist/ZeroNet-mac.zip)\n  * [Linux 64-bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux64.tar.gz)\n  * [Linux 32-bit](https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux32.tar.gz)\n* Распакуйте где угодно\n* Запустите `ZeroNet.exe` (win), `ZeroNet(.app)` (osx), `ZeroNet.sh` (linux)\n\n### Linux терминал\n\n* `wget https://github.com/HelloZeroNet/ZeroBundle/raw/master/dist/ZeroBundle-linux64.tar.gz`\n* `tar xvpfz ZeroBundle-linux64.tar.gz`\n* `cd ZeroBundle`\n* Запустите с помощью `./ZeroNet.sh`\n\nОн загружает последнюю версию ZeroNet, затем запускает её автоматически.\n\n#### Ручная установка для Debian Linux\n\n* `sudo apt-get update`\n* `sudo apt-get install msgpack-python python-gevent`\n* `wget https://github.com/HelloZeroNet/ZeroNet/archive/master.tar.gz`\n* `tar xvpfz master.tar.gz`\n* `cd ZeroNet-master`\n* Запустите с помощью `python2 zeronet.py`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\n### [Arch Linux](https://www.archlinux.org)\n\n* `git clone https://aur.archlinux.org/zeronet.git`\n* `cd zeronet`\n* `makepkg -srci`\n* `systemctl start zeronet`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\nСмотрите [ArchWiki](https://wiki.archlinux.org)'s [ZeroNet\narticle](https://wiki.archlinux.org/index.php/ZeroNet) для дальнейшей помощи.\n\n### [Gentoo Linux](https://www.gentoo.org)\n\n* [`layman -a raiagent`](https://github.com/leycec/raiagent)\n* `echo '>=net-vpn/zeronet-0.5.4' >> /etc/portage/package.accept_keywords`\n* *(Опционально)* Включить поддержку Tor: `echo 'net-vpn/zeronet tor' >>\n  /etc/portage/package.use`\n* `emerge zeronet`\n* `rc-service zeronet start`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\nСмотрите `/usr/share/doc/zeronet-*/README.gentoo.bz2` для дальнейшей помощи.\n\n### [FreeBSD](https://www.freebsd.org/)\n\n* `pkg install zeronet` or `cd /usr/ports/security/zeronet/ && make install clean`\n* `sysrc zeronet_enable=\"YES\"`\n* `service zeronet start`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\n### [Vagrant](https://www.vagrantup.com/)\n\n* `vagrant up`\n* Подключитесь к VM с помощью `vagrant ssh`\n* `cd /vagrant`\n* Запустите `python2 zeronet.py --ui_ip 0.0.0.0`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\n### [Docker](https://www.docker.com/)\n* `docker run -d -v <local_data_folder>:/root/data -p 15441:15441 -p 127.0.0.1:43110:43110 nofish/zeronet`\n* Это изображение Docker включает в себя прокси-сервер Tor, который по умолчанию отключён.\n  Остерегайтесь что некоторые хостинг-провайдеры могут не позволить вам запускать Tor на своих серверах.\n  Если вы хотите включить его,установите переменную среды `ENABLE_TOR` в` true` (по умолчанию: `false`) Например:\n\n `docker run -d -e \"ENABLE_TOR=true\" -v <local_data_folder>:/root/data -p 15441:15441 -p 127.0.0.1:43110:43110 nofish/zeronet`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\n### [Virtualenv](https://virtualenv.readthedocs.org/en/latest/)\n\n* `virtualenv env`\n* `source env/bin/activate`\n* `pip install msgpack gevent`\n* `python2 zeronet.py`\n* Откройте http://127.0.0.1:43110/ в вашем браузере.\n\n## Текущие ограничения\n\n* ~~Нет torrent-похожего файла разделения для поддержки больших файлов~~ (поддержка больших файлов добавлена)\n* ~~Не анонимнее чем Bittorrent~~ (добавлена встроенная поддержка Tor)\n* Файловые транзакции не сжаты ~~ или незашифрованы еще ~~ (добавлено шифрование TLS)\n* Нет приватных сайтов\n\n\n## Как я могу создать сайт в Zeronet?\n\nЗавершите работу zeronet, если он запущен\n\n```bash\n$ zeronet.py siteCreate\n...\n- Site private key (Приватный ключ сайта): 23DKQpzxhbVBrAtvLEc2uvk7DZweh4qL3fn3jpM3LgHDczMK2TtYUq\n- Site address (Адрес сайта): 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2\n...\n- Site created! (Сайт создан)\n$ zeronet.py\n...\n```\n\nПоздравляем, вы закончили! Теперь каждый может получить доступ к вашему зайту используя\n`http://localhost:43110/13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2`\n\nСледующие шаги: [ZeroNet Developer Documentation](https://zeronet.io/docs/site_development/getting_started/)\n\n\n## Как я могу модифицировать Zeronet сайт?\n\n* Измените файлы расположенные в data/13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2 директории.\n  Когда закончите с изменением:\n\n```bash\n$ zeronet.py siteSign 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2\n- Signing site (Подпись сайта): 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2...\nPrivate key (Приватный ключ) (input hidden):\n```\n\n* Введите секретный ключ, который вы получили при создании сайта, потом:\n\n```bash\n$ zeronet.py sitePublish 13DNDkMUExRf9Xa9ogwPKqp7zyHFEqbhC2\n...\nSite:13DNDk..bhC2 Publishing to 3/10 peers...\nSite:13DNDk..bhC2 Successfuly published to 3 peers\n- Serving files....\n```\n\n* Вот и всё! Вы успешно подписали и опубликовали свои изменения.\n\n\n## Поддержите проект\n\n- Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX\n- Paypal: https://zeronet.io/docs/help_zeronet/donate/\n\n### Спонсоры\n\n* Улучшенная совместимость с MacOS / Safari стала возможной благодаря [BrowserStack.com](https://www.browserstack.com)\n\n#### Спасибо!\n\n* Больше информации, помощь, журнал изменений, zeronet сайты: https://www.reddit.com/r/zeronet/\n* Приходите, пообщайтесь с нами: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) или на [gitter](https://gitter.im/HelloZeroNet/ZeroNet)\n* Email: hello@zeronet.io (PGP: CB9613AE)\n"
  },
  {
    "path": "README-zh-cn.md",
    "content": "# ZeroNet [![Build Status](https://travis-ci.org/HelloZeroNet/ZeroNet.svg?branch=py3)](https://travis-ci.org/HelloZeroNet/ZeroNet) [![Documentation](https://img.shields.io/badge/docs-faq-brightgreen.svg)](https://zeronet.io/docs/faq/) [![Help](https://img.shields.io/badge/keep_this_project_alive-donate-yellow.svg)](https://zeronet.io/docs/help_zeronet/donate/)\n\n[English](./README.md)\n\n使用 Bitcoin 加密和 BitTorrent 网络的去中心化网络 - https://zeronet.io\n\n\n## 为什么？\n\n* 我们相信开放，自由，无审查的网络和通讯\n* 不会受单点故障影响：只要有在线的节点，站点就会保持在线\n* 无托管费用：站点由访问者托管\n* 无法关闭：因为节点无处不在\n* 快速并可离线运行：即使没有互联网连接也可以使用\n\n\n## 功能\n * 实时站点更新\n * 支持 Namecoin 的 .bit 域名\n * 安装方便：只需解压并运行\n * 一键克隆存在的站点\n * 无需密码、基于 [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)\n   的认证：您的账户被与比特币钱包相同的加密方法保护\n * 内建 SQL 服务器和 P2P 数据同步：让开发更简单并提升加载速度\n * 匿名性：完整的 Tor 网络支持，支持通过 .onion 隐藏服务相互连接而不是通过 IPv4 地址连接\n * TLS 加密连接\n * 自动打开 uPnP 端口\n * 多用户（openproxy）支持的插件\n * 适用于任何浏览器 / 操作系统\n\n\n## 原理\n\n* 在运行 `zeronet.py` 后，您将可以通过\n  `http://127.0.0.1:43110/{zeronet_address}`（例如：\n  `http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D`）访问 zeronet 中的站点\n* 在您浏览 zeronet 站点时，客户端会尝试通过 BitTorrent 网络来寻找可用的节点，从而下载需要的文件（html，css，js...）\n* 您将会储存每一个浏览过的站点\n* 每个站点都包含一个名为 `content.json` 的文件，它储存了其他所有文件的 sha512 散列值以及一个通过站点私钥生成的签名\n* 如果站点的所有者（拥有站点地址的私钥）修改了站点，并且他 / 她签名了新的 `content.json` 然后推送至其他节点，\n  那么这些节点将会在使用签名验证 `content.json` 的真实性后，下载修改后的文件并将新内容推送至另外的节点\n\n####  [关于 ZeroNet 加密，站点更新，多用户站点的幻灯片 »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)\n####  [常见问题 »](https://zeronet.io/docs/faq/)\n\n####  [ZeroNet 开发者文档 »](https://zeronet.io/docs/site_development/getting_started/)\n\n\n## 屏幕截图\n\n![Screenshot](https://i.imgur.com/H60OAHY.png)\n![ZeroTalk](https://zeronet.io/docs/img/zerotalk.png)\n\n#### [ZeroNet 文档中的更多屏幕截图 »](https://zeronet.io/docs/using_zeronet/sample_sites/)\n\n\n## 如何加入\n\n### Windows\n\n - 下载 [ZeroNet-py3-win64.zip](https://github.com/HelloZeroNet/ZeroNet-win/archive/dist-win64/ZeroNet-py3-win64.zip) (18MB)\n - 在任意位置解压缩\n - 运行 `ZeroNet.exe`\n \n### macOS\n\n - 下载 [ZeroNet-dist-mac.zip](https://github.com/HelloZeroNet/ZeroNet-dist/archive/mac/ZeroNet-dist-mac.zip) (13.2MB)\n - 在任意位置解压缩\n - 运行 `ZeroNet.app`\n \n### Linux (x86-64bit)\n\n - `wget https://github.com/HelloZeroNet/ZeroNet-linux/archive/dist-linux64/ZeroNet-py3-linux64.tar.gz`\n - `tar xvpfz ZeroNet-py3-linux64.tar.gz`\n - `cd ZeroNet-linux-dist-linux64/`\n - 使用以下命令启动 `./ZeroNet.sh`\n - 在浏览器打开 http://127.0.0.1:43110/ 即可访问 ZeroHello 页面\n \n __提示：__ 若要允许在 Web 界面上的远程连接，使用以下命令启动 `./ZeroNet.sh --ui_ip '*' --ui_restrict your.ip.address`\n\n### 从源代码安装\n\n - `wget https://github.com/HelloZeroNet/ZeroNet/archive/py3/ZeroNet-py3.tar.gz`\n - `tar xvpfz ZeroNet-py3.tar.gz`\n - `cd ZeroNet-py3`\n - `sudo apt-get update`\n - `sudo apt-get install python3-pip`\n - `sudo python3 -m pip install -r requirements.txt`\n - 使用以下命令启动 `python3 zeronet.py`\n - 在浏览器打开 http://127.0.0.1:43110/ 即可访问 ZeroHello 页面\n\n## 现有限制\n\n* ~~没有类似于 torrent 的文件拆分来支持大文件~~ （已添加大文件支持）\n* ~~没有比 BitTorrent 更好的匿名性~~ （已添加内置的完整 Tor 支持）\n* 传输文件时没有压缩~~和加密~~ （已添加 TLS 支持）\n* 不支持私有站点\n\n\n## 如何创建一个 ZeroNet 站点？\n\n * 点击 [ZeroHello](http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D) 站点的 **⋮** > **「新建空站点」** 菜单项\n * 您将被**重定向**到一个全新的站点，该站点只能由您修改\n * 您可以在 **data/[您的站点地址]** 目录中找到并修改网站的内容\n * 修改后打开您的网站，将右上角的「0」按钮拖到左侧，然后点击底部的**签名**并**发布**按钮\n\n接下来的步骤：[ZeroNet 开发者文档](https://zeronet.io/docs/site_development/getting_started/)\n\n## 帮助这个项目\n\n- Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX\n- Paypal: https://zeronet.io/docs/help_zeronet/donate/\n\n### 赞助商\n\n* [BrowserStack.com](https://www.browserstack.com) 使更好的 macOS/Safari 兼容性成为可能\n\n#### 感谢您！\n\n* 更多信息，帮助，变更记录和 zeronet 站点：https://www.reddit.com/r/zeronet/\n* 前往 [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) 或 [gitter](https://gitter.im/HelloZeroNet/ZeroNet) 和我们聊天\n* [这里](https://gitter.im/ZeroNet-zh/Lobby)是一个 gitter 上的中文聊天室\n* Email: hello@zeronet.io (PGP: [960F FF2D 6C14 5AA6 13E8 491B 5B63 BAE6 CB96 13AE](https://zeronet.io/files/tamas@zeronet.io_pub.asc))\n"
  },
  {
    "path": "README.md",
    "content": "# ZeroNet [![Build Status](https://travis-ci.org/HelloZeroNet/ZeroNet.svg?branch=py3)](https://travis-ci.org/HelloZeroNet/ZeroNet) [![Documentation](https://img.shields.io/badge/docs-faq-brightgreen.svg)](https://zeronet.io/docs/faq/) [![Help](https://img.shields.io/badge/keep_this_project_alive-donate-yellow.svg)](https://zeronet.io/docs/help_zeronet/donate/) ![tests](https://github.com/HelloZeroNet/ZeroNet/workflows/tests/badge.svg) [![Docker Pulls](https://img.shields.io/docker/pulls/nofish/zeronet)](https://hub.docker.com/r/nofish/zeronet)\n\nDecentralized websites using Bitcoin crypto and the BitTorrent network - https://zeronet.io / [onion](http://zeronet34m3r5ngdu54uj57dcafpgdjhxsgq5kla5con4qvcmfzpvhad.onion)\n\n\n## Why?\n\n* We believe in open, free, and uncensored network and communication.\n* No single point of failure: Site remains online so long as at least 1 peer is\n  serving it.\n* No hosting costs: Sites are served by visitors.\n* Impossible to shut down: It's nowhere because it's everywhere.\n* Fast and works offline: You can access the site even if Internet is\n  unavailable.\n\n\n## Features\n * Real-time updated sites\n * Namecoin .bit domains support\n * Easy to setup: unpack & run\n * Clone websites in one click\n * Password-less [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)\n   based authorization: Your account is protected by the same cryptography as your Bitcoin wallet\n * Built-in SQL server with P2P data synchronization: Allows easier site development and faster page load times\n * Anonymity: Full Tor network support with .onion hidden services instead of IPv4 addresses\n * TLS encrypted connections\n * Automatic uPnP port opening\n * Plugin for multiuser (openproxy) support\n * Works with any browser/OS\n\n\n## How does it work?\n\n* After starting `zeronet.py` you will be able to visit zeronet sites using\n  `http://127.0.0.1:43110/{zeronet_address}` (eg.\n  `http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D`).\n* When you visit a new zeronet site, it tries to find peers using the BitTorrent\n  network so it can download the site files (html, css, js...) from them.\n* Each visited site is also served by you.\n* Every site contains a `content.json` file which holds all other files in a sha512 hash\n  and a signature generated using the site's private key.\n* If the site owner (who has the private key for the site address) modifies the\n  site, then he/she signs the new `content.json` and publishes it to the peers.\n  Afterwards, the peers verify the `content.json` integrity (using the\n  signature), they download the modified files and publish the new content to\n  other peers.\n\n####  [Slideshow about ZeroNet cryptography, site updates, multi-user sites »](https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000)\n####  [Frequently asked questions »](https://zeronet.io/docs/faq/)\n\n####  [ZeroNet Developer Documentation »](https://zeronet.io/docs/site_development/getting_started/)\n\n\n## Screenshots\n\n![Screenshot](https://i.imgur.com/H60OAHY.png)\n![ZeroTalk](https://zeronet.io/docs/img/zerotalk.png)\n\n#### [More screenshots in ZeroNet docs »](https://zeronet.io/docs/using_zeronet/sample_sites/)\n\n\n## How to join\n\n### Windows\n\n - Download [ZeroNet-py3-win64.zip](https://github.com/HelloZeroNet/ZeroNet-win/archive/dist-win64/ZeroNet-py3-win64.zip) (18MB)\n - Unpack anywhere\n - Run `ZeroNet.exe`\n \n### macOS\n\n - Download [ZeroNet-dist-mac.zip](https://github.com/HelloZeroNet/ZeroNet-dist/archive/mac/ZeroNet-dist-mac.zip) (13.2MB)\n - Unpack anywhere\n - Run `ZeroNet.app`\n \n### Linux (x86-64bit)\n - `wget https://github.com/HelloZeroNet/ZeroNet-linux/archive/dist-linux64/ZeroNet-py3-linux64.tar.gz`\n - `tar xvpfz ZeroNet-py3-linux64.tar.gz`\n - `cd ZeroNet-linux-dist-linux64/`\n - Start with: `./ZeroNet.sh`\n - Open the ZeroHello landing page in your browser by navigating to: http://127.0.0.1:43110/\n \n __Tip:__ Start with `./ZeroNet.sh --ui_ip '*' --ui_restrict your.ip.address` to allow remote connections on the web interface.\n \n ### Android (arm, arm64, x86)\n - minimum Android version supported 16 (JellyBean)\n - [<img src=\"https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png\" \n      alt=\"Download from Google Play\" \n      height=\"80\">](https://play.google.com/store/apps/details?id=in.canews.zeronetmobile)\n - APK download: https://github.com/canewsin/zeronet_mobile/releases\n - XDA Labs: https://labs.xda-developers.com/store/app/in.canews.zeronet\n \n#### Docker\nThere is an official image, built from source at: https://hub.docker.com/r/nofish/zeronet/\n\n### Install from source\n\n - `wget https://github.com/HelloZeroNet/ZeroNet/archive/py3/ZeroNet-py3.tar.gz`\n - `tar xvpfz ZeroNet-py3.tar.gz`\n - `cd ZeroNet-py3`\n - `sudo apt-get update`\n - `sudo apt-get install python3-pip`\n - `sudo python3 -m pip install -r requirements.txt`\n - Start with: `python3 zeronet.py`\n - Open the ZeroHello landing page in your browser by navigating to: http://127.0.0.1:43110/\n\n## Current limitations\n\n* ~~No torrent-like file splitting for big file support~~ (big file support added)\n* ~~No more anonymous than Bittorrent~~ (built-in full Tor support added)\n* File transactions are not compressed ~~or encrypted yet~~ (TLS encryption added)\n* No private sites\n\n\n## How can I create a ZeroNet site?\n\n * Click on **⋮** > **\"Create new, empty site\"** menu item on the site [ZeroHello](http://127.0.0.1:43110/1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D).\n * You will be **redirected** to a completely new site that is only modifiable by you!\n * You can find and modify your site's content in **data/[yoursiteaddress]** directory\n * After the modifications open your site, drag the topright \"0\" button to left, then press **sign** and **publish** buttons on the bottom\n\nNext steps: [ZeroNet Developer Documentation](https://zeronet.io/docs/site_development/getting_started/)\n\n## Help keep this project alive\n\n- Bitcoin: 1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX\n- Paypal: https://zeronet.io/docs/help_zeronet/donate/\n\n### Sponsors\n\n* Better macOS/Safari compatibility made possible by [BrowserStack.com](https://www.browserstack.com)\n\n#### Thank you!\n\n* More info, help, changelog, zeronet sites: https://www.reddit.com/r/zeronet/\n* Come, chat with us: [#zeronet @ FreeNode](https://kiwiirc.com/client/irc.freenode.net/zeronet) or on [gitter](https://gitter.im/HelloZeroNet/ZeroNet)\n* Email: hello@zeronet.io (PGP: [960F FF2D 6C14 5AA6 13E8 491B 5B63 BAE6 CB96 13AE](https://zeronet.io/files/tamas@zeronet.io_pub.asc))\n"
  },
  {
    "path": "Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\nVAGRANTFILE_API_VERSION = \"2\"\n\nVagrant.configure(VAGRANTFILE_API_VERSION) do |config|\n\n  #Set box\n  config.vm.box = \"ubuntu/trusty64\"\n\n  #Do not check fo updates\n  config.vm.box_check_update = false\n\n  #Add private network\n  config.vm.network \"private_network\", type: \"dhcp\"\n\n  #Redirect ports\n  config.vm.network \"forwarded_port\", guest: 43110, host: 43110\n  config.vm.network \"forwarded_port\", guest: 15441, host: 15441\n\n  #Sync folder using NFS if not windows\n  config.vm.synced_folder \".\", \"/vagrant\",\n      :nfs => !Vagrant::Util::Platform.windows?\n\n  #Virtal Box settings\n  config.vm.provider \"virtualbox\" do |vb|\n    # Don't boot with headless mode\n    #vb.gui = true\n\n    # Set VM settings\n    vb.customize [\"modifyvm\", :id, \"--memory\", \"512\"]\n    vb.customize [\"modifyvm\", :id, \"--cpus\", 1]\n  end\n\n  #Update system\n  config.vm.provision \"shell\",\n      inline: \"sudo apt-get update -y && sudo apt-get upgrade -y\"\n\n  #Install deps\n  config.vm.provision \"shell\",\n      inline: \"sudo apt-get install msgpack-python python-gevent python-pip python-dev -y\"\n  config.vm.provision \"shell\",\n      inline: \"sudo pip install msgpack --upgrade\"\n\nend\n"
  },
  {
    "path": "plugins/AnnounceBitTorrent/AnnounceBitTorrentPlugin.py",
    "content": "import time\nimport urllib.request\nimport struct\nimport socket\n\nimport lib.bencode_open as bencode_open\nfrom lib.subtl.subtl import UdpTrackerClient\nimport socks\nimport sockshandler\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\nfrom util import helper\n\n\n# We can only import plugin host clases after the plugins are loaded\n@PluginManager.afterLoad\ndef importHostClasses():\n    global Peer, AnnounceError\n    from Peer import Peer\n    from Site.SiteAnnouncer import AnnounceError\n\n\n@PluginManager.registerTo(\"SiteAnnouncer\")\nclass SiteAnnouncerPlugin(object):\n    def getSupportedTrackers(self):\n        trackers = super(SiteAnnouncerPlugin, self).getSupportedTrackers()\n        if config.disable_udp or config.trackers_proxy != \"disable\":\n            trackers = [tracker for tracker in trackers if not tracker.startswith(\"udp://\")]\n\n        return trackers\n\n    def getTrackerHandler(self, protocol):\n        if protocol == \"udp\":\n            handler = self.announceTrackerUdp\n        elif protocol == \"http\":\n            handler = self.announceTrackerHttp\n        elif protocol == \"https\":\n            handler = self.announceTrackerHttps\n        else:\n            handler = super(SiteAnnouncerPlugin, self).getTrackerHandler(protocol)\n        return handler\n\n    def announceTrackerUdp(self, tracker_address, mode=\"start\", num_want=10):\n        s = time.time()\n        if config.disable_udp:\n            raise AnnounceError(\"Udp disabled by config\")\n        if config.trackers_proxy != \"disable\":\n            raise AnnounceError(\"Udp trackers not available with proxies\")\n\n        ip, port = tracker_address.split(\"/\")[0].split(\":\")\n        tracker = UdpTrackerClient(ip, int(port))\n        if helper.getIpType(ip) in self.getOpenedServiceTypes():\n            tracker.peer_port = self.fileserver_port\n        else:\n            tracker.peer_port = 0\n        tracker.connect()\n        if not tracker.poll_once():\n            raise AnnounceError(\"Could not connect\")\n        tracker.announce(info_hash=self.site.address_sha1, num_want=num_want, left=431102370)\n        back = tracker.poll_once()\n        if not back:\n            raise AnnounceError(\"No response after %.0fs\" % (time.time() - s))\n        elif type(back) is dict and \"response\" in back:\n            peers = back[\"response\"][\"peers\"]\n        else:\n            raise AnnounceError(\"Invalid response: %r\" % back)\n\n        return peers\n\n    def httpRequest(self, url):\n        headers = {\n            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',\n            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',\n            'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',\n            'Accept-Encoding': 'none',\n            'Accept-Language': 'en-US,en;q=0.8',\n            'Connection': 'keep-alive'\n        }\n\n        req = urllib.request.Request(url, headers=headers)\n\n        if config.trackers_proxy == \"tor\":\n            tor_manager = self.site.connection_server.tor_manager\n            handler = sockshandler.SocksiPyHandler(socks.SOCKS5, tor_manager.proxy_ip, tor_manager.proxy_port)\n            opener = urllib.request.build_opener(handler)\n            return opener.open(req, timeout=50)\n        elif config.trackers_proxy == \"disable\":\n            return urllib.request.urlopen(req, timeout=25)\n        else:\n            proxy_ip, proxy_port = config.trackers_proxy.split(\":\")\n            handler = sockshandler.SocksiPyHandler(socks.SOCKS5, proxy_ip, int(proxy_port))\n            opener = urllib.request.build_opener(handler)\n            return opener.open(req, timeout=50)\n\n    def announceTrackerHttps(self, *args, **kwargs):\n        kwargs[\"protocol\"] = \"https\"\n        return self.announceTrackerHttp(*args, **kwargs)\n\n    def announceTrackerHttp(self, tracker_address, mode=\"start\", num_want=10, protocol=\"http\"):\n        tracker_ip, tracker_port = tracker_address.rsplit(\":\", 1)\n        if helper.getIpType(tracker_ip) in self.getOpenedServiceTypes():\n            port = self.fileserver_port\n        else:\n            port = 1\n        params = {\n            'info_hash': self.site.address_sha1,\n            'peer_id': self.peer_id, 'port': port,\n            'uploaded': 0, 'downloaded': 0, 'left': 431102370, 'compact': 1, 'numwant': num_want,\n            'event': 'started'\n        }\n\n        url = protocol + \"://\" + tracker_address + \"?\" + urllib.parse.urlencode(params)\n\n        s = time.time()\n        response = None\n        # Load url\n        if config.tor == \"always\" or config.trackers_proxy != \"disable\":\n            timeout = 60\n        else:\n            timeout = 30\n\n        with gevent.Timeout(timeout, False):  # Make sure of timeout\n            req = self.httpRequest(url)\n            response = req.read()\n            req.close()\n            req = None\n\n        if not response:\n            raise AnnounceError(\"No response after %.0fs\" % (time.time() - s))\n\n        # Decode peers\n        try:\n            peer_data = bencode_open.loads(response)[b\"peers\"]\n            response = None\n            peer_count = int(len(peer_data) / 6)\n            peers = []\n            for peer_offset in range(peer_count):\n                off = 6 * peer_offset\n                peer = peer_data[off:off + 6]\n                addr, port = struct.unpack('!LH', peer)\n                peers.append({\"addr\": socket.inet_ntoa(struct.pack('!L', addr)), \"port\": port})\n        except Exception as err:\n            raise AnnounceError(\"Invalid response: %r (%s)\" % (response, Debug.formatException(err)))\n\n        return peers\n"
  },
  {
    "path": "plugins/AnnounceBitTorrent/__init__.py",
    "content": "from . import AnnounceBitTorrentPlugin"
  },
  {
    "path": "plugins/AnnounceBitTorrent/plugin_info.json",
    "content": "{\n\t\"name\": \"AnnounceBitTorrent\",\n\t\"description\": \"Discover new peers using BitTorrent trackers.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/AnnounceLocal/AnnounceLocalPlugin.py",
    "content": "import time\n\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom . import BroadcastServer\n\n\n@PluginManager.registerTo(\"SiteAnnouncer\")\nclass SiteAnnouncerPlugin(object):\n    def announce(self, force=False, *args, **kwargs):\n        local_announcer = self.site.connection_server.local_announcer\n\n        thread = None\n        if local_announcer and (force or time.time() - local_announcer.last_discover > 5 * 60):\n            thread = gevent.spawn(local_announcer.discover, force=force)\n        back = super(SiteAnnouncerPlugin, self).announce(force=force, *args, **kwargs)\n\n        if thread:\n            thread.join()\n\n        return back\n\n\nclass LocalAnnouncer(BroadcastServer.BroadcastServer):\n    def __init__(self, server, listen_port):\n        super(LocalAnnouncer, self).__init__(\"zeronet\", listen_port=listen_port)\n        self.server = server\n\n        self.sender_info[\"peer_id\"] = self.server.peer_id\n        self.sender_info[\"port\"] = self.server.port\n        self.sender_info[\"broadcast_port\"] = listen_port\n        self.sender_info[\"rev\"] = config.rev\n\n        self.known_peers = {}\n        self.last_discover = 0\n\n    def discover(self, force=False):\n        self.log.debug(\"Sending discover request (force: %s)\" % force)\n        self.last_discover = time.time()\n        if force:  # Probably new site added, clean cache\n            self.known_peers = {}\n\n        for peer_id, known_peer in list(self.known_peers.items()):\n            if time.time() - known_peer[\"found\"] > 20 * 60:\n                del(self.known_peers[peer_id])\n                self.log.debug(\"Timeout, removing from known_peers: %s\" % peer_id)\n        self.broadcast({\"cmd\": \"discoverRequest\", \"params\": {}}, port=self.listen_port)\n\n    def actionDiscoverRequest(self, sender, params):\n        back = {\n            \"cmd\": \"discoverResponse\",\n            \"params\": {\n                \"sites_changed\": self.server.site_manager.sites_changed\n            }\n        }\n\n        if sender[\"peer_id\"] not in self.known_peers:\n            self.known_peers[sender[\"peer_id\"]] = {\"added\": time.time(), \"sites_changed\": 0, \"updated\": 0, \"found\": time.time()}\n            self.log.debug(\"Got discover request from unknown peer %s (%s), time to refresh known peers\" % (sender[\"ip\"], sender[\"peer_id\"]))\n            gevent.spawn_later(1.0, self.discover)  # Let the response arrive first to the requester\n\n        return back\n\n    def actionDiscoverResponse(self, sender, params):\n        if sender[\"peer_id\"] in self.known_peers:\n            self.known_peers[sender[\"peer_id\"]][\"found\"] = time.time()\n        if params[\"sites_changed\"] != self.known_peers.get(sender[\"peer_id\"], {}).get(\"sites_changed\"):\n            # Peer's site list changed, request the list of new sites\n            return {\"cmd\": \"siteListRequest\"}\n        else:\n            # Peer's site list is the same\n            for site in self.server.sites.values():\n                peer = site.peers.get(\"%s:%s\" % (sender[\"ip\"], sender[\"port\"]))\n                if peer:\n                    peer.found(\"local\")\n\n    def actionSiteListRequest(self, sender, params):\n        back = []\n        sites = list(self.server.sites.values())\n\n        # Split adresses to group of 100 to avoid UDP size limit\n        site_groups = [sites[i:i + 100] for i in range(0, len(sites), 100)]\n        for site_group in site_groups:\n            res = {}\n            res[\"sites_changed\"] = self.server.site_manager.sites_changed\n            res[\"sites\"] = [site.address_hash for site in site_group]\n            back.append({\"cmd\": \"siteListResponse\", \"params\": res})\n        return back\n\n    def actionSiteListResponse(self, sender, params):\n        s = time.time()\n        peer_sites = set(params[\"sites\"])\n        num_found = 0\n        added_sites = []\n        for site in self.server.sites.values():\n            if site.address_hash in peer_sites:\n                added = site.addPeer(sender[\"ip\"], sender[\"port\"], source=\"local\")\n                num_found += 1\n                if added:\n                    site.worker_manager.onPeers()\n                    site.updateWebsocket(peers_added=1)\n                    added_sites.append(site)\n\n        # Save sites changed value to avoid unnecessary site list download\n        if sender[\"peer_id\"] not in self.known_peers:\n            self.known_peers[sender[\"peer_id\"]] = {\"added\": time.time()}\n\n        self.known_peers[sender[\"peer_id\"]][\"sites_changed\"] = params[\"sites_changed\"]\n        self.known_peers[sender[\"peer_id\"]][\"updated\"] = time.time()\n        self.known_peers[sender[\"peer_id\"]][\"found\"] = time.time()\n\n        self.log.debug(\n            \"Tracker result: Discover from %s response parsed in %.3fs, found: %s added: %s of %s\" %\n            (sender[\"ip\"], time.time() - s, num_found, added_sites, len(peer_sites))\n        )\n\n\n@PluginManager.registerTo(\"FileServer\")\nclass FileServerPlugin(object):\n    def __init__(self, *args, **kwargs):\n        super(FileServerPlugin, self).__init__(*args, **kwargs)\n        if config.broadcast_port and config.tor != \"always\" and not config.disable_udp:\n            self.local_announcer = LocalAnnouncer(self, config.broadcast_port)\n        else:\n            self.local_announcer = None\n\n    def start(self, *args, **kwargs):\n        if self.local_announcer:\n            gevent.spawn(self.local_announcer.start)\n        return super(FileServerPlugin, self).start(*args, **kwargs)\n\n    def stop(self):\n        if self.local_announcer:\n            self.local_announcer.stop()\n        res = super(FileServerPlugin, self).stop()\n        return res\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"AnnounceLocal plugin\")\n        group.add_argument('--broadcast_port', help='UDP broadcasting port for local peer discovery', default=1544, type=int, metavar='port')\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/AnnounceLocal/BroadcastServer.py",
    "content": "import socket\nimport logging\nimport time\nfrom contextlib import closing\n\nfrom Debug import Debug\nfrom util import UpnpPunch\nfrom util import Msgpack\n\n\nclass BroadcastServer(object):\n    def __init__(self, service_name, listen_port=1544, listen_ip=''):\n        self.log = logging.getLogger(\"BroadcastServer\")\n        self.listen_port = listen_port\n        self.listen_ip = listen_ip\n\n        self.running = False\n        self.sock = None\n        self.sender_info = {\"service\": service_name}\n\n    def createBroadcastSocket(self):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        if hasattr(socket, 'SO_REUSEPORT'):\n            try:\n                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n            except Exception as err:\n                self.log.warning(\"Error setting SO_REUSEPORT: %s\" % err)\n\n        binded = False\n        for retry in range(3):\n            try:\n                sock.bind((self.listen_ip, self.listen_port))\n                binded = True\n                break\n            except Exception as err:\n                self.log.error(\n                    \"Socket bind to %s:%s error: %s, retry #%s\" %\n                    (self.listen_ip, self.listen_port, Debug.formatException(err), retry)\n                )\n                time.sleep(retry)\n\n        if binded:\n            return sock\n        else:\n            return False\n\n    def start(self):  # Listens for discover requests\n        self.sock = self.createBroadcastSocket()\n        if not self.sock:\n            self.log.error(\"Unable to listen on port %s\" % self.listen_port)\n            return\n\n        self.log.debug(\"Started on port %s\" % self.listen_port)\n\n        self.running = True\n\n        while self.running:\n            try:\n                data, addr = self.sock.recvfrom(8192)\n            except Exception as err:\n                if self.running:\n                    self.log.error(\"Listener receive error: %s\" % err)\n                continue\n\n            if not self.running:\n                break\n\n            try:\n                message = Msgpack.unpack(data)\n                response_addr, message = self.handleMessage(addr, message)\n                if message:\n                    self.send(response_addr, message)\n            except Exception as err:\n                self.log.error(\"Handlemessage error: %s\" % Debug.formatException(err))\n        self.log.debug(\"Stopped listening on port %s\" % self.listen_port)\n\n    def stop(self):\n        self.log.debug(\"Stopping, socket: %s\" % self.sock)\n        self.running = False\n        if self.sock:\n            self.sock.close()\n\n    def send(self, addr, message):\n        if type(message) is not list:\n            message = [message]\n\n        for message_part in message:\n            message_part[\"sender\"] = self.sender_info\n\n            self.log.debug(\"Send to %s: %s\" % (addr, message_part[\"cmd\"]))\n            with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sock:\n                sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n                sock.sendto(Msgpack.pack(message_part), addr)\n\n    def getMyIps(self):\n        return UpnpPunch._get_local_ips()\n\n    def broadcast(self, message, port=None):\n        if not port:\n            port = self.listen_port\n\n        my_ips = self.getMyIps()\n        addr = (\"255.255.255.255\", port)\n\n        message[\"sender\"] = self.sender_info\n        self.log.debug(\"Broadcast using ips %s on port %s: %s\" % (my_ips, port, message[\"cmd\"]))\n\n        for my_ip in my_ips:\n            try:\n                with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as sock:\n                    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n                    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\n                    sock.bind((my_ip, 0))\n                    sock.sendto(Msgpack.pack(message), addr)\n            except Exception as err:\n                self.log.warning(\"Error sending broadcast using ip %s: %s\" % (my_ip, err))\n\n    def handleMessage(self, addr, message):\n        self.log.debug(\"Got from %s: %s\" % (addr, message[\"cmd\"]))\n        cmd = message[\"cmd\"]\n        params = message.get(\"params\", {})\n        sender = message[\"sender\"]\n        sender[\"ip\"] = addr[0]\n\n        func_name = \"action\" + cmd[0].upper() + cmd[1:]\n        func = getattr(self, func_name, None)\n\n        if sender[\"service\"] != \"zeronet\" or sender[\"peer_id\"] == self.sender_info[\"peer_id\"]:\n            # Skip messages not for us or sent by us\n            message = None\n        elif func:\n            message = func(sender, params)\n        else:\n            self.log.debug(\"Unknown cmd: %s\" % cmd)\n            message = None\n\n        return (sender[\"ip\"], sender[\"broadcast_port\"]), message\n"
  },
  {
    "path": "plugins/AnnounceLocal/Test/TestAnnounce.py",
    "content": "import time\nimport copy\n\nimport gevent\nimport pytest\nimport mock\n\nfrom AnnounceLocal import AnnounceLocalPlugin\nfrom File import FileServer\nfrom Test import Spy\n\n@pytest.fixture\ndef announcer(file_server, site):\n    file_server.sites[site.address] = site\n    announcer = AnnounceLocalPlugin.LocalAnnouncer(file_server, listen_port=1100)\n    file_server.local_announcer = announcer\n    announcer.listen_port = 1100\n    announcer.sender_info[\"broadcast_port\"] = 1100\n    announcer.getMyIps = mock.MagicMock(return_value=[\"127.0.0.1\"])\n    announcer.discover = mock.MagicMock(return_value=False)  # Don't send discover requests automatically\n    gevent.spawn(announcer.start)\n    time.sleep(0.5)\n\n    assert file_server.local_announcer.running\n    return file_server.local_announcer\n\n@pytest.fixture\ndef announcer_remote(request, site_temp):\n    file_server_remote = FileServer(\"127.0.0.1\", 1545)\n    file_server_remote.sites[site_temp.address] = site_temp\n    announcer = AnnounceLocalPlugin.LocalAnnouncer(file_server_remote, listen_port=1101)\n    file_server_remote.local_announcer = announcer\n    announcer.listen_port = 1101\n    announcer.sender_info[\"broadcast_port\"] = 1101\n    announcer.getMyIps = mock.MagicMock(return_value=[\"127.0.0.1\"])\n    announcer.discover = mock.MagicMock(return_value=False)  # Don't send discover requests automatically\n    gevent.spawn(announcer.start)\n    time.sleep(0.5)\n\n    assert file_server_remote.local_announcer.running\n\n    def cleanup():\n        file_server_remote.stop()\n    request.addfinalizer(cleanup)\n\n\n    return file_server_remote.local_announcer\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestAnnounce:\n    def testSenderInfo(self, announcer):\n        sender_info = announcer.sender_info\n        assert sender_info[\"port\"] > 0\n        assert len(sender_info[\"peer_id\"]) == 20\n        assert sender_info[\"rev\"] > 0\n\n    def testIgnoreSelfMessages(self, announcer):\n        # No response to messages that has same peer_id as server\n        assert not announcer.handleMessage((\"0.0.0.0\", 123), {\"cmd\": \"discoverRequest\", \"sender\": announcer.sender_info, \"params\": {}})[1]\n\n        # Response to messages with different peer id\n        sender_info = copy.copy(announcer.sender_info)\n        sender_info[\"peer_id\"] += \"-\"\n        addr, res = announcer.handleMessage((\"0.0.0.0\", 123), {\"cmd\": \"discoverRequest\", \"sender\": sender_info, \"params\": {}})\n        assert res[\"params\"][\"sites_changed\"] > 0\n\n    def testDiscoverRequest(self, announcer, announcer_remote):\n        assert len(announcer_remote.known_peers) == 0\n        with Spy.Spy(announcer_remote, \"handleMessage\") as responses:\n            announcer_remote.broadcast({\"cmd\": \"discoverRequest\", \"params\": {}}, port=announcer.listen_port)\n            time.sleep(0.1)\n\n        response_cmds = [response[1][\"cmd\"] for response in responses]\n        assert response_cmds == [\"discoverResponse\", \"siteListResponse\"]\n        assert len(responses[-1][1][\"params\"][\"sites\"]) == 1\n\n        # It should only request siteList if sites_changed value is different from last response\n        with Spy.Spy(announcer_remote, \"handleMessage\") as responses:\n            announcer_remote.broadcast({\"cmd\": \"discoverRequest\", \"params\": {}}, port=announcer.listen_port)\n            time.sleep(0.1)\n\n        response_cmds = [response[1][\"cmd\"] for response in responses]\n        assert response_cmds == [\"discoverResponse\"]\n\n    def testPeerDiscover(self, announcer, announcer_remote, site):\n        assert announcer.server.peer_id != announcer_remote.server.peer_id\n        assert len(list(announcer.server.sites.values())[0].peers) == 0\n        announcer.broadcast({\"cmd\": \"discoverRequest\"}, port=announcer_remote.listen_port)\n        time.sleep(0.1)\n        assert len(list(announcer.server.sites.values())[0].peers) == 1\n\n    def testRecentPeerList(self, announcer, announcer_remote, site):\n        assert len(site.peers_recent) == 0\n        assert len(site.peers) == 0\n        with Spy.Spy(announcer, \"handleMessage\") as responses:\n            announcer.broadcast({\"cmd\": \"discoverRequest\", \"params\": {}}, port=announcer_remote.listen_port)\n            time.sleep(0.1)\n        assert [response[1][\"cmd\"] for response in responses] == [\"discoverResponse\", \"siteListResponse\"]\n        assert len(site.peers_recent) == 1\n        assert len(site.peers) == 1\n\n        # It should update peer without siteListResponse\n        last_time_found = list(site.peers.values())[0].time_found\n        site.peers_recent.clear()\n        with Spy.Spy(announcer, \"handleMessage\") as responses:\n            announcer.broadcast({\"cmd\": \"discoverRequest\", \"params\": {}}, port=announcer_remote.listen_port)\n            time.sleep(0.1)\n        assert [response[1][\"cmd\"] for response in responses] == [\"discoverResponse\"]\n        assert len(site.peers_recent) == 1\n        assert list(site.peers.values())[0].time_found > last_time_found\n\n\n"
  },
  {
    "path": "plugins/AnnounceLocal/Test/conftest.py",
    "content": "from src.Test.conftest import *\n\nfrom Config import config\nconfig.broadcast_port = 0\n"
  },
  {
    "path": "plugins/AnnounceLocal/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/AnnounceLocal/__init__.py",
    "content": "from . import AnnounceLocalPlugin"
  },
  {
    "path": "plugins/AnnounceLocal/plugin_info.json",
    "content": "{\n\t\"name\": \"AnnounceLocal\",\n\t\"description\": \"Discover LAN clients using UDP broadcasting.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/AnnounceShare/AnnounceSharePlugin.py",
    "content": "import time\nimport os\nimport logging\nimport json\nimport atexit\n\nimport gevent\n\nfrom Config import config\nfrom Plugin import PluginManager\nfrom util import helper\n\n\nclass TrackerStorage(object):\n    def __init__(self):\n        self.log = logging.getLogger(\"TrackerStorage\")\n        self.file_path = \"%s/trackers.json\" % config.data_dir\n        self.load()\n        self.time_discover = 0.0\n        atexit.register(self.save)\n\n    def getDefaultFile(self):\n        return {\"shared\": {}}\n\n    def onTrackerFound(self, tracker_address, type=\"shared\", my=False):\n        if not tracker_address.startswith(\"zero://\"):\n            return False\n\n        trackers = self.getTrackers()\n        added = False\n        if tracker_address not in trackers:\n            trackers[tracker_address] = {\n                \"time_added\": time.time(),\n                \"time_success\": 0,\n                \"latency\": 99.0,\n                \"num_error\": 0,\n                \"my\": False\n            }\n            self.log.debug(\"New tracker found: %s\" % tracker_address)\n            added = True\n\n        trackers[tracker_address][\"time_found\"] = time.time()\n        trackers[tracker_address][\"my\"] = my\n        return added\n\n    def onTrackerSuccess(self, tracker_address, latency):\n        trackers = self.getTrackers()\n        if tracker_address not in trackers:\n            return False\n\n        trackers[tracker_address][\"latency\"] = latency\n        trackers[tracker_address][\"time_success\"] = time.time()\n        trackers[tracker_address][\"num_error\"] = 0\n\n    def onTrackerError(self, tracker_address):\n        trackers = self.getTrackers()\n        if tracker_address not in trackers:\n            return False\n\n        trackers[tracker_address][\"time_error\"] = time.time()\n        trackers[tracker_address][\"num_error\"] += 1\n\n        if len(self.getWorkingTrackers()) >= config.working_shared_trackers_limit:\n            error_limit = 5\n        else:\n            error_limit = 30\n        error_limit\n\n        if trackers[tracker_address][\"num_error\"] > error_limit and trackers[tracker_address][\"time_success\"] < time.time() - 60 * 60:\n            self.log.debug(\"Tracker %s looks down, removing.\" % tracker_address)\n            del trackers[tracker_address]\n\n    def getTrackers(self, type=\"shared\"):\n        return self.file_content.setdefault(type, {})\n\n    def getWorkingTrackers(self, type=\"shared\"):\n        trackers = {\n            key: tracker for key, tracker in self.getTrackers(type).items()\n            if tracker[\"time_success\"] > time.time() - 60 * 60\n        }\n        return trackers\n\n    def getFileContent(self):\n        if not os.path.isfile(self.file_path):\n            open(self.file_path, \"w\").write(\"{}\")\n            return self.getDefaultFile()\n        try:\n            return json.load(open(self.file_path))\n        except Exception as err:\n            self.log.error(\"Error loading trackers list: %s\" % err)\n            return self.getDefaultFile()\n\n    def load(self):\n        self.file_content = self.getFileContent()\n\n        trackers = self.getTrackers()\n        self.log.debug(\"Loaded %s shared trackers\" % len(trackers))\n        for address, tracker in list(trackers.items()):\n            tracker[\"num_error\"] = 0\n            if not address.startswith(\"zero://\"):\n                del trackers[address]\n\n    def save(self):\n        s = time.time()\n        helper.atomicWrite(self.file_path, json.dumps(self.file_content, indent=2, sort_keys=True).encode(\"utf8\"))\n        self.log.debug(\"Saved in %.3fs\" % (time.time() - s))\n\n    def discoverTrackers(self, peers):\n        if len(self.getWorkingTrackers()) > config.working_shared_trackers_limit:\n            return False\n        s = time.time()\n        num_success = 0\n        for peer in peers:\n            if peer.connection and peer.connection.handshake.get(\"rev\", 0) < 3560:\n                continue  # Not supported\n\n            res = peer.request(\"getTrackers\")\n            if not res or \"error\" in res:\n                continue\n\n            num_success += 1\n            for tracker_address in res[\"trackers\"]:\n                if type(tracker_address) is bytes:  # Backward compatibilitys\n                    tracker_address = tracker_address.decode(\"utf8\")\n                added = self.onTrackerFound(tracker_address)\n                if added:  # Only add one tracker from one source\n                    break\n\n        if not num_success and len(peers) < 20:\n            self.time_discover = 0.0\n\n        if num_success:\n            self.save()\n\n        self.log.debug(\"Trackers discovered from %s/%s peers in %.3fs\" % (num_success, len(peers), time.time() - s))\n\n\nif \"tracker_storage\" not in locals():\n    tracker_storage = TrackerStorage()\n\n\n@PluginManager.registerTo(\"SiteAnnouncer\")\nclass SiteAnnouncerPlugin(object):\n    def getTrackers(self):\n        if tracker_storage.time_discover < time.time() - 5 * 60:\n            tracker_storage.time_discover = time.time()\n            gevent.spawn(tracker_storage.discoverTrackers, self.site.getConnectedPeers())\n        trackers = super(SiteAnnouncerPlugin, self).getTrackers()\n        shared_trackers = list(tracker_storage.getTrackers(\"shared\").keys())\n        if shared_trackers:\n            return trackers + shared_trackers\n        else:\n            return trackers\n\n    def announceTracker(self, tracker, *args, **kwargs):\n        res = super(SiteAnnouncerPlugin, self).announceTracker(tracker, *args, **kwargs)\n        if res:\n            latency = res\n            tracker_storage.onTrackerSuccess(tracker, latency)\n        elif res is False:\n            tracker_storage.onTrackerError(tracker)\n\n        return res\n\n\n@PluginManager.registerTo(\"FileRequest\")\nclass FileRequestPlugin(object):\n    def actionGetTrackers(self, params):\n        shared_trackers = list(tracker_storage.getWorkingTrackers(\"shared\").keys())\n        self.response({\"trackers\": shared_trackers})\n\n\n@PluginManager.registerTo(\"FileServer\")\nclass FileServerPlugin(object):\n    def portCheck(self, *args, **kwargs):\n        res = super(FileServerPlugin, self).portCheck(*args, **kwargs)\n        if res and not config.tor == \"always\" and \"Bootstrapper\" in PluginManager.plugin_manager.plugin_names:\n            for ip in self.ip_external_list:\n                my_tracker_address = \"zero://%s:%s\" % (ip, config.fileserver_port)\n                tracker_storage.onTrackerFound(my_tracker_address, my=True)\n        return res\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"AnnounceShare plugin\")\n        group.add_argument('--working_shared_trackers_limit', help='Stop discovering new shared trackers after this number of shared trackers reached', default=5, type=int, metavar='limit')\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/AnnounceShare/Test/TestAnnounceShare.py",
    "content": "import pytest\n\nfrom AnnounceShare import AnnounceSharePlugin\nfrom Peer import Peer\nfrom Config import config\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestAnnounceShare:\n    def testAnnounceList(self, file_server):\n        open(\"%s/trackers.json\" % config.data_dir, \"w\").write(\"{}\")\n        tracker_storage = AnnounceSharePlugin.tracker_storage\n        tracker_storage.load()\n        peer = Peer(file_server.ip, 1544, connection_server=file_server)\n        assert peer.request(\"getTrackers\")[\"trackers\"] == []\n\n        tracker_storage.onTrackerFound(\"zero://%s:15441\" % file_server.ip)\n        assert peer.request(\"getTrackers\")[\"trackers\"] == []\n\n        # It needs to have at least one successfull announce to be shared to other peers\n        tracker_storage.onTrackerSuccess(\"zero://%s:15441\" % file_server.ip, 1.0)\n        assert peer.request(\"getTrackers\")[\"trackers\"] == [\"zero://%s:15441\" % file_server.ip]\n\n"
  },
  {
    "path": "plugins/AnnounceShare/Test/conftest.py",
    "content": "from src.Test.conftest import *\n\nfrom Config import config\n"
  },
  {
    "path": "plugins/AnnounceShare/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/AnnounceShare/__init__.py",
    "content": "from . import AnnounceSharePlugin\n"
  },
  {
    "path": "plugins/AnnounceShare/plugin_info.json",
    "content": "{\n\t\"name\": \"AnnounceShare\",\n\t\"description\": \"Share possible trackers between clients.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/AnnounceZero/AnnounceZeroPlugin.py",
    "content": "import time\nimport itertools\n\nfrom Plugin import PluginManager\nfrom util import helper\nfrom Crypt import CryptRsa\n\nallow_reload = False  # No source reload supported in this plugin\ntime_full_announced = {}  # Tracker address: Last announced all site to tracker\nconnection_pool = {}  # Tracker address: Peer object\n\n\n# We can only import plugin host clases after the plugins are loaded\n@PluginManager.afterLoad\ndef importHostClasses():\n    global Peer, AnnounceError\n    from Peer import Peer\n    from Site.SiteAnnouncer import AnnounceError\n\n\n# Process result got back from tracker\ndef processPeerRes(tracker_address, site, peers):\n    added = 0\n\n    # Onion\n    found_onion = 0\n    for packed_address in peers[\"onion\"]:\n        found_onion += 1\n        peer_onion, peer_port = helper.unpackOnionAddress(packed_address)\n        if site.addPeer(peer_onion, peer_port, source=\"tracker\"):\n            added += 1\n\n    # Ip4\n    found_ipv4 = 0\n    peers_normal = itertools.chain(peers.get(\"ip4\", []), peers.get(\"ipv4\", []), peers.get(\"ipv6\", []))\n    for packed_address in peers_normal:\n        found_ipv4 += 1\n        peer_ip, peer_port = helper.unpackAddress(packed_address)\n        if site.addPeer(peer_ip, peer_port, source=\"tracker\"):\n            added += 1\n\n    if added:\n        site.worker_manager.onPeers()\n        site.updateWebsocket(peers_added=added)\n    return added\n\n\n@PluginManager.registerTo(\"SiteAnnouncer\")\nclass SiteAnnouncerPlugin(object):\n    def getTrackerHandler(self, protocol):\n        if protocol == \"zero\":\n            return self.announceTrackerZero\n        else:\n            return super(SiteAnnouncerPlugin, self).getTrackerHandler(protocol)\n\n    def announceTrackerZero(self, tracker_address, mode=\"start\", num_want=10):\n        global time_full_announced\n        s = time.time()\n\n        need_types = [\"ip4\"]   # ip4 for backward compatibility reasons\n        need_types += self.site.connection_server.supported_ip_types\n        if self.site.connection_server.tor_manager.enabled:\n            need_types.append(\"onion\")\n\n        if mode == \"start\" or mode == \"more\":  # Single: Announce only this site\n            sites = [self.site]\n            full_announce = False\n        else:  # Multi: Announce all currently serving site\n            full_announce = True\n            if time.time() - time_full_announced.get(tracker_address, 0) < 60 * 15:  # No reannounce all sites within short time\n                return None\n            time_full_announced[tracker_address] = time.time()\n            from Site import SiteManager\n            sites = [site for site in SiteManager.site_manager.sites.values() if site.isServing()]\n\n        # Create request\n        add_types = self.getOpenedServiceTypes()\n        request = {\n            \"hashes\": [], \"onions\": [], \"port\": self.fileserver_port, \"need_types\": need_types, \"need_num\": 20, \"add\": add_types\n        }\n        for site in sites:\n            if \"onion\" in add_types:\n                onion = self.site.connection_server.tor_manager.getOnion(site.address)\n                request[\"onions\"].append(onion)\n            request[\"hashes\"].append(site.address_hash)\n\n        # Tracker can remove sites that we don't announce\n        if full_announce:\n            request[\"delete\"] = True\n\n        # Sent request to tracker\n        tracker_peer = connection_pool.get(tracker_address)  # Re-use tracker connection if possible\n        if not tracker_peer:\n            tracker_ip, tracker_port = tracker_address.rsplit(\":\", 1)\n            tracker_peer = Peer(str(tracker_ip), int(tracker_port), connection_server=self.site.connection_server)\n            tracker_peer.is_tracker_connection = True\n            connection_pool[tracker_address] = tracker_peer\n\n        res = tracker_peer.request(\"announce\", request)\n\n        if not res or \"peers\" not in res:\n            if full_announce:\n                time_full_announced[tracker_address] = 0\n            raise AnnounceError(\"Invalid response: %s\" % res)\n\n        # Add peers from response to site\n        site_index = 0\n        peers_added = 0\n        for site_res in res[\"peers\"]:\n            site = sites[site_index]\n            peers_added += processPeerRes(tracker_address, site, site_res)\n            site_index += 1\n\n        # Check if we need to sign prove the onion addresses\n        if \"onion_sign_this\" in res:\n            self.site.log.debug(\"Signing %s for %s to add %s onions\" % (res[\"onion_sign_this\"], tracker_address, len(sites)))\n            request[\"onion_signs\"] = {}\n            request[\"onion_sign_this\"] = res[\"onion_sign_this\"]\n            request[\"need_num\"] = 0\n            for site in sites:\n                onion = self.site.connection_server.tor_manager.getOnion(site.address)\n                publickey = self.site.connection_server.tor_manager.getPublickey(onion)\n                if publickey not in request[\"onion_signs\"]:\n                    sign = CryptRsa.sign(res[\"onion_sign_this\"].encode(\"utf8\"), self.site.connection_server.tor_manager.getPrivatekey(onion))\n                    request[\"onion_signs\"][publickey] = sign\n            res = tracker_peer.request(\"announce\", request)\n            if not res or \"onion_sign_this\" in res:\n                if full_announce:\n                    time_full_announced[tracker_address] = 0\n                raise AnnounceError(\"Announce onion address to failed: %s\" % res)\n\n        if full_announce:\n            tracker_peer.remove()  # Close connection, we don't need it in next 5 minute\n\n        self.site.log.debug(\n            \"Tracker announce result: zero://%s (sites: %s, new peers: %s, add: %s, mode: %s) in %.3fs\" %\n            (tracker_address, site_index, peers_added, add_types, mode, time.time() - s)\n        )\n\n        return True\n"
  },
  {
    "path": "plugins/AnnounceZero/__init__.py",
    "content": "from . import AnnounceZeroPlugin"
  },
  {
    "path": "plugins/AnnounceZero/plugin_info.json",
    "content": "{\n\t\"name\": \"AnnounceZero\",\n\t\"description\": \"Announce using ZeroNet protocol.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/Benchmark/BenchmarkDb.py",
    "content": "import os\nimport json\nimport contextlib\nimport time\n\nfrom Plugin import PluginManager\nfrom Config import config\n\n\n@PluginManager.registerTo(\"Actions\")\nclass ActionsPlugin:\n    def getBenchmarkTests(self, online=False):\n        tests = super().getBenchmarkTests(online)\n        tests.extend([\n            {\"func\": self.testDbConnect, \"num\": 10, \"time_standard\": 0.27},\n            {\"func\": self.testDbInsert, \"num\": 10, \"time_standard\": 0.91},\n            {\"func\": self.testDbInsertMultiuser, \"num\": 1, \"time_standard\": 0.57},\n            {\"func\": self.testDbQueryIndexed, \"num\": 1000, \"time_standard\": 0.84},\n            {\"func\": self.testDbQueryNotIndexed, \"num\": 1000, \"time_standard\": 1.30}\n        ])\n        return tests\n\n\n    @contextlib.contextmanager\n    def getTestDb(self):\n        from Db import Db\n        path = \"%s/benchmark.db\" % config.data_dir\n        if os.path.isfile(path):\n            os.unlink(path)\n        schema = {\n            \"db_name\": \"TestDb\",\n            \"db_file\": path,\n            \"maps\": {\n                \".*\": {\n                    \"to_table\": {\n                        \"test\": \"test\"\n                    }\n                }\n            },\n            \"tables\": {\n                \"test\": {\n                    \"cols\": [\n                        [\"test_id\", \"INTEGER\"],\n                        [\"title\", \"TEXT\"],\n                        [\"json_id\", \"INTEGER REFERENCES json (json_id)\"]\n                    ],\n                    \"indexes\": [\"CREATE UNIQUE INDEX test_key ON test(test_id, json_id)\"],\n                    \"schema_changed\": 1426195822\n                }\n            }\n        }\n\n        db = Db.Db(schema, path)\n\n        yield db\n\n        db.close()\n        if os.path.isfile(path):\n            os.unlink(path)\n\n    def testDbConnect(self, num_run=1):\n        import sqlite3\n        for i in range(num_run):\n            with self.getTestDb() as db:\n                db.checkTables()\n            yield \".\"\n        yield \"(SQLite version: %s, API: %s)\" % (sqlite3.sqlite_version, sqlite3.version)\n\n    def testDbInsert(self, num_run=1):\n        yield \"x 1000 lines \"\n        for u in range(num_run):\n            with self.getTestDb() as db:\n                db.checkTables()\n                data = {\"test\": []}\n                for i in range(1000):  # 1000 line of data\n                    data[\"test\"].append({\"test_id\": i, \"title\": \"Testdata for %s message %s\" % (u, i)})\n                json.dump(data, open(\"%s/test_%s.json\" % (config.data_dir, u), \"w\"))\n                db.updateJson(\"%s/test_%s.json\" % (config.data_dir, u))\n                os.unlink(\"%s/test_%s.json\" % (config.data_dir, u))\n                assert db.execute(\"SELECT COUNT(*) FROM test\").fetchone()[0] == 1000\n            yield \".\"\n\n    def fillTestDb(self, db):\n        db.checkTables()\n        cur = db.getCursor()\n        cur.logging = False\n        for u in range(100, 200):  # 100 user\n            data = {\"test\": []}\n            for i in range(100):  # 1000 line of data\n                data[\"test\"].append({\"test_id\": i, \"title\": \"Testdata for %s message %s\" % (u, i)})\n            json.dump(data, open(\"%s/test_%s.json\" % (config.data_dir, u), \"w\"))\n            db.updateJson(\"%s/test_%s.json\" % (config.data_dir, u), cur=cur)\n            os.unlink(\"%s/test_%s.json\" % (config.data_dir, u))\n            if u % 10 == 0:\n                yield \".\"\n\n    def testDbInsertMultiuser(self, num_run=1):\n        yield \"x 100 users x 100 lines \"\n        for u in range(num_run):\n            with self.getTestDb() as db:\n                for progress in self.fillTestDb(db):\n                    yield progress\n                num_rows = db.execute(\"SELECT COUNT(*) FROM test\").fetchone()[0]\n                assert num_rows == 10000, \"%s != 10000\" % num_rows\n\n    def testDbQueryIndexed(self, num_run=1):\n        s = time.time()\n        with self.getTestDb() as db:\n            for progress in self.fillTestDb(db):\n                pass\n            yield \" (Db warmup done in %.3fs) \" % (time.time() - s)\n            found_total = 0\n            for i in range(num_run):  # 1000x by test_id\n                found = 0\n                res = db.execute(\"SELECT * FROM test WHERE test_id = %s\" % (i % 100))\n                for row in res:\n                    found_total += 1\n                    found += 1\n                del(res)\n                yield \".\"\n                assert found == 100, \"%s != 100 (i: %s)\" % (found, i)\n            yield \"Found: %s\" % found_total\n\n    def testDbQueryNotIndexed(self, num_run=1):\n        s = time.time()\n        with self.getTestDb() as db:\n            for progress in self.fillTestDb(db):\n                pass\n            yield \" (Db warmup done in %.3fs) \" % (time.time() - s)\n            found_total = 0\n            for i in range(num_run):  # 1000x by test_id\n                found = 0\n                res = db.execute(\"SELECT * FROM test WHERE json_id = %s\" % i)\n                for row in res:\n                    found_total += 1\n                    found += 1\n                yield \".\"\n                del(res)\n                if i == 0 or i > 100:\n                    assert found == 0, \"%s != 0 (i: %s)\" % (found, i)\n                else:\n                    assert found == 100, \"%s != 100 (i: %s)\" % (found, i)\n            yield \"Found: %s\" % found_total\n"
  },
  {
    "path": "plugins/Benchmark/BenchmarkPack.py",
    "content": "import os\nimport io\nfrom collections import OrderedDict\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom util import Msgpack\n\n\n@PluginManager.registerTo(\"Actions\")\nclass ActionsPlugin:\n    def createZipFile(self, path):\n        import zipfile\n        test_data = b\"Test\" * 1024\n        file_name = b\"\\xc3\\x81rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91%s.txt\".decode(\"utf8\")\n        with zipfile.ZipFile(path, 'w') as archive:\n            for y in range(100):\n                zip_info = zipfile.ZipInfo(file_name % y, (1980, 1, 1, 0, 0, 0))\n                zip_info.compress_type = zipfile.ZIP_DEFLATED\n                zip_info.create_system = 3\n                zip_info.flag_bits = 0\n                zip_info.external_attr = 25165824\n                archive.writestr(zip_info, test_data)\n\n    def testPackZip(self, num_run=1):\n        \"\"\"\n        Test zip file creating\n        \"\"\"\n        yield \"x 100 x 5KB \"\n        from Crypt import CryptHash\n        zip_path = '%s/test.zip' % config.data_dir\n        for i in range(num_run):\n            self.createZipFile(zip_path)\n            yield \".\"\n\n        archive_size = os.path.getsize(zip_path) / 1024\n        yield \"(Generated file size: %.2fkB)\" % archive_size\n\n        hash = CryptHash.sha512sum(open(zip_path, \"rb\"))\n        valid = \"cb32fb43783a1c06a2170a6bc5bb228a032b67ff7a1fd7a5efb9b467b400f553\"\n        assert hash == valid, \"Invalid hash: %s != %s<br>\" % (hash, valid)\n        os.unlink(zip_path)\n\n    def testUnpackZip(self, num_run=1):\n        \"\"\"\n        Test zip file reading\n        \"\"\"\n        yield \"x 100 x 5KB \"\n        import zipfile\n        zip_path = '%s/test.zip' % config.data_dir\n        test_data = b\"Test\" * 1024\n        file_name = b\"\\xc3\\x81rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91\".decode(\"utf8\")\n\n        self.createZipFile(zip_path)\n        for i in range(num_run):\n            with zipfile.ZipFile(zip_path) as archive:\n                for f in archive.filelist:\n                    assert f.filename.startswith(file_name), \"Invalid filename: %s != %s\" % (f.filename, file_name)\n                    data = archive.open(f.filename).read()\n                    assert archive.open(f.filename).read() == test_data, \"Invalid data: %s...\" % data[0:30]\n            yield \".\"\n\n        os.unlink(zip_path)\n\n    def createArchiveFile(self, path, archive_type=\"gz\"):\n        import tarfile\n        import gzip\n\n        # Monkey patch _init_write_gz to use fixed date in order to keep the hash independent from datetime\n        def nodate_write_gzip_header(self):\n            self._write_mtime = 0\n            original_write_gzip_header(self)\n\n        test_data_io = io.BytesIO(b\"Test\" * 1024)\n        file_name = b\"\\xc3\\x81rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91%s.txt\".decode(\"utf8\")\n\n        original_write_gzip_header = gzip.GzipFile._write_gzip_header\n        gzip.GzipFile._write_gzip_header = nodate_write_gzip_header\n        with tarfile.open(path, 'w:%s' % archive_type) as archive:\n            for y in range(100):\n                test_data_io.seek(0)\n                tar_info = tarfile.TarInfo(file_name % y)\n                tar_info.size = 4 * 1024\n                archive.addfile(tar_info, test_data_io)\n\n    def testPackArchive(self, num_run=1, archive_type=\"gz\"):\n        \"\"\"\n        Test creating tar archive files\n        \"\"\"\n        yield \"x 100 x 5KB \"\n        from Crypt import CryptHash\n\n        hash_valid_db = {\n            \"gz\": \"92caec5121a31709cbbc8c11b0939758e670b055bbbe84f9beb3e781dfde710f\",\n            \"bz2\": \"b613f41e6ee947c8b9b589d3e8fa66f3e28f63be23f4faf015e2f01b5c0b032d\",\n            \"xz\": \"ae43892581d770959c8d993daffab25fd74490b7cf9fafc7aaee746f69895bcb\",\n        }\n        archive_path = '%s/test.tar.%s' % (config.data_dir, archive_type)\n        for i in range(num_run):\n            self.createArchiveFile(archive_path, archive_type=archive_type)\n            yield \".\"\n\n        archive_size = os.path.getsize(archive_path) / 1024\n        yield \"(Generated file size: %.2fkB)\" % archive_size\n\n        hash = CryptHash.sha512sum(open(\"%s/test.tar.%s\" % (config.data_dir, archive_type), \"rb\"))\n        valid = hash_valid_db[archive_type]\n        assert hash == valid, \"Invalid hash: %s != %s<br>\" % (hash, valid)\n\n        if os.path.isfile(archive_path):\n            os.unlink(archive_path)\n\n    def testUnpackArchive(self, num_run=1, archive_type=\"gz\"):\n        \"\"\"\n        Test reading tar archive files\n        \"\"\"\n        yield \"x 100 x 5KB \"\n        import tarfile\n\n        test_data = b\"Test\" * 1024\n        file_name = b\"\\xc3\\x81rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91%s.txt\".decode(\"utf8\")\n        archive_path = '%s/test.tar.%s' % (config.data_dir, archive_type)\n        self.createArchiveFile(archive_path, archive_type=archive_type)\n        for i in range(num_run):\n            with tarfile.open(archive_path, 'r:%s' % archive_type) as archive:\n                for y in range(100):\n                    assert archive.extractfile(file_name % y).read() == test_data\n            yield \".\"\n        if os.path.isfile(archive_path):\n            os.unlink(archive_path)\n\n    def testPackMsgpack(self, num_run=1):\n        \"\"\"\n        Test msgpack encoding\n        \"\"\"\n        yield \"x 100 x 5KB \"\n        binary = b'fqv\\xf0\\x1a\"e\\x10,\\xbe\\x9cT\\x9e(\\xa5]u\\x072C\\x8c\\x15\\xa2\\xa8\\x93Sw)\\x19\\x02\\xdd\\t\\xfb\\xf67\\x88\\xd9\\xee\\x86\\xa1\\xe4\\xb6,\\xc6\\x14\\xbb\\xd7$z\\x1d\\xb2\\xda\\x85\\xf5\\xa0\\x97^\\x01*\\xaf\\xd3\\xb0!\\xb7\\x9d\\xea\\x89\\xbbh8\\xa1\"\\xa7]e(@\\xa2\\xa5g\\xb7[\\xae\\x8eE\\xc2\\x9fL\\xb6s\\x19\\x19\\r\\xc8\\x04S\\xd0N\\xe4]?/\\x01\\xea\\xf6\\xec\\xd1\\xb3\\xc2\\x91\\x86\\xd7\\xf4K\\xdf\\xc2lV\\xf4\\xe8\\x80\\xfc\\x8ep\\xbb\\x82\\xb3\\x86\\x98F\\x1c\\xecS\\xc8\\x15\\xcf\\xdc\\xf1\\xed\\xfc\\xd8\\x18r\\xf9\\x80\\x0f\\xfa\\x8cO\\x97(\\x0b]\\xf1\\xdd\\r\\xe7\\xbf\\xed\\x06\\xbd\\x1b?\\xc5\\xa0\\xd7a\\x82\\xf3\\xa8\\xe6@\\xf3\\ri\\xa1\\xb10\\xf6\\xd4W\\xbc\\x86\\x1a\\xbb\\xfd\\x94!bS\\xdb\\xaeM\\x92\\x00#\\x0b\\xf7\\xad\\xe9\\xc2\\x8e\\x86\\xbfi![%\\xd31]\\xc6\\xfc2\\xc9\\xda\\xc6v\\x82P\\xcc\\xa9\\xea\\xb9\\xff\\xf6\\xc8\\x17iD\\xcf\\xf3\\xeeI\\x04\\xe9\\xa1\\x19\\xbb\\x01\\x92\\xf5nn4K\\xf8\\xbb\\xc6\\x17e>\\xa7 \\xbbv'\n        data = OrderedDict(\n            sorted({\"int\": 1024 * 1024 * 1024, \"float\": 12345.67890, \"text\": \"hello\" * 1024, \"binary\": binary}.items())\n        )\n        data_packed_valid = b'\\x84\\xa6binary\\xc5\\x01\\x00fqv\\xf0\\x1a\"e\\x10,\\xbe\\x9cT\\x9e(\\xa5]u\\x072C\\x8c\\x15\\xa2\\xa8\\x93Sw)\\x19\\x02\\xdd\\t\\xfb\\xf67\\x88\\xd9\\xee\\x86\\xa1\\xe4\\xb6,\\xc6\\x14\\xbb\\xd7$z\\x1d\\xb2\\xda\\x85\\xf5\\xa0\\x97^\\x01*\\xaf\\xd3\\xb0!\\xb7\\x9d\\xea\\x89\\xbbh8\\xa1\"\\xa7]e(@\\xa2\\xa5g\\xb7[\\xae\\x8eE\\xc2\\x9fL\\xb6s\\x19\\x19\\r\\xc8\\x04S\\xd0N\\xe4]?/\\x01\\xea\\xf6\\xec\\xd1\\xb3\\xc2\\x91\\x86\\xd7\\xf4K\\xdf\\xc2lV\\xf4\\xe8\\x80\\xfc\\x8ep\\xbb\\x82\\xb3\\x86\\x98F\\x1c\\xecS\\xc8\\x15\\xcf\\xdc\\xf1\\xed\\xfc\\xd8\\x18r\\xf9\\x80\\x0f\\xfa\\x8cO\\x97(\\x0b]\\xf1\\xdd\\r\\xe7\\xbf\\xed\\x06\\xbd\\x1b?\\xc5\\xa0\\xd7a\\x82\\xf3\\xa8\\xe6@\\xf3\\ri\\xa1\\xb10\\xf6\\xd4W\\xbc\\x86\\x1a\\xbb\\xfd\\x94!bS\\xdb\\xaeM\\x92\\x00#\\x0b\\xf7\\xad\\xe9\\xc2\\x8e\\x86\\xbfi![%\\xd31]\\xc6\\xfc2\\xc9\\xda\\xc6v\\x82P\\xcc\\xa9\\xea\\xb9\\xff\\xf6\\xc8\\x17iD\\xcf\\xf3\\xeeI\\x04\\xe9\\xa1\\x19\\xbb\\x01\\x92\\xf5nn4K\\xf8\\xbb\\xc6\\x17e>\\xa7 \\xbbv\\xa5float\\xcb@\\xc8\\x1c\\xd6\\xe61\\xf8\\xa1\\xa3int\\xce@\\x00\\x00\\x00\\xa4text\\xda\\x14\\x00'\n        data_packed_valid += b'hello' * 1024\n        for y in range(num_run):\n            for i in range(100):\n                data_packed = Msgpack.pack(data)\n            yield \".\"\n        assert data_packed == data_packed_valid, \"%s<br>!=<br>%s\" % (repr(data_packed), repr(data_packed_valid))\n\n    def testUnpackMsgpack(self, num_run=1):\n        \"\"\"\n        Test msgpack decoding\n        \"\"\"\n        yield \"x 5KB \"\n        binary = b'fqv\\xf0\\x1a\"e\\x10,\\xbe\\x9cT\\x9e(\\xa5]u\\x072C\\x8c\\x15\\xa2\\xa8\\x93Sw)\\x19\\x02\\xdd\\t\\xfb\\xf67\\x88\\xd9\\xee\\x86\\xa1\\xe4\\xb6,\\xc6\\x14\\xbb\\xd7$z\\x1d\\xb2\\xda\\x85\\xf5\\xa0\\x97^\\x01*\\xaf\\xd3\\xb0!\\xb7\\x9d\\xea\\x89\\xbbh8\\xa1\"\\xa7]e(@\\xa2\\xa5g\\xb7[\\xae\\x8eE\\xc2\\x9fL\\xb6s\\x19\\x19\\r\\xc8\\x04S\\xd0N\\xe4]?/\\x01\\xea\\xf6\\xec\\xd1\\xb3\\xc2\\x91\\x86\\xd7\\xf4K\\xdf\\xc2lV\\xf4\\xe8\\x80\\xfc\\x8ep\\xbb\\x82\\xb3\\x86\\x98F\\x1c\\xecS\\xc8\\x15\\xcf\\xdc\\xf1\\xed\\xfc\\xd8\\x18r\\xf9\\x80\\x0f\\xfa\\x8cO\\x97(\\x0b]\\xf1\\xdd\\r\\xe7\\xbf\\xed\\x06\\xbd\\x1b?\\xc5\\xa0\\xd7a\\x82\\xf3\\xa8\\xe6@\\xf3\\ri\\xa1\\xb10\\xf6\\xd4W\\xbc\\x86\\x1a\\xbb\\xfd\\x94!bS\\xdb\\xaeM\\x92\\x00#\\x0b\\xf7\\xad\\xe9\\xc2\\x8e\\x86\\xbfi![%\\xd31]\\xc6\\xfc2\\xc9\\xda\\xc6v\\x82P\\xcc\\xa9\\xea\\xb9\\xff\\xf6\\xc8\\x17iD\\xcf\\xf3\\xeeI\\x04\\xe9\\xa1\\x19\\xbb\\x01\\x92\\xf5nn4K\\xf8\\xbb\\xc6\\x17e>\\xa7 \\xbbv'\n        data = OrderedDict(\n            sorted({\"int\": 1024 * 1024 * 1024, \"float\": 12345.67890, \"text\": \"hello\" * 1024, \"binary\": binary}.items())\n        )\n        data_packed = b'\\x84\\xa6binary\\xc5\\x01\\x00fqv\\xf0\\x1a\"e\\x10,\\xbe\\x9cT\\x9e(\\xa5]u\\x072C\\x8c\\x15\\xa2\\xa8\\x93Sw)\\x19\\x02\\xdd\\t\\xfb\\xf67\\x88\\xd9\\xee\\x86\\xa1\\xe4\\xb6,\\xc6\\x14\\xbb\\xd7$z\\x1d\\xb2\\xda\\x85\\xf5\\xa0\\x97^\\x01*\\xaf\\xd3\\xb0!\\xb7\\x9d\\xea\\x89\\xbbh8\\xa1\"\\xa7]e(@\\xa2\\xa5g\\xb7[\\xae\\x8eE\\xc2\\x9fL\\xb6s\\x19\\x19\\r\\xc8\\x04S\\xd0N\\xe4]?/\\x01\\xea\\xf6\\xec\\xd1\\xb3\\xc2\\x91\\x86\\xd7\\xf4K\\xdf\\xc2lV\\xf4\\xe8\\x80\\xfc\\x8ep\\xbb\\x82\\xb3\\x86\\x98F\\x1c\\xecS\\xc8\\x15\\xcf\\xdc\\xf1\\xed\\xfc\\xd8\\x18r\\xf9\\x80\\x0f\\xfa\\x8cO\\x97(\\x0b]\\xf1\\xdd\\r\\xe7\\xbf\\xed\\x06\\xbd\\x1b?\\xc5\\xa0\\xd7a\\x82\\xf3\\xa8\\xe6@\\xf3\\ri\\xa1\\xb10\\xf6\\xd4W\\xbc\\x86\\x1a\\xbb\\xfd\\x94!bS\\xdb\\xaeM\\x92\\x00#\\x0b\\xf7\\xad\\xe9\\xc2\\x8e\\x86\\xbfi![%\\xd31]\\xc6\\xfc2\\xc9\\xda\\xc6v\\x82P\\xcc\\xa9\\xea\\xb9\\xff\\xf6\\xc8\\x17iD\\xcf\\xf3\\xeeI\\x04\\xe9\\xa1\\x19\\xbb\\x01\\x92\\xf5nn4K\\xf8\\xbb\\xc6\\x17e>\\xa7 \\xbbv\\xa5float\\xcb@\\xc8\\x1c\\xd6\\xe61\\xf8\\xa1\\xa3int\\xce@\\x00\\x00\\x00\\xa4text\\xda\\x14\\x00'\n        data_packed += b'hello' * 1024\n        for y in range(num_run):\n            data_unpacked = Msgpack.unpack(data_packed, decode=False)\n            yield \".\"\n        assert data_unpacked == data, \"%s<br>!=<br>%s\" % (data_unpacked, data)\n\n    def testUnpackMsgpackStreaming(self, num_run=1, fallback=False):\n        \"\"\"\n        Test streaming msgpack decoding\n        \"\"\"\n        yield \"x 1000 x 5KB \"\n        binary = b'fqv\\xf0\\x1a\"e\\x10,\\xbe\\x9cT\\x9e(\\xa5]u\\x072C\\x8c\\x15\\xa2\\xa8\\x93Sw)\\x19\\x02\\xdd\\t\\xfb\\xf67\\x88\\xd9\\xee\\x86\\xa1\\xe4\\xb6,\\xc6\\x14\\xbb\\xd7$z\\x1d\\xb2\\xda\\x85\\xf5\\xa0\\x97^\\x01*\\xaf\\xd3\\xb0!\\xb7\\x9d\\xea\\x89\\xbbh8\\xa1\"\\xa7]e(@\\xa2\\xa5g\\xb7[\\xae\\x8eE\\xc2\\x9fL\\xb6s\\x19\\x19\\r\\xc8\\x04S\\xd0N\\xe4]?/\\x01\\xea\\xf6\\xec\\xd1\\xb3\\xc2\\x91\\x86\\xd7\\xf4K\\xdf\\xc2lV\\xf4\\xe8\\x80\\xfc\\x8ep\\xbb\\x82\\xb3\\x86\\x98F\\x1c\\xecS\\xc8\\x15\\xcf\\xdc\\xf1\\xed\\xfc\\xd8\\x18r\\xf9\\x80\\x0f\\xfa\\x8cO\\x97(\\x0b]\\xf1\\xdd\\r\\xe7\\xbf\\xed\\x06\\xbd\\x1b?\\xc5\\xa0\\xd7a\\x82\\xf3\\xa8\\xe6@\\xf3\\ri\\xa1\\xb10\\xf6\\xd4W\\xbc\\x86\\x1a\\xbb\\xfd\\x94!bS\\xdb\\xaeM\\x92\\x00#\\x0b\\xf7\\xad\\xe9\\xc2\\x8e\\x86\\xbfi![%\\xd31]\\xc6\\xfc2\\xc9\\xda\\xc6v\\x82P\\xcc\\xa9\\xea\\xb9\\xff\\xf6\\xc8\\x17iD\\xcf\\xf3\\xeeI\\x04\\xe9\\xa1\\x19\\xbb\\x01\\x92\\xf5nn4K\\xf8\\xbb\\xc6\\x17e>\\xa7 \\xbbv'\n        data = OrderedDict(\n            sorted({\"int\": 1024 * 1024 * 1024, \"float\": 12345.67890, \"text\": \"hello\" * 1024, \"binary\": binary}.items())\n        )\n        data_packed = b'\\x84\\xa6binary\\xc5\\x01\\x00fqv\\xf0\\x1a\"e\\x10,\\xbe\\x9cT\\x9e(\\xa5]u\\x072C\\x8c\\x15\\xa2\\xa8\\x93Sw)\\x19\\x02\\xdd\\t\\xfb\\xf67\\x88\\xd9\\xee\\x86\\xa1\\xe4\\xb6,\\xc6\\x14\\xbb\\xd7$z\\x1d\\xb2\\xda\\x85\\xf5\\xa0\\x97^\\x01*\\xaf\\xd3\\xb0!\\xb7\\x9d\\xea\\x89\\xbbh8\\xa1\"\\xa7]e(@\\xa2\\xa5g\\xb7[\\xae\\x8eE\\xc2\\x9fL\\xb6s\\x19\\x19\\r\\xc8\\x04S\\xd0N\\xe4]?/\\x01\\xea\\xf6\\xec\\xd1\\xb3\\xc2\\x91\\x86\\xd7\\xf4K\\xdf\\xc2lV\\xf4\\xe8\\x80\\xfc\\x8ep\\xbb\\x82\\xb3\\x86\\x98F\\x1c\\xecS\\xc8\\x15\\xcf\\xdc\\xf1\\xed\\xfc\\xd8\\x18r\\xf9\\x80\\x0f\\xfa\\x8cO\\x97(\\x0b]\\xf1\\xdd\\r\\xe7\\xbf\\xed\\x06\\xbd\\x1b?\\xc5\\xa0\\xd7a\\x82\\xf3\\xa8\\xe6@\\xf3\\ri\\xa1\\xb10\\xf6\\xd4W\\xbc\\x86\\x1a\\xbb\\xfd\\x94!bS\\xdb\\xaeM\\x92\\x00#\\x0b\\xf7\\xad\\xe9\\xc2\\x8e\\x86\\xbfi![%\\xd31]\\xc6\\xfc2\\xc9\\xda\\xc6v\\x82P\\xcc\\xa9\\xea\\xb9\\xff\\xf6\\xc8\\x17iD\\xcf\\xf3\\xeeI\\x04\\xe9\\xa1\\x19\\xbb\\x01\\x92\\xf5nn4K\\xf8\\xbb\\xc6\\x17e>\\xa7 \\xbbv\\xa5float\\xcb@\\xc8\\x1c\\xd6\\xe61\\xf8\\xa1\\xa3int\\xce@\\x00\\x00\\x00\\xa4text\\xda\\x14\\x00'\n        data_packed += b'hello' * 1024\n        for i in range(num_run):\n            unpacker = Msgpack.getUnpacker(decode=False, fallback=fallback)\n            for y in range(1000):\n                unpacker.feed(data_packed)\n                for data_unpacked in unpacker:\n                    pass\n            yield \".\"\n        assert data == data_unpacked, \"%s != %s\" % (data_unpacked, data)\n"
  },
  {
    "path": "plugins/Benchmark/BenchmarkPlugin.py",
    "content": "import os\nimport time\nimport io\nimport math\nimport hashlib\nimport re\nimport sys\n\nfrom Config import config\nfrom Crypt import CryptHash\nfrom Plugin import PluginManager\nfrom Debug import Debug\nfrom util import helper\n\nplugin_dir = os.path.dirname(__file__)\n\nbenchmark_key = None\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    @helper.encodeResponse\n    def actionBenchmark(self):\n        global benchmark_key\n        script_nonce = self.getScriptNonce()\n        if not benchmark_key:\n            benchmark_key = CryptHash.random(encoding=\"base64\")\n        self.sendHeader(script_nonce=script_nonce)\n\n        if \"Multiuser\" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:\n            yield \"This function is disabled on this proxy\"\n            return\n\n        data = self.render(\n            plugin_dir + \"/media/benchmark.html\",\n            script_nonce=script_nonce,\n            benchmark_key=benchmark_key,\n            filter=re.sub(\"[^A-Za-z0-9]\", \"\", self.get.get(\"filter\", \"\"))\n        )\n        yield data\n\n    @helper.encodeResponse\n    def actionBenchmarkResult(self):\n        global benchmark_key\n        if self.get.get(\"benchmark_key\", \"\") != benchmark_key:\n            return self.error403(\"Invalid benchmark key\")\n\n        self.sendHeader(content_type=\"text/plain\", noscript=True)\n\n        if \"Multiuser\" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:\n            yield \"This function is disabled on this proxy\"\n            return\n\n        yield \" \" * 1024  # Head (required for streaming)\n\n        import main\n        s = time.time()\n\n        for part in main.actions.testBenchmark(filter=self.get.get(\"filter\", \"\")):\n            yield part\n\n        yield \"\\n - Total time: %.3fs\" % (time.time() - s)\n\n\n@PluginManager.registerTo(\"Actions\")\nclass ActionsPlugin:\n    def getMultiplerTitle(self, multipler):\n        if multipler < 0.3:\n            multipler_title = \"Sloooow\"\n        elif multipler < 0.6:\n            multipler_title = \"Ehh\"\n        elif multipler < 0.8:\n            multipler_title = \"Goodish\"\n        elif multipler < 1.2:\n            multipler_title = \"OK\"\n        elif multipler < 1.7:\n            multipler_title = \"Fine\"\n        elif multipler < 2.5:\n            multipler_title = \"Fast\"\n        elif multipler < 3.5:\n            multipler_title = \"WOW\"\n        else:\n            multipler_title = \"Insane!!\"\n        return multipler_title\n\n    def formatResult(self, taken, standard):\n        if not standard:\n            return \" Done in %.3fs\" % taken\n\n        if taken > 0:\n            multipler = standard / taken\n        else:\n            multipler = 99\n        multipler_title = self.getMultiplerTitle(multipler)\n\n        return \" Done in %.3fs = %s (%.2fx)\" % (taken, multipler_title, multipler)\n\n    def getBenchmarkTests(self, online=False):\n        if hasattr(super(), \"getBenchmarkTests\"):\n            tests = super().getBenchmarkTests(online)\n        else:\n            tests = []\n\n        tests.extend([\n            {\"func\": self.testHdPrivatekey, \"num\": 50, \"time_standard\": 0.57},\n            {\"func\": self.testSign, \"num\": 20, \"time_standard\": 0.46},\n            {\"func\": self.testVerify, \"kwargs\": {\"lib_verify\": \"sslcrypto_fallback\"}, \"num\": 20, \"time_standard\": 0.38},\n            {\"func\": self.testVerify, \"kwargs\": {\"lib_verify\": \"sslcrypto\"}, \"num\": 200, \"time_standard\": 0.30},\n            {\"func\": self.testVerify, \"kwargs\": {\"lib_verify\": \"libsecp256k1\"}, \"num\": 200, \"time_standard\": 0.10},\n\n            {\"func\": self.testPackMsgpack, \"num\": 100, \"time_standard\": 0.35},\n            {\"func\": self.testUnpackMsgpackStreaming, \"kwargs\": {\"fallback\": False}, \"num\": 100, \"time_standard\": 0.35},\n            {\"func\": self.testUnpackMsgpackStreaming, \"kwargs\": {\"fallback\": True}, \"num\": 10, \"time_standard\": 0.5},\n\n            {\"func\": self.testPackZip, \"num\": 5, \"time_standard\": 0.065},\n            {\"func\": self.testPackArchive, \"kwargs\": {\"archive_type\": \"gz\"}, \"num\": 5, \"time_standard\": 0.08},\n            {\"func\": self.testPackArchive, \"kwargs\": {\"archive_type\": \"bz2\"}, \"num\": 5, \"time_standard\": 0.68},\n            {\"func\": self.testPackArchive, \"kwargs\": {\"archive_type\": \"xz\"}, \"num\": 5, \"time_standard\": 0.47},\n            {\"func\": self.testUnpackZip, \"num\": 20, \"time_standard\": 0.25},\n            {\"func\": self.testUnpackArchive, \"kwargs\": {\"archive_type\": \"gz\"}, \"num\": 20, \"time_standard\": 0.28},\n            {\"func\": self.testUnpackArchive, \"kwargs\": {\"archive_type\": \"bz2\"}, \"num\": 20, \"time_standard\": 0.83},\n            {\"func\": self.testUnpackArchive, \"kwargs\": {\"archive_type\": \"xz\"}, \"num\": 20, \"time_standard\": 0.38},\n\n            {\"func\": self.testCryptHash, \"kwargs\": {\"hash_type\": \"sha256\"}, \"num\": 10, \"time_standard\": 0.50},\n            {\"func\": self.testCryptHash, \"kwargs\": {\"hash_type\": \"sha512\"}, \"num\": 10, \"time_standard\": 0.33},\n            {\"func\": self.testCryptHashlib, \"kwargs\": {\"hash_type\": \"sha3_256\"}, \"num\": 10, \"time_standard\": 0.33},\n            {\"func\": self.testCryptHashlib, \"kwargs\": {\"hash_type\": \"sha3_512\"}, \"num\": 10, \"time_standard\": 0.65},\n\n            {\"func\": self.testRandom, \"num\": 100, \"time_standard\": 0.08},\n        ])\n\n        if online:\n            tests += [\n                {\"func\": self.testHttps, \"num\": 1, \"time_standard\": 2.1}\n            ]\n        return tests\n\n    def testBenchmark(self, num_multipler=1, online=False, num_run=None, filter=None):\n        \"\"\"\n        Run benchmark on client functions\n        \"\"\"\n        tests = self.getBenchmarkTests(online=online)\n\n        if filter:\n            tests = [test for test in tests[:] if filter.lower() in test[\"func\"].__name__.lower()]\n\n        yield \"\\n\"\n        res = {}\n        res_time_taken = {}\n        multiplers = []\n        for test in tests:\n            s = time.time()\n            if num_run:\n                num_run_test = num_run\n            else:\n                num_run_test = math.ceil(test[\"num\"] * num_multipler)\n            func = test[\"func\"]\n            func_name = func.__name__\n            kwargs = test.get(\"kwargs\", {})\n            key = \"%s %s\" % (func_name, kwargs)\n            if kwargs:\n                yield \"* Running %s (%s) x %s \" % (func_name, kwargs, num_run_test)\n            else:\n                yield \"* Running %s x %s \" % (func_name, num_run_test)\n            i = 0\n            try:\n                for progress in func(num_run_test, **kwargs):\n                    i += 1\n                    if num_run_test > 10:\n                        should_print = i % (num_run_test / 10) == 0 or progress != \".\"\n                    else:\n                        should_print = True\n\n                    if should_print:\n                        if num_run_test == 1 and progress == \".\":\n                            progress = \"...\"\n                        yield progress\n                time_taken = time.time() - s\n                if num_run:\n                    time_standard = 0\n                else:\n                    time_standard = test[\"time_standard\"] * num_multipler\n                yield self.formatResult(time_taken, time_standard)\n                yield \"\\n\"\n                res[key] = \"ok\"\n                res_time_taken[key] = time_taken\n                multiplers.append(time_standard / max(time_taken, 0.001))\n            except Exception as err:\n                res[key] = err\n                yield \"Failed!\\n! Error: %s\\n\\n\" % Debug.formatException(err)\n\n        yield \"\\n== Result ==\\n\"\n\n        # Check verification speed\n        if \"testVerify {'lib_verify': 'sslcrypto'}\" in res_time_taken:\n            speed_order = [\"sslcrypto_fallback\", \"sslcrypto\", \"libsecp256k1\"]\n            time_taken = {}\n            for lib_verify in speed_order:\n                time_taken[lib_verify] = res_time_taken[\"testVerify {'lib_verify': '%s'}\" % lib_verify]\n\n            time_taken[\"sslcrypto_fallback\"] *= 10  # fallback benchmark only run 20 times instead of 200\n            speedup_sslcrypto = time_taken[\"sslcrypto_fallback\"] / time_taken[\"sslcrypto\"]\n            speedup_libsecp256k1 = time_taken[\"sslcrypto_fallback\"] / time_taken[\"libsecp256k1\"]\n\n            yield \"\\n* Verification speedup:\\n\"\n            yield \" - OpenSSL: %.1fx (reference: 7.0x)\\n\" % speedup_sslcrypto\n            yield \" - libsecp256k1: %.1fx (reference: 23.8x)\\n\" % speedup_libsecp256k1\n\n            if speedup_sslcrypto < 2:\n                res[\"Verification speed\"] = \"error: OpenSSL speedup low: %.1fx\" % speedup_sslcrypto\n\n            if speedup_libsecp256k1 < speedup_sslcrypto:\n                res[\"Verification speed\"] = \"error: libsecp256k1 speedup low: %.1fx\" % speedup_libsecp256k1\n\n        if not res:\n            yield \"! No tests found\"\n            if config.action == \"test\":\n                sys.exit(1)\n        else:\n            num_failed = len([res_key for res_key, res_val in res.items() if res_val != \"ok\"])\n            num_success = len([res_key for res_key, res_val in res.items() if res_val == \"ok\"])\n            yield \"\\n* Tests:\\n\"\n            yield \" - Total: %s tests\\n\" % len(res)\n            yield \" - Success: %s tests\\n\" % num_success\n            yield \" - Failed: %s tests\\n\" % num_failed\n            if any(multiplers):\n                multipler_avg = sum(multiplers) / len(multiplers)\n                multipler_title = self.getMultiplerTitle(multipler_avg)\n                yield \" - Average speed factor: %.2fx (%s)\\n\" % (multipler_avg, multipler_title)\n\n            # Display errors\n            for res_key, res_val in res.items():\n                if res_val != \"ok\":\n                    yield \" ! %s %s\\n\" % (res_key, res_val)\n\n            if num_failed != 0 and config.action == \"test\":\n                sys.exit(1)\n\n    def testHttps(self, num_run=1):\n        \"\"\"\n        Test https connection with valid and invalid certs\n        \"\"\"\n        import urllib.request\n        import urllib.error\n\n        body = urllib.request.urlopen(\"https://google.com\").read()\n        assert len(body) > 100\n        yield \".\"\n\n        badssl_urls = [\n            \"https://expired.badssl.com/\",\n            \"https://wrong.host.badssl.com/\",\n            \"https://self-signed.badssl.com/\",\n            \"https://untrusted-root.badssl.com/\"\n        ]\n        for badssl_url in badssl_urls:\n            try:\n                body = urllib.request.urlopen(badssl_url).read()\n                https_err = None\n            except urllib.error.URLError as err:\n                https_err = err\n            assert https_err\n            yield \".\"\n\n    def testCryptHash(self, num_run=1, hash_type=\"sha256\"):\n        \"\"\"\n        Test hashing functions\n        \"\"\"\n        yield \"(5MB) \"\n\n        from Crypt import CryptHash\n\n        hash_types = {\n            \"sha256\": {\"func\": CryptHash.sha256sum, \"hash_valid\": \"8cd629d9d6aff6590da8b80782a5046d2673d5917b99d5603c3dcb4005c45ffa\"},\n            \"sha512\": {\"func\": CryptHash.sha512sum, \"hash_valid\": \"9ca7e855d430964d5b55b114e95c6bbb114a6d478f6485df93044d87b108904d\"}\n        }\n        hash_func = hash_types[hash_type][\"func\"]\n        hash_valid = hash_types[hash_type][\"hash_valid\"]\n\n        data = io.BytesIO(b\"Hello\" * 1024 * 1024)  # 5MB\n        for i in range(num_run):\n            data.seek(0)\n            hash = hash_func(data)\n            yield \".\"\n        assert hash == hash_valid, \"%s != %s\" % (hash, hash_valid)\n\n    def testCryptHashlib(self, num_run=1, hash_type=\"sha3_256\"):\n        \"\"\"\n        Test SHA3 hashing functions\n        \"\"\"\n        yield \"x 5MB \"\n\n        hash_types = {\n            \"sha3_256\": {\"func\": hashlib.sha3_256, \"hash_valid\": \"c8aeb3ef9fe5d6404871c0d2a4410a4d4e23268e06735648c9596f436c495f7e\"},\n            \"sha3_512\": {\"func\": hashlib.sha3_512, \"hash_valid\": \"b75dba9472d8af3cc945ce49073f3f8214d7ac12086c0453fb08944823dee1ae83b3ffbc87a53a57cc454521d6a26fe73ff0f3be38dddf3f7de5d7692ebc7f95\"},\n        }\n\n        hash_func = hash_types[hash_type][\"func\"]\n        hash_valid = hash_types[hash_type][\"hash_valid\"]\n\n        data = io.BytesIO(b\"Hello\" * 1024 * 1024)  # 5MB\n        for i in range(num_run):\n            data.seek(0)\n            h = hash_func()\n            while 1:\n                buff = data.read(1024 * 64)\n                if not buff:\n                    break\n                h.update(buff)\n            hash = h.hexdigest()\n            yield \".\"\n        assert hash == hash_valid, \"%s != %s\" % (hash, hash_valid)\n\n    def testRandom(self, num_run=1):\n        \"\"\"\n        Test generating random data\n        \"\"\"\n        yield \"x 1000 x 256 bytes \"\n        for i in range(num_run):\n            data_last = None\n            for y in range(1000):\n                data = os.urandom(256)\n                assert data != data_last\n                assert len(data) == 256\n                data_last = data\n            yield \".\"\n\n    def testHdPrivatekey(self, num_run=2):\n        \"\"\"\n        Test generating deterministic private keys from a master seed\n        \"\"\"\n        from Crypt import CryptBitcoin\n        seed = \"e180efa477c63b0f2757eac7b1cce781877177fe0966be62754ffd4c8592ce38\"\n        privatekeys = []\n        for i in range(num_run):\n            privatekeys.append(CryptBitcoin.hdPrivatekey(seed, i * 10))\n            yield \".\"\n        valid = \"5JSbeF5PevdrsYjunqpg7kAGbnCVYa1T4APSL3QRu8EoAmXRc7Y\"\n        assert privatekeys[0] == valid, \"%s != %s\" % (privatekeys[0], valid)\n        if len(privatekeys) > 1:\n            assert privatekeys[0] != privatekeys[-1]\n\n    def testSign(self, num_run=1):\n        \"\"\"\n        Test signing data using a private key\n        \"\"\"\n        from Crypt import CryptBitcoin\n        data = \"Hello\" * 1024\n        privatekey = \"5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk\"\n        for i in range(num_run):\n            yield \".\"\n            sign = CryptBitcoin.sign(data, privatekey)\n            valid = \"G1GXaDauZ8vX/N9Jn+MRiGm9h+I94zUhDnNYFaqMGuOiBHB+kp4cRPZOL7l1yqK5BHa6J+W97bMjvTXtxzljp6w=\"\n            assert sign == valid, \"%s != %s\" % (sign, valid)\n\n    def testVerify(self, num_run=1, lib_verify=\"sslcrypto\"):\n        \"\"\"\n        Test verification of generated signatures\n        \"\"\"\n        from Crypt import CryptBitcoin\n        CryptBitcoin.loadLib(lib_verify, silent=True)\n\n\n        data = \"Hello\" * 1024\n        privatekey = \"5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk\"\n        address = CryptBitcoin.privatekeyToAddress(privatekey)\n        sign = \"G1GXaDauZ8vX/N9Jn+MRiGm9h+I94zUhDnNYFaqMGuOiBHB+kp4cRPZOL7l1yqK5BHa6J+W97bMjvTXtxzljp6w=\"\n\n        for i in range(num_run):\n            ok = CryptBitcoin.verify(data, address, sign, lib_verify=lib_verify)\n            yield \".\"\n            assert ok, \"does not verify from %s\" % address\n\n        if lib_verify == \"sslcrypto\":\n            yield(\"(%s)\" % CryptBitcoin.sslcrypto.ecc.get_backend())\n\n    def testPortCheckers(self):\n        \"\"\"\n        Test all active open port checker\n        \"\"\"\n        from Peer import PeerPortchecker\n        for ip_type, func_names in PeerPortchecker.PeerPortchecker.checker_functions.items():\n            yield \"\\n- %s:\" % ip_type\n            for func_name in func_names:\n                yield \"\\n - Tracker %s: \" % func_name\n                try:\n                    for res in self.testPortChecker(func_name):\n                        yield res\n                except Exception as err:\n                    yield Debug.formatException(err)\n\n    def testPortChecker(self, func_name):\n        \"\"\"\n        Test single open port checker\n        \"\"\"\n        from Peer import PeerPortchecker\n        peer_portchecker = PeerPortchecker.PeerPortchecker(None)\n        announce_func = getattr(peer_portchecker, func_name)\n        res = announce_func(3894)\n        yield res\n\n    def testAll(self):\n        \"\"\"\n        Run all tests to check system compatibility with ZeroNet functions\n        \"\"\"\n        for progress in self.testBenchmark(online=not config.offline, num_run=1):\n            yield progress\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        back = super(ConfigPlugin, self).createArguments()\n        if self.getCmdlineValue(\"test\") == \"benchmark\":\n            self.test_parser.add_argument(\n                '--num_multipler', help='Benchmark run time multipler',\n                default=1.0, type=float, metavar='num'\n            )\n            self.test_parser.add_argument(\n                '--filter', help='Filter running benchmark',\n                default=None, metavar='test name'\n            )\n        elif self.getCmdlineValue(\"test\") == \"portChecker\":\n            self.test_parser.add_argument(\n                '--func_name', help='Name of open port checker function',\n                default=None, metavar='func_name'\n            )\n        return back\n"
  },
  {
    "path": "plugins/Benchmark/__init__.py",
    "content": "from . import BenchmarkPlugin\nfrom . import BenchmarkDb\nfrom . import BenchmarkPack\n"
  },
  {
    "path": "plugins/Benchmark/media/benchmark.html",
    "content": "<html>\n<script nonce=\"{script_nonce}\">\nwindow.benchmark_key = \"{benchmark_key}\";\n\nfunction setState(elem, text) {\n    var formatted = text\n    var parts = text.match(/\\* Running (.*?)(\\n|$)/g)\n    if (parts) {\n        for (var i=0; i < parts.length; i++) {\n            part = parts[i];\n            var details = part.match(/\\* Running (.*?) (\\.+|$)(.*)/);\n            if (details) {\n                var title = details[1]\n                var progress = details[2]\n                var result = details[3]\n\n                result_parts = result.match(/(.*) Done in ([0-9\\.]+)s = (.*?) \\(([0-9\\.]+)x\\)/)\n                var percent = Math.min(100, progress.length * 10)\n                if (result_parts) percent = 100\n                var style = \"background-image: linear-gradient(90deg, #FFF \" + percent + \"%, #FFF 0%, #d9d5de 0%);\"\n                var part_formatted = \"<div class='test' style='\" + style + \"'>\"\n                part_formatted += \"<span class='title'>\" + title + \"</span><span class='percent percent-\" + percent + \"'>\" + percent + \"%</span> \"\n                if (result_parts) {\n                    var result_extra = result_parts[1]\n                    var taken = result_parts[2]\n                    var multipler_title = result_parts[3]\n                    var multipler = result_parts[4]\n                    part_formatted += \"<div class='result result-\" + multipler_title.replace(/[^A-Za-z]/g, \"\") + \"'>\"\n                    part_formatted += \" <span class='taken'>\" + taken + \"s</span>\"\n                    part_formatted += \" <span class='multipler'>\" + multipler + \"x</span>\"\n                    part_formatted += \" <span class='multipler-title'>\" + multipler_title + \"</span>\"\n                    part_formatted += \"</div>\"\n                } else {\n                    part_formatted += \"<div class='result'>\" + result + \"</div>\"\n                }\n                part_formatted += \"</div>\"\n                formatted = formatted.replace(part, part_formatted);\n            }\n        }\n    }\n    formatted = formatted.replace(/(\\! Error:.*)/, \"<div class='test error'>$1</div>\");\n    formatted = formatted.replace(/(\\== Result ==[^]*)/, \"<div class='test summary'>$1</div>\");\n    var is_bottom = document.body.scrollTop + document.body.clientHeight >= document.body.scrollHeight - 5;\n    elem.innerHTML = formatted.trim();\n    if (is_bottom)\n        document.body.scrollTop = document.body.scrollHeight;\n}\n\nfunction stream(url, elem) {\n    document.getElementById(\"h1\").innerText = \"Benchmark: Starting...\"\n    var xhr = new XMLHttpRequest();\n    xhr.open('GET', url, true);\n    xhr.setRequestHeader('Accept', 'text/html');\n    xhr.send(null);\n    xhr.onreadystatechange = function(state) {\n        document.getElementById(\"h1\").innerText = \"Benchmark: Running...\"\n\t\tsetState(elem, xhr.responseText);\n\t\tif (xhr.readyState == 4) {\n            document.getElementById(\"h1\").innerText = \"Benchmark: Done.\"\n\t\t}\n\t}\n}\n</script>\n<body>\n<style>\nbody {\nbackground-color: #3c3546;\nbackground-image: url(\"data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23cfcfcf' fill-opacity='0.09'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\");}\nh1 {\n    font-family: monospace; color: white; font-weight: normal; text-transform: uppercase;\n    max-width: 690px; margin: 30px auto; margin-bottom: 10px;\n}\n#out {\n    white-space: pre-line; background-color: #ffffff1a; padding: 20px; font-family: Consolas, monospace;\n    font-size: 11px; width: 90%; margin: auto; max-width: 650px; box-shadow: 0px 10px 30px -10px #5c5c5c6b;\n}\n.test { padding: 12px; box-shadow: 0px 5px 13px -5px #5c5c5c6b; margin-bottom: -2px; background-color: white; border: 1px solid #dbdbdb; }\n.test .percent { float: right; }\n.test .percent-100 { display: none; }\n.test .result { float: right; }\n.test .title { max-width: calc(100% - 150px); display: inline-block; }\n.test .multipler-title { display: inline-block; width: 50px; text-align: right; }\n.test:last-child { margin-bottom: 15px; border-color: #c1c1c1; }\n\n.test .result-Sloooow { color: red; }\n.test .result-Ehh { color: #ad1457; }\n.test .result-Goodish { color: #ef6c00; }\n.test .result-Ok { color: #00cf03; }\n.test .result-Fine { color: #00bcd4; }\n.test .result-Fast { color: #4b78ff; }\n.test .result-WOW { color: #9c27b0; }\n.test .result-Insane { color: #d603f4; }\n\n.test.summary { margin-top: 20px; text-transform: uppercase; border-left: 10px solid #00ff63; border-color: #00ff63; }\n.test.error { background-color: #ff2259; color: white; border-color: red; }\n\n#start { text-align: center }\n.button {\n    background-color: white; padding: 10px 20px; display: inline-block; border-radius: 5px;\n    text-decoration: none; color: #673AB7; text-transform: uppercase; margin-bottom: 11px; border-bottom: 2px solid #c1bff8;\n}\n.button:hover { border-bottom-color: #673AB7; }\n.button:active { transform: translateY(1px) }\nsmall { text-transform: uppercase; opacity: 0.7; color: white; letter-spacing: 1px; }\n</style>\n\n<h1 id=\"h1\">Benchmark</h1>\n<div id=\"out\">\n <div id=\"start\">\n  <a href=\"#Start\" class=\"button\" id=\"start_button\">Start benchmark</a>\n  <small>(It will take around 20 sec)</small>\n </div>\n</div>\n\n<script nonce=\"{script_nonce}\">\nfunction start() {\n    stream(\"/BenchmarkResult?benchmark_key={benchmark_key}&filter={filter}\", document.getElementById(\"out\"));\n    return false;\n}\ndocument.getElementById(\"start_button\").onclick = start\n</script>\n</body>\n</html>"
  },
  {
    "path": "plugins/Benchmark/plugin_info.json",
    "content": "{\n\t\"name\": \"Benchmark\",\n\t\"description\": \"Test and benchmark database and cryptographic functions related to ZeroNet.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/Bigfile/BigfilePiecefield.py",
    "content": "import array\n\n\ndef packPiecefield(data):\n    if not isinstance(data, bytes) and not isinstance(data, bytearray):\n        raise Exception(\"Invalid data type: %s\" % type(data))\n\n    res = []\n    if not data:\n        return array.array(\"H\", b\"\")\n\n    if data[0] == b\"\\x00\":\n        res.append(0)\n        find = b\"\\x01\"\n    else:\n        find = b\"\\x00\"\n    last_pos = 0\n    pos = 0\n    while 1:\n        pos = data.find(find, pos)\n        if find == b\"\\x00\":\n            find = b\"\\x01\"\n        else:\n            find = b\"\\x00\"\n        if pos == -1:\n            res.append(len(data) - last_pos)\n            break\n        res.append(pos - last_pos)\n        last_pos = pos\n    return array.array(\"H\", res)\n\n\ndef unpackPiecefield(data):\n    if not data:\n        return b\"\"\n\n    res = []\n    char = b\"\\x01\"\n    for times in data:\n        if times > 10000:\n            return b\"\"\n        res.append(char * times)\n        if char == b\"\\x01\":\n            char = b\"\\x00\"\n        else:\n            char = b\"\\x01\"\n    return b\"\".join(res)\n\n\ndef spliceBit(data, idx, bit):\n    if bit != b\"\\x00\" and bit != b\"\\x01\":\n        raise Exception(\"Invalid bit: %s\" % bit)\n\n    if len(data) < idx:\n        data = data.ljust(idx + 1, b\"\\x00\")\n    return data[:idx] + bit + data[idx+ 1:]\n\nclass Piecefield(object):\n    def tostring(self):\n        return \"\".join([\"1\" if b else \"0\" for b in self.tobytes()])\n\n\nclass BigfilePiecefield(Piecefield):\n    __slots__ = [\"data\"]\n\n    def __init__(self):\n        self.data = b\"\"\n\n    def frombytes(self, s):\n        if not isinstance(s, bytes) and not isinstance(s, bytearray):\n            raise Exception(\"Invalid type: %s\" % type(s))\n        self.data = s\n\n    def tobytes(self):\n        return self.data\n\n    def pack(self):\n        return packPiecefield(self.data).tobytes()\n\n    def unpack(self, s):\n        self.data = unpackPiecefield(array.array(\"H\", s))\n\n    def __getitem__(self, key):\n        try:\n            return self.data[key]\n        except IndexError:\n            return False\n\n    def __setitem__(self, key, value):\n        self.data = spliceBit(self.data, key, value)\n\nclass BigfilePiecefieldPacked(Piecefield):\n    __slots__ = [\"data\"]\n\n    def __init__(self):\n        self.data = b\"\"\n\n    def frombytes(self, data):\n        if not isinstance(data, bytes) and not isinstance(data, bytearray):\n            raise Exception(\"Invalid type: %s\" % type(data))\n        self.data = packPiecefield(data).tobytes()\n\n    def tobytes(self):\n        return unpackPiecefield(array.array(\"H\", self.data))\n\n    def pack(self):\n        return array.array(\"H\", self.data).tobytes()\n\n    def unpack(self, data):\n        self.data = data\n\n    def __getitem__(self, key):\n        try:\n            return self.tobytes()[key]\n        except IndexError:\n            return False\n\n    def __setitem__(self, key, value):\n        data = spliceBit(self.tobytes(), key, value)\n        self.frombytes(data)\n\n\nif __name__ == \"__main__\":\n    import os\n    import psutil\n    import time\n    testdata = b\"\\x01\" * 100 + b\"\\x00\" * 900 + b\"\\x01\" * 4000 + b\"\\x00\" * 4999 + b\"\\x01\"\n    meminfo = psutil.Process(os.getpid()).memory_info\n\n    for storage in [BigfilePiecefieldPacked, BigfilePiecefield]:\n        print(\"-- Testing storage: %s --\" % storage)\n        m = meminfo()[0]\n        s = time.time()\n        piecefields = {}\n        for i in range(10000):\n            piecefield = storage()\n            piecefield.frombytes(testdata[:i] + b\"\\x00\" + testdata[i + 1:])\n            piecefields[i] = piecefield\n\n        print(\"Create x10000: +%sKB in %.3fs (len: %s)\" % ((meminfo()[0] - m) / 1024, time.time() - s, len(piecefields[0].data)))\n\n        m = meminfo()[0]\n        s = time.time()\n        for piecefield in list(piecefields.values()):\n            val = piecefield[1000]\n\n        print(\"Query one x10000: +%sKB in %.3fs\" % ((meminfo()[0] - m) / 1024, time.time() - s))\n\n        m = meminfo()[0]\n        s = time.time()\n        for piecefield in list(piecefields.values()):\n            piecefield[1000] = b\"\\x01\"\n\n        print(\"Change one x10000: +%sKB in %.3fs\" % ((meminfo()[0] - m) / 1024, time.time() - s))\n\n        m = meminfo()[0]\n        s = time.time()\n        for piecefield in list(piecefields.values()):\n            packed = piecefield.pack()\n\n        print(\"Pack x10000: +%sKB in %.3fs (len: %s)\" % ((meminfo()[0] - m) / 1024, time.time() - s, len(packed)))\n\n        m = meminfo()[0]\n        s = time.time()\n        for piecefield in list(piecefields.values()):\n            piecefield.unpack(packed)\n\n        print(\"Unpack x10000: +%sKB in %.3fs (len: %s)\" % ((meminfo()[0] - m) / 1024, time.time() - s, len(piecefields[0].data)))\n\n        piecefields = {}\n"
  },
  {
    "path": "plugins/Bigfile/BigfilePlugin.py",
    "content": "import time\nimport os\nimport subprocess\nimport shutil\nimport collections\nimport math\nimport warnings\nimport base64\nimport binascii\nimport json\n\nimport gevent\nimport gevent.lock\n\nfrom Plugin import PluginManager\nfrom Debug import Debug\nfrom Crypt import CryptHash\nwith warnings.catch_warnings():\n    warnings.filterwarnings(\"ignore\")  # Ignore missing sha3 warning\n    import merkletools\n\nfrom util import helper\nfrom util import Msgpack\nfrom util.Flag import flag\nimport util\nfrom .BigfilePiecefield import BigfilePiecefield, BigfilePiecefieldPacked\n\n\n# We can only import plugin host clases after the plugins are loaded\n@PluginManager.afterLoad\ndef importPluginnedClasses():\n    global VerifyError, config\n    from Content.ContentManager import VerifyError\n    from Config import config\n\n\nif \"upload_nonces\" not in locals():\n    upload_nonces = {}\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def isCorsAllowed(self, path):\n        if path == \"/ZeroNet-Internal/BigfileUpload\":\n            return True\n        else:\n            return super(UiRequestPlugin, self).isCorsAllowed(path)\n\n    @helper.encodeResponse\n    def actionBigfileUpload(self):\n        nonce = self.get.get(\"upload_nonce\")\n        if nonce not in upload_nonces:\n            return self.error403(\"Upload nonce error.\")\n\n        upload_info = upload_nonces[nonce]\n        del upload_nonces[nonce]\n\n        self.sendHeader(200, \"text/html\", noscript=True, extra_headers={\n            \"Access-Control-Allow-Origin\": \"null\",\n            \"Access-Control-Allow-Credentials\": \"true\"\n        })\n\n        self.readMultipartHeaders(self.env['wsgi.input'])  # Skip http headers\n        result = self.handleBigfileUpload(upload_info, self.env['wsgi.input'].read)\n        return json.dumps(result)\n\n    def actionBigfileUploadWebsocket(self):\n        ws = self.env.get(\"wsgi.websocket\")\n\n        if not ws:\n            self.start_response(\"400 Bad Request\", [])\n            return [b\"Not a websocket request!\"]\n\n        nonce = self.get.get(\"upload_nonce\")\n        if nonce not in upload_nonces:\n            return self.error403(\"Upload nonce error.\")\n\n        upload_info = upload_nonces[nonce]\n        del upload_nonces[nonce]\n\n        ws.send(\"poll\")\n\n        buffer = b\"\"\n        def read(size):\n            nonlocal buffer\n            while len(buffer) < size:\n                buffer += ws.receive()\n                ws.send(\"poll\")\n            part, buffer = buffer[:size], buffer[size:]\n            return part\n\n        result = self.handleBigfileUpload(upload_info, read)\n        ws.send(json.dumps(result))\n\n    def handleBigfileUpload(self, upload_info, read):\n        site = upload_info[\"site\"]\n        inner_path = upload_info[\"inner_path\"]\n\n        with site.storage.open(inner_path, \"wb\", create_dirs=True) as out_file:\n            merkle_root, piece_size, piecemap_info = site.content_manager.hashBigfile(\n                read, upload_info[\"size\"], upload_info[\"piece_size\"], out_file\n            )\n\n        if len(piecemap_info[\"sha512_pieces\"]) == 1:  # Small file, don't split\n            hash = binascii.hexlify(piecemap_info[\"sha512_pieces\"][0])\n            hash_id = site.content_manager.hashfield.getHashId(hash)\n            site.content_manager.optionalDownloaded(inner_path, hash_id, upload_info[\"size\"], own=True)\n\n        else:  # Big file\n            file_name = helper.getFilename(inner_path)\n            site.storage.open(upload_info[\"piecemap\"], \"wb\").write(Msgpack.pack({file_name: piecemap_info}))\n\n            # Find piecemap and file relative path to content.json\n            file_info = site.content_manager.getFileInfo(inner_path, new_file=True)\n            content_inner_path_dir = helper.getDirname(file_info[\"content_inner_path\"])\n            piecemap_relative_path = upload_info[\"piecemap\"][len(content_inner_path_dir):]\n            file_relative_path = inner_path[len(content_inner_path_dir):]\n\n            # Add file to content.json\n            if site.storage.isFile(file_info[\"content_inner_path\"]):\n                content = site.storage.loadJson(file_info[\"content_inner_path\"])\n            else:\n                content = {}\n            if \"files_optional\" not in content:\n                content[\"files_optional\"] = {}\n\n            content[\"files_optional\"][file_relative_path] = {\n                \"sha512\": merkle_root,\n                \"size\": upload_info[\"size\"],\n                \"piecemap\": piecemap_relative_path,\n                \"piece_size\": piece_size\n            }\n\n            merkle_root_hash_id = site.content_manager.hashfield.getHashId(merkle_root)\n            site.content_manager.optionalDownloaded(inner_path, merkle_root_hash_id, upload_info[\"size\"], own=True)\n            site.storage.writeJson(file_info[\"content_inner_path\"], content)\n\n            site.content_manager.contents.loadItem(file_info[\"content_inner_path\"])  # reload cache\n\n        return {\n            \"merkle_root\": merkle_root,\n            \"piece_num\": len(piecemap_info[\"sha512_pieces\"]),\n            \"piece_size\": piece_size,\n            \"inner_path\": inner_path\n        }\n\n    def readMultipartHeaders(self, wsgi_input):\n        found = False\n        for i in range(100):\n            line = wsgi_input.readline()\n            if line == b\"\\r\\n\":\n                found = True\n                break\n        if not found:\n            raise Exception(\"No multipart header found\")\n        return i\n\n    def actionFile(self, file_path, *args, **kwargs):\n        if kwargs.get(\"file_size\", 0) > 1024 * 1024 and kwargs.get(\"path_parts\"):  # Only check files larger than 1MB\n            path_parts = kwargs[\"path_parts\"]\n            site = self.server.site_manager.get(path_parts[\"address\"])\n            big_file = site.storage.openBigfile(path_parts[\"inner_path\"], prebuffer=2 * 1024 * 1024)\n            if big_file:\n                kwargs[\"file_obj\"] = big_file\n                kwargs[\"file_size\"] = big_file.size\n\n        return super(UiRequestPlugin, self).actionFile(file_path, *args, **kwargs)\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def actionBigfileUploadInit(self, to, inner_path, size, protocol=\"xhr\"):\n        valid_signers = self.site.content_manager.getValidSigners(inner_path)\n        auth_address = self.user.getAuthAddress(self.site.address)\n        if not self.site.settings[\"own\"] and auth_address not in valid_signers:\n            self.log.error(\"FileWrite forbidden %s not in valid_signers %s\" % (auth_address, valid_signers))\n            return self.response(to, {\"error\": \"Forbidden, you can only modify your own files\"})\n\n        nonce = CryptHash.random()\n        piece_size = 1024 * 1024\n        inner_path = self.site.content_manager.sanitizePath(inner_path)\n        file_info = self.site.content_manager.getFileInfo(inner_path, new_file=True)\n\n        content_inner_path_dir = helper.getDirname(file_info[\"content_inner_path\"])\n        file_relative_path = inner_path[len(content_inner_path_dir):]\n\n        upload_nonces[nonce] = {\n            \"added\": time.time(),\n            \"site\": self.site,\n            \"inner_path\": inner_path,\n            \"websocket_client\": self,\n            \"size\": size,\n            \"piece_size\": piece_size,\n            \"piecemap\": inner_path + \".piecemap.msgpack\"\n        }\n\n        if protocol == \"xhr\":\n            return {\n                \"url\": \"/ZeroNet-Internal/BigfileUpload?upload_nonce=\" + nonce,\n                \"piece_size\": piece_size,\n                \"inner_path\": inner_path,\n                \"file_relative_path\": file_relative_path\n            }\n        elif protocol == \"websocket\":\n            server_url = self.request.getWsServerUrl()\n            if server_url:\n                proto, host = server_url.split(\"://\")\n                origin = proto.replace(\"http\", \"ws\") + \"://\" + host\n            else:\n                origin = \"{origin}\"\n            return {\n                \"url\": origin + \"/ZeroNet-Internal/BigfileUploadWebsocket?upload_nonce=\" + nonce,\n                \"piece_size\": piece_size,\n                \"inner_path\": inner_path,\n                \"file_relative_path\": file_relative_path\n            }\n        else:\n            return {\"error\": \"Unknown protocol\"}\n\n    @flag.no_multiuser\n    def actionSiteSetAutodownloadBigfileLimit(self, to, limit):\n        permissions = self.getPermissions(to)\n        if \"ADMIN\" not in permissions:\n            return self.response(to, \"You don't have permission to run this command\")\n\n        self.site.settings[\"autodownload_bigfile_size_limit\"] = int(limit)\n        self.response(to, \"ok\")\n\n    def actionFileDelete(self, to, inner_path):\n        piecemap_inner_path = inner_path + \".piecemap.msgpack\"\n        if self.hasFilePermission(inner_path) and self.site.storage.isFile(piecemap_inner_path):\n            # Also delete .piecemap.msgpack file if exists\n            self.log.debug(\"Deleting piecemap: %s\" % piecemap_inner_path)\n            file_info = self.site.content_manager.getFileInfo(piecemap_inner_path)\n            if file_info:\n                content_json = self.site.storage.loadJson(file_info[\"content_inner_path\"])\n                relative_path = file_info[\"relative_path\"]\n                if relative_path in content_json.get(\"files_optional\", {}):\n                    del content_json[\"files_optional\"][relative_path]\n                    self.site.storage.writeJson(file_info[\"content_inner_path\"], content_json)\n                    self.site.content_manager.loadContent(file_info[\"content_inner_path\"], add_bad_files=False, force=True)\n                    try:\n                        self.site.storage.delete(piecemap_inner_path)\n                    except Exception as err:\n                        self.log.error(\"File %s delete error: %s\" % (piecemap_inner_path, err))\n\n        return super(UiWebsocketPlugin, self).actionFileDelete(to, inner_path)\n\n\n@PluginManager.registerTo(\"ContentManager\")\nclass ContentManagerPlugin(object):\n    def getFileInfo(self, inner_path, *args, **kwargs):\n        if \"|\" not in inner_path:\n            return super(ContentManagerPlugin, self).getFileInfo(inner_path, *args, **kwargs)\n\n        inner_path, file_range = inner_path.split(\"|\")\n        pos_from, pos_to = map(int, file_range.split(\"-\"))\n        file_info = super(ContentManagerPlugin, self).getFileInfo(inner_path, *args, **kwargs)\n        return file_info\n\n    def readFile(self, read_func, size, buff_size=1024 * 64):\n        part_num = 0\n        recv_left = size\n\n        while 1:\n            part_num += 1\n            read_size = min(buff_size, recv_left)\n            part = read_func(read_size)\n\n            if not part:\n                break\n            yield part\n\n            if part_num % 100 == 0:  # Avoid blocking ZeroNet execution during upload\n                time.sleep(0.001)\n\n            recv_left -= read_size\n            if recv_left <= 0:\n                break\n\n    def hashBigfile(self, read_func, size, piece_size=1024 * 1024, file_out=None):\n        self.site.settings[\"has_bigfile\"] = True\n\n        recv = 0\n        try:\n            piece_hash = CryptHash.sha512t()\n            piece_hashes = []\n            piece_recv = 0\n\n            mt = merkletools.MerkleTools()\n            mt.hash_function = CryptHash.sha512t\n\n            part = \"\"\n            for part in self.readFile(read_func, size):\n                if file_out:\n                    file_out.write(part)\n\n                recv += len(part)\n                piece_recv += len(part)\n                piece_hash.update(part)\n                if piece_recv >= piece_size:\n                    piece_digest = piece_hash.digest()\n                    piece_hashes.append(piece_digest)\n                    mt.leaves.append(piece_digest)\n                    piece_hash = CryptHash.sha512t()\n                    piece_recv = 0\n\n                    if len(piece_hashes) % 100 == 0 or recv == size:\n                        self.log.info(\"- [HASHING:%.0f%%] Pieces: %s, %.1fMB/%.1fMB\" % (\n                            float(recv) / size * 100, len(piece_hashes), recv / 1024 / 1024, size / 1024 / 1024\n                        ))\n                        part = \"\"\n            if len(part) > 0:\n                piece_digest = piece_hash.digest()\n                piece_hashes.append(piece_digest)\n                mt.leaves.append(piece_digest)\n        except Exception as err:\n            raise err\n        finally:\n            if file_out:\n                file_out.close()\n\n        mt.make_tree()\n        merkle_root = mt.get_merkle_root()\n        if type(merkle_root) is bytes:  # Python <3.5\n            merkle_root = merkle_root.decode()\n        return merkle_root, piece_size, {\n            \"sha512_pieces\": piece_hashes\n        }\n\n    def hashFile(self, dir_inner_path, file_relative_path, optional=False):\n        inner_path = dir_inner_path + file_relative_path\n\n        file_size = self.site.storage.getSize(inner_path)\n        # Only care about optional files >1MB\n        if not optional or file_size < 1 * 1024 * 1024:\n            return super(ContentManagerPlugin, self).hashFile(dir_inner_path, file_relative_path, optional)\n\n        back = {}\n        content = self.contents.get(dir_inner_path + \"content.json\")\n\n        hash = None\n        piecemap_relative_path = None\n        piece_size = None\n\n        # Don't re-hash if it's already in content.json\n        if content and file_relative_path in content.get(\"files_optional\", {}):\n            file_node = content[\"files_optional\"][file_relative_path]\n            if file_node[\"size\"] == file_size:\n                self.log.info(\"- [SAME SIZE] %s\" % file_relative_path)\n                hash = file_node.get(\"sha512\")\n                piecemap_relative_path = file_node.get(\"piecemap\")\n                piece_size = file_node.get(\"piece_size\")\n\n        if not hash or not piecemap_relative_path:  # Not in content.json yet\n            if file_size < 5 * 1024 * 1024:  # Don't create piecemap automatically for files smaller than 5MB\n                return super(ContentManagerPlugin, self).hashFile(dir_inner_path, file_relative_path, optional)\n\n            self.log.info(\"- [HASHING] %s\" % file_relative_path)\n            merkle_root, piece_size, piecemap_info = self.hashBigfile(self.site.storage.open(inner_path, \"rb\").read, file_size)\n            if not hash:\n                hash = merkle_root\n\n            if not piecemap_relative_path:\n                file_name = helper.getFilename(file_relative_path)\n                piecemap_relative_path = file_relative_path + \".piecemap.msgpack\"\n                piecemap_inner_path = inner_path + \".piecemap.msgpack\"\n\n                self.site.storage.open(piecemap_inner_path, \"wb\").write(Msgpack.pack({file_name: piecemap_info}))\n\n                back.update(super(ContentManagerPlugin, self).hashFile(dir_inner_path, piecemap_relative_path, optional=True))\n\n        piece_num = int(math.ceil(float(file_size) / piece_size))\n\n        # Add the merkle root to hashfield\n        hash_id = self.site.content_manager.hashfield.getHashId(hash)\n        self.optionalDownloaded(inner_path, hash_id, file_size, own=True)\n        self.site.storage.piecefields[hash].frombytes(b\"\\x01\" * piece_num)\n\n        back[file_relative_path] = {\"sha512\": hash, \"size\": file_size, \"piecemap\": piecemap_relative_path, \"piece_size\": piece_size}\n        return back\n\n    def getPiecemap(self, inner_path):\n        file_info = self.site.content_manager.getFileInfo(inner_path)\n        piecemap_inner_path = helper.getDirname(file_info[\"content_inner_path\"]) + file_info[\"piecemap\"]\n        self.site.needFile(piecemap_inner_path, priority=20)\n        piecemap = Msgpack.unpack(self.site.storage.open(piecemap_inner_path, \"rb\").read())[helper.getFilename(inner_path)]\n        piecemap[\"piece_size\"] = file_info[\"piece_size\"]\n        return piecemap\n\n    def verifyPiece(self, inner_path, pos, piece):\n        try:\n            piecemap = self.getPiecemap(inner_path)\n        except Exception as err:\n            raise VerifyError(\"Unable to download piecemap: %s\" % Debug.formatException(err))\n\n        piece_i = int(pos / piecemap[\"piece_size\"])\n        if CryptHash.sha512sum(piece, format=\"digest\") != piecemap[\"sha512_pieces\"][piece_i]:\n            raise VerifyError(\"Invalid hash\")\n        return True\n\n    def verifyFile(self, inner_path, file, ignore_same=True):\n        if \"|\" not in inner_path:\n            return super(ContentManagerPlugin, self).verifyFile(inner_path, file, ignore_same)\n\n        inner_path, file_range = inner_path.split(\"|\")\n        pos_from, pos_to = map(int, file_range.split(\"-\"))\n\n        return self.verifyPiece(inner_path, pos_from, file)\n\n    def optionalDownloaded(self, inner_path, hash_id, size=None, own=False):\n        if \"|\" in inner_path:\n            inner_path, file_range = inner_path.split(\"|\")\n            pos_from, pos_to = map(int, file_range.split(\"-\"))\n            file_info = self.getFileInfo(inner_path)\n\n            # Mark piece downloaded\n            piece_i = int(pos_from / file_info[\"piece_size\"])\n            self.site.storage.piecefields[file_info[\"sha512\"]][piece_i] = b\"\\x01\"\n\n            # Only add to site size on first request\n            if hash_id in self.hashfield:\n                size = 0\n        elif size > 1024 * 1024:\n            file_info = self.getFileInfo(inner_path)\n            if file_info and \"sha512\" in file_info:  # We already have the file, but not in piecefield\n                sha512 = file_info[\"sha512\"]\n                if sha512 not in self.site.storage.piecefields:\n                    self.site.storage.checkBigfile(inner_path)\n\n        return super(ContentManagerPlugin, self).optionalDownloaded(inner_path, hash_id, size, own)\n\n    def optionalRemoved(self, inner_path, hash_id, size=None):\n        if size and size > 1024 * 1024:\n            file_info = self.getFileInfo(inner_path)\n            sha512 = file_info[\"sha512\"]\n            if sha512 in self.site.storage.piecefields:\n                del self.site.storage.piecefields[sha512]\n\n            # Also remove other pieces of the file from download queue\n            for key in list(self.site.bad_files.keys()):\n                if key.startswith(inner_path + \"|\"):\n                    del self.site.bad_files[key]\n            self.site.worker_manager.removeSolvedFileTasks()\n        return super(ContentManagerPlugin, self).optionalRemoved(inner_path, hash_id, size)\n\n\n@PluginManager.registerTo(\"SiteStorage\")\nclass SiteStoragePlugin(object):\n    def __init__(self, *args, **kwargs):\n        super(SiteStoragePlugin, self).__init__(*args, **kwargs)\n        self.piecefields = collections.defaultdict(BigfilePiecefield)\n        if \"piecefields\" in self.site.settings.get(\"cache\", {}):\n            for sha512, piecefield_packed in self.site.settings[\"cache\"].get(\"piecefields\").items():\n                if piecefield_packed:\n                    self.piecefields[sha512].unpack(base64.b64decode(piecefield_packed))\n            self.site.settings[\"cache\"][\"piecefields\"] = {}\n\n    def createSparseFile(self, inner_path, size, sha512=None):\n        file_path = self.getPath(inner_path)\n\n        self.ensureDir(os.path.dirname(inner_path))\n\n        f = open(file_path, 'wb')\n        f.truncate(min(1024 * 1024 * 5, size))  # Only pre-allocate up to 5MB\n        f.close()\n        if os.name == \"nt\":\n            startupinfo = subprocess.STARTUPINFO()\n            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW\n            subprocess.call([\"fsutil\", \"sparse\", \"setflag\", file_path], close_fds=True, startupinfo=startupinfo)\n\n        if sha512 and sha512 in self.piecefields:\n            self.log.debug(\"%s: File not exists, but has piecefield. Deleting piecefield.\" % inner_path)\n            del self.piecefields[sha512]\n\n    def write(self, inner_path, content):\n        if \"|\" not in inner_path:\n            return super(SiteStoragePlugin, self).write(inner_path, content)\n\n        # Write to specific position by passing |{pos} after the filename\n        inner_path, file_range = inner_path.split(\"|\")\n        pos_from, pos_to = map(int, file_range.split(\"-\"))\n        file_path = self.getPath(inner_path)\n\n        # Create dir if not exist\n        self.ensureDir(os.path.dirname(inner_path))\n\n        if not os.path.isfile(file_path):\n            file_info = self.site.content_manager.getFileInfo(inner_path)\n            self.createSparseFile(inner_path, file_info[\"size\"])\n\n        # Write file\n        with open(file_path, \"rb+\") as file:\n            file.seek(pos_from)\n            if hasattr(content, 'read'):  # File-like object\n                shutil.copyfileobj(content, file)  # Write buff to disk\n            else:  # Simple string\n                file.write(content)\n        del content\n        self.onUpdated(inner_path)\n\n    def checkBigfile(self, inner_path):\n        file_info = self.site.content_manager.getFileInfo(inner_path)\n        if not file_info or (file_info and \"piecemap\" not in file_info):  # It's not a big file\n            return False\n\n        self.site.settings[\"has_bigfile\"] = True\n        file_path = self.getPath(inner_path)\n        sha512 = file_info[\"sha512\"]\n        piece_num = int(math.ceil(float(file_info[\"size\"]) / file_info[\"piece_size\"]))\n        if os.path.isfile(file_path):\n            if sha512 not in self.piecefields:\n                if open(file_path, \"rb\").read(128) == b\"\\0\" * 128:\n                    piece_data = b\"\\x00\"\n                else:\n                    piece_data = b\"\\x01\"\n                self.log.debug(\"%s: File exists, but not in piecefield. Filling piecefiled with %s * %s.\" % (inner_path, piece_num, piece_data))\n                self.piecefields[sha512].frombytes(piece_data * piece_num)\n        else:\n            self.log.debug(\"Creating bigfile: %s\" % inner_path)\n            self.createSparseFile(inner_path, file_info[\"size\"], sha512)\n            self.piecefields[sha512].frombytes(b\"\\x00\" * piece_num)\n            self.log.debug(\"Created bigfile: %s\" % inner_path)\n        return True\n\n    def openBigfile(self, inner_path, prebuffer=0):\n        if not self.checkBigfile(inner_path):\n            return False\n        self.site.needFile(inner_path, blocking=False)  # Download piecemap\n        return BigFile(self.site, inner_path, prebuffer=prebuffer)\n\n\nclass BigFile(object):\n    def __init__(self, site, inner_path, prebuffer=0):\n        self.site = site\n        self.inner_path = inner_path\n        file_path = site.storage.getPath(inner_path)\n        file_info = self.site.content_manager.getFileInfo(inner_path)\n        self.piece_size = file_info[\"piece_size\"]\n        self.sha512 = file_info[\"sha512\"]\n        self.size = file_info[\"size\"]\n        self.prebuffer = prebuffer\n        self.read_bytes = 0\n\n        self.piecefield = self.site.storage.piecefields[self.sha512]\n        self.f = open(file_path, \"rb+\")\n        self.read_lock = gevent.lock.Semaphore()\n\n    def read(self, buff=64 * 1024):\n        with self.read_lock:\n            pos = self.f.tell()\n            read_until = min(self.size, pos + buff)\n            requests = []\n            # Request all required blocks\n            while 1:\n                piece_i = int(pos / self.piece_size)\n                if piece_i * self.piece_size >= read_until:\n                    break\n                pos_from = piece_i * self.piece_size\n                pos_to = pos_from + self.piece_size\n                if not self.piecefield[piece_i]:\n                    requests.append(self.site.needFile(\"%s|%s-%s\" % (self.inner_path, pos_from, pos_to), blocking=False, update=True, priority=10))\n                pos += self.piece_size\n\n            if not all(requests):\n                return None\n\n            # Request prebuffer\n            if self.prebuffer:\n                prebuffer_until = min(self.size, read_until + self.prebuffer)\n                priority = 3\n                while 1:\n                    piece_i = int(pos / self.piece_size)\n                    if piece_i * self.piece_size >= prebuffer_until:\n                        break\n                    pos_from = piece_i * self.piece_size\n                    pos_to = pos_from + self.piece_size\n                    if not self.piecefield[piece_i]:\n                        self.site.needFile(\"%s|%s-%s\" % (self.inner_path, pos_from, pos_to), blocking=False, update=True, priority=max(0, priority))\n                    priority -= 1\n                    pos += self.piece_size\n\n            gevent.joinall(requests)\n            self.read_bytes += buff\n\n            # Increase buffer for long reads\n            if self.read_bytes > 7 * 1024 * 1024 and self.prebuffer < 5 * 1024 * 1024:\n                self.site.log.debug(\"%s: Increasing bigfile buffer size to 5MB...\" % self.inner_path)\n                self.prebuffer = 5 * 1024 * 1024\n\n            return self.f.read(buff)\n\n    def seek(self, pos, whence=0):\n        with self.read_lock:\n            if whence == 2:  # Relative from file end\n                pos = self.size + pos  # Use the real size instead of size on the disk\n                whence = 0\n            return self.f.seek(pos, whence)\n\n    def seekable(self):\n        return self.f.seekable()\n\n    def tell(self):\n        return self.f.tell()\n\n    def close(self):\n        self.f.close()\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.close()\n\n\n@PluginManager.registerTo(\"WorkerManager\")\nclass WorkerManagerPlugin(object):\n    def addTask(self, inner_path, *args, **kwargs):\n        file_info = kwargs.get(\"file_info\")\n        if file_info and \"piecemap\" in file_info:  # Bigfile\n            self.site.settings[\"has_bigfile\"] = True\n\n            piecemap_inner_path = helper.getDirname(file_info[\"content_inner_path\"]) + file_info[\"piecemap\"]\n            piecemap_task = None\n            if not self.site.storage.isFile(piecemap_inner_path):\n                # Start download piecemap\n                piecemap_task = super(WorkerManagerPlugin, self).addTask(piecemap_inner_path, priority=30)\n                autodownload_bigfile_size_limit = self.site.settings.get(\"autodownload_bigfile_size_limit\", config.autodownload_bigfile_size_limit)\n                if \"|\" not in inner_path and self.site.isDownloadable(inner_path) and file_info[\"size\"] / 1024 / 1024 <= autodownload_bigfile_size_limit:\n                    gevent.spawn_later(0.1, self.site.needFile, inner_path + \"|all\")  # Download all pieces\n\n            if \"|\" in inner_path:\n                # Start download piece\n                task = super(WorkerManagerPlugin, self).addTask(inner_path, *args, **kwargs)\n\n                inner_path, file_range = inner_path.split(\"|\")\n                pos_from, pos_to = map(int, file_range.split(\"-\"))\n                task[\"piece_i\"] = int(pos_from / file_info[\"piece_size\"])\n                task[\"sha512\"] = file_info[\"sha512\"]\n            else:\n                if inner_path in self.site.bad_files:\n                    del self.site.bad_files[inner_path]\n                if piecemap_task:\n                    task = piecemap_task\n                else:\n                    fake_evt = gevent.event.AsyncResult()  # Don't download anything if no range specified\n                    fake_evt.set(True)\n                    task = {\"evt\": fake_evt}\n\n            if not self.site.storage.isFile(inner_path):\n                self.site.storage.createSparseFile(inner_path, file_info[\"size\"], file_info[\"sha512\"])\n                piece_num = int(math.ceil(float(file_info[\"size\"]) / file_info[\"piece_size\"]))\n                self.site.storage.piecefields[file_info[\"sha512\"]].frombytes(b\"\\x00\" * piece_num)\n        else:\n            task = super(WorkerManagerPlugin, self).addTask(inner_path, *args, **kwargs)\n        return task\n\n    def taskAddPeer(self, task, peer):\n        if \"piece_i\" in task:\n            if not peer.piecefields[task[\"sha512\"]][task[\"piece_i\"]]:\n                if task[\"sha512\"] not in peer.piecefields:\n                    gevent.spawn(peer.updatePiecefields, force=True)\n                elif not task[\"peers\"]:\n                    gevent.spawn(peer.updatePiecefields)\n\n                return False  # Deny to add peers to task if file not in piecefield\n        return super(WorkerManagerPlugin, self).taskAddPeer(task, peer)\n\n\n@PluginManager.registerTo(\"FileRequest\")\nclass FileRequestPlugin(object):\n    def isReadable(self, site, inner_path, file, pos):\n        # Peek into file\n        if file.read(10) == b\"\\0\" * 10:\n            # Looks empty, but makes sures we don't have that piece\n            file_info = site.content_manager.getFileInfo(inner_path)\n            if \"piece_size\" in file_info:\n                piece_i = int(pos / file_info[\"piece_size\"])\n                if not site.storage.piecefields[file_info[\"sha512\"]][piece_i]:\n                    return False\n        # Seek back to position we want to read\n        file.seek(pos)\n        return super(FileRequestPlugin, self).isReadable(site, inner_path, file, pos)\n\n    def actionGetPiecefields(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            return False\n\n        # Add peer to site if not added before\n        peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True)\n        if not peer.connection:  # Just added\n            peer.connect(self.connection)  # Assign current connection to peer\n\n        piecefields_packed = {sha512: piecefield.pack() for sha512, piecefield in site.storage.piecefields.items()}\n        self.response({\"piecefields_packed\": piecefields_packed})\n\n    def actionSetPiecefields(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n\n        # Add or get peer\n        peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, connection=self.connection)\n        if not peer.connection:\n            peer.connect(self.connection)\n\n        peer.piecefields = collections.defaultdict(BigfilePiecefieldPacked)\n        for sha512, piecefield_packed in params[\"piecefields_packed\"].items():\n            peer.piecefields[sha512].unpack(piecefield_packed)\n        site.settings[\"has_bigfile\"] = True\n\n        self.response({\"ok\": \"Updated\"})\n\n\n@PluginManager.registerTo(\"Peer\")\nclass PeerPlugin(object):\n    def __getattr__(self, key):\n        if key == \"piecefields\":\n            self.piecefields = collections.defaultdict(BigfilePiecefieldPacked)\n            return self.piecefields\n        elif key == \"time_piecefields_updated\":\n            self.time_piecefields_updated = None\n            return self.time_piecefields_updated\n        else:\n            return super(PeerPlugin, self).__getattr__(key)\n\n    @util.Noparallel(ignore_args=True)\n    def updatePiecefields(self, force=False):\n        if self.connection and self.connection.handshake.get(\"rev\", 0) < 2190:\n            return False  # Not supported\n\n        # Don't update piecefield again in 1 min\n        if self.time_piecefields_updated and time.time() - self.time_piecefields_updated < 60 and not force:\n            return False\n\n        self.time_piecefields_updated = time.time()\n        res = self.request(\"getPiecefields\", {\"site\": self.site.address})\n        if not res or \"error\" in res:\n            return False\n\n        self.piecefields = collections.defaultdict(BigfilePiecefieldPacked)\n        try:\n            for sha512, piecefield_packed in res[\"piecefields_packed\"].items():\n                self.piecefields[sha512].unpack(piecefield_packed)\n        except Exception as err:\n            self.log(\"Invalid updatePiecefields response: %s\" % Debug.formatException(err))\n\n        return self.piecefields\n\n    def sendMyHashfield(self, *args, **kwargs):\n        return super(PeerPlugin, self).sendMyHashfield(*args, **kwargs)\n\n    def updateHashfield(self, *args, **kwargs):\n        if self.site.settings.get(\"has_bigfile\"):\n            thread = gevent.spawn(self.updatePiecefields, *args, **kwargs)\n            back = super(PeerPlugin, self).updateHashfield(*args, **kwargs)\n            thread.join()\n            return back\n        else:\n            return super(PeerPlugin, self).updateHashfield(*args, **kwargs)\n\n    def getFile(self, site, inner_path, *args, **kwargs):\n        if \"|\" in inner_path:\n            inner_path, file_range = inner_path.split(\"|\")\n            pos_from, pos_to = map(int, file_range.split(\"-\"))\n            kwargs[\"pos_from\"] = pos_from\n            kwargs[\"pos_to\"] = pos_to\n        return super(PeerPlugin, self).getFile(site, inner_path, *args, **kwargs)\n\n\n@PluginManager.registerTo(\"Site\")\nclass SitePlugin(object):\n    def isFileDownloadAllowed(self, inner_path, file_info):\n        if \"piecemap\" in file_info:\n            file_size_mb = file_info[\"size\"] / 1024 / 1024\n            if config.bigfile_size_limit and file_size_mb > config.bigfile_size_limit:\n                self.log.debug(\n                    \"Bigfile size %s too large: %sMB > %sMB, skipping...\" %\n                    (inner_path, file_size_mb, config.bigfile_size_limit)\n                )\n                return False\n\n            file_info = file_info.copy()\n            file_info[\"size\"] = file_info[\"piece_size\"]\n        return super(SitePlugin, self).isFileDownloadAllowed(inner_path, file_info)\n\n    def getSettingsCache(self):\n        back = super(SitePlugin, self).getSettingsCache()\n        if self.storage.piecefields:\n            back[\"piecefields\"] = {sha512: base64.b64encode(piecefield.pack()).decode(\"utf8\") for sha512, piecefield in self.storage.piecefields.items()}\n        return back\n\n    def needFile(self, inner_path, *args, **kwargs):\n        if inner_path.endswith(\"|all\"):\n            @util.Pooled(20)\n            def pooledNeedBigfile(inner_path, *args, **kwargs):\n                if inner_path not in self.bad_files:\n                    self.log.debug(\"Cancelled piece, skipping %s\" % inner_path)\n                    return False\n                return self.needFile(inner_path, *args, **kwargs)\n\n            inner_path = inner_path.replace(\"|all\", \"\")\n            file_info = self.needFileInfo(inner_path)\n\n            # Use default function to download non-optional file\n            if \"piece_size\" not in file_info:\n                return super(SitePlugin, self).needFile(inner_path, *args, **kwargs)\n\n            file_size = file_info[\"size\"]\n            piece_size = file_info[\"piece_size\"]\n\n            piece_num = int(math.ceil(float(file_size) / piece_size))\n\n            file_threads = []\n\n            piecefield = self.storage.piecefields.get(file_info[\"sha512\"])\n\n            for piece_i in range(piece_num):\n                piece_from = piece_i * piece_size\n                piece_to = min(file_size, piece_from + piece_size)\n                if not piecefield or not piecefield[piece_i]:\n                    inner_path_piece = \"%s|%s-%s\" % (inner_path, piece_from, piece_to)\n                    self.bad_files[inner_path_piece] = self.bad_files.get(inner_path_piece, 1)\n                    res = pooledNeedBigfile(inner_path_piece, blocking=False)\n                    if res is not True and res is not False:\n                        file_threads.append(res)\n            gevent.joinall(file_threads)\n        else:\n            return super(SitePlugin, self).needFile(inner_path, *args, **kwargs)\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"Bigfile plugin\")\n        group.add_argument('--autodownload_bigfile_size_limit', help='Also download bigfiles smaller than this limit if help distribute option is checked', default=10, metavar=\"MB\", type=int)\n        group.add_argument('--bigfile_size_limit', help='Maximum size of downloaded big files', default=False, metavar=\"MB\", type=int)\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/Bigfile/Test/TestBigfile.py",
    "content": "import time\nimport io\nimport binascii\n\nimport pytest\nimport mock\n\nfrom Connection import ConnectionServer\nfrom Content.ContentManager import VerifyError\nfrom File import FileServer\nfrom File import FileRequest\nfrom Worker import WorkerManager\nfrom Peer import Peer\nfrom Bigfile import BigfilePiecefield, BigfilePiecefieldPacked\nfrom Test import Spy\nfrom util import Msgpack\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestBigfile:\n    privatekey = \"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\"\n    piece_size = 1024 * 1024\n\n    def createBigfile(self, site, inner_path=\"data/optional.any.iso\", pieces=10):\n        f = site.storage.open(inner_path, \"w\")\n        for i in range(pieces * 100):\n            f.write((\"Test%s\" % i).ljust(10, \"-\") * 1000)\n        f.close()\n        assert site.content_manager.sign(\"content.json\", self.privatekey)\n        return inner_path\n\n    def testPiecemapCreate(self, site):\n        inner_path = self.createBigfile(site)\n        content = site.storage.loadJson(\"content.json\")\n        assert \"data/optional.any.iso\" in content[\"files_optional\"]\n        file_node = content[\"files_optional\"][inner_path]\n        assert file_node[\"size\"] == 10 * 1000 * 1000\n        assert file_node[\"sha512\"] == \"47a72cde3be80b4a829e7674f72b7c6878cf6a70b0c58c6aa6c17d7e9948daf6\"\n        assert file_node[\"piecemap\"] == inner_path + \".piecemap.msgpack\"\n\n        piecemap = Msgpack.unpack(site.storage.open(file_node[\"piecemap\"], \"rb\").read())[\"optional.any.iso\"]\n        assert len(piecemap[\"sha512_pieces\"]) == 10\n        assert piecemap[\"sha512_pieces\"][0] != piecemap[\"sha512_pieces\"][1]\n        assert binascii.hexlify(piecemap[\"sha512_pieces\"][0]) == b\"a73abad9992b3d0b672d0c2a292046695d31bebdcb1e150c8410bbe7c972eff3\"\n\n    def testVerifyPiece(self, site):\n        inner_path = self.createBigfile(site)\n\n        # Verify all 10 piece\n        f = site.storage.open(inner_path, \"rb\")\n        for i in range(10):\n            piece = io.BytesIO(f.read(1024 * 1024))\n            piece.seek(0)\n            site.content_manager.verifyPiece(inner_path, i * 1024 * 1024, piece)\n        f.close()\n\n        # Try to verify piece 0 with piece 1 hash\n        with pytest.raises(VerifyError) as err:\n            i = 1\n            f = site.storage.open(inner_path, \"rb\")\n            piece = io.BytesIO(f.read(1024 * 1024))\n            f.close()\n            site.content_manager.verifyPiece(inner_path, i * 1024 * 1024, piece)\n        assert \"Invalid hash\" in str(err.value)\n\n    def testSparseFile(self, site):\n        inner_path = \"sparsefile\"\n\n        # Create a 100MB sparse file\n        site.storage.createSparseFile(inner_path, 100 * 1024 * 1024)\n\n        # Write to file beginning\n        s = time.time()\n        f = site.storage.write(\"%s|%s-%s\" % (inner_path, 0, 1024 * 1024), b\"hellostart\" * 1024)\n        time_write_start = time.time() - s\n\n        # Write to file end\n        s = time.time()\n        f = site.storage.write(\"%s|%s-%s\" % (inner_path, 99 * 1024 * 1024, 99 * 1024 * 1024 + 1024 * 1024), b\"helloend\" * 1024)\n        time_write_end = time.time() - s\n\n        # Verify writes\n        f = site.storage.open(inner_path)\n        assert f.read(10) == b\"hellostart\"\n        f.seek(99 * 1024 * 1024)\n        assert f.read(8) == b\"helloend\"\n        f.close()\n\n        site.storage.delete(inner_path)\n\n        # Writing to end shold not take much longer, than writing to start\n        assert time_write_end <= max(0.1, time_write_start * 1.1)\n\n    def testRangedFileRequest(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        file_server.sites[site.address] = site\n        client = FileServer(file_server.ip, 1545)\n        client.sites[site_temp.address] = site_temp\n        site_temp.connection_server = client\n        connection = client.getConnection(file_server.ip, 1544)\n\n        # Add file_server as peer to client\n        peer_file_server = site_temp.addPeer(file_server.ip, 1544)\n\n        buff = peer_file_server.getFile(site_temp.address, \"%s|%s-%s\" % (inner_path, 5 * 1024 * 1024, 6 * 1024 * 1024))\n\n        assert len(buff.getvalue()) == 1 * 1024 * 1024  # Correct block size\n        assert buff.getvalue().startswith(b\"Test524\")  # Correct data\n        buff.seek(0)\n        assert site.content_manager.verifyPiece(inner_path, 5 * 1024 * 1024, buff)  # Correct hash\n\n        connection.close()\n        client.stop()\n\n    def testRangedFileDownload(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Make sure the file and the piecemap in the optional hashfield\n        file_info = site.content_manager.getFileInfo(inner_path)\n        assert site.content_manager.hashfield.hasHash(file_info[\"sha512\"])\n\n        piecemap_hash = site.content_manager.getFileInfo(file_info[\"piecemap\"])[\"sha512\"]\n        assert site.content_manager.hashfield.hasHash(piecemap_hash)\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        peer_client = site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        bad_files = site_temp.storage.verifyFiles(quick_check=True)[\"bad_files\"]\n        assert not bad_files\n\n        # client_piecefield = peer_client.piecefields[file_info[\"sha512\"]].tostring()\n        # assert client_piecefield == \"1\" * 10\n\n        # Download 5. and 10. block\n\n        site_temp.needFile(\"%s|%s-%s\" % (inner_path, 5 * 1024 * 1024, 6 * 1024 * 1024))\n        site_temp.needFile(\"%s|%s-%s\" % (inner_path, 9 * 1024 * 1024, 10 * 1024 * 1024))\n\n        # Verify 0. block not downloaded\n        f = site_temp.storage.open(inner_path)\n        assert f.read(10) == b\"\\0\" * 10\n        # Verify 5. and 10. block downloaded\n        f.seek(5 * 1024 * 1024)\n        assert f.read(7) == b\"Test524\"\n        f.seek(9 * 1024 * 1024)\n        assert f.read(7) == b\"943---T\"\n\n        # Verify hashfield\n        assert set(site_temp.content_manager.hashfield) == set([18343, 43727])  # 18343: data/optional.any.iso, 43727: data/optional.any.iso.hashmap.msgpack\n\n    def testOpenBigfile(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        # Open virtual file\n        assert not site_temp.storage.isFile(inner_path)\n\n        with site_temp.storage.openBigfile(inner_path) as f:\n            with Spy.Spy(FileRequest, \"route\") as requests:\n                f.seek(5 * 1024 * 1024)\n                assert f.read(7) == b\"Test524\"\n\n                f.seek(9 * 1024 * 1024)\n                assert f.read(7) == b\"943---T\"\n\n            assert len(requests) == 4  # 1x peicemap + 1x getpiecefield + 2x for pieces\n\n            assert set(site_temp.content_manager.hashfield) == set([18343, 43727])\n\n            assert site_temp.storage.piecefields[f.sha512].tostring() == \"0000010001\"\n            assert f.sha512 in site_temp.getSettingsCache()[\"piecefields\"]\n\n            # Test requesting already downloaded\n            with Spy.Spy(FileRequest, \"route\") as requests:\n                f.seek(5 * 1024 * 1024)\n                assert f.read(7) == b\"Test524\"\n\n            assert len(requests) == 0\n\n            # Test requesting multi-block overflow reads\n            with Spy.Spy(FileRequest, \"route\") as requests:\n                f.seek(5 * 1024 * 1024)  # We already have this block\n                data = f.read(1024 * 1024 * 3)  # Our read overflow to 6. and 7. block\n                assert data.startswith(b\"Test524\")\n                assert data.endswith(b\"Test838-\")\n                assert b\"\\0\" not in data  # No null bytes allowed\n\n            assert len(requests) == 2  # Two block download\n\n            # Test out of range request\n            f.seek(5 * 1024 * 1024)\n            data = f.read(1024 * 1024 * 30)\n            assert len(data) == 10 * 1000 * 1000 - (5 * 1024 * 1024)\n\n            f.seek(30 * 1024 * 1024)\n            data = f.read(1024 * 1024 * 30)\n            assert len(data) == 0\n\n    @pytest.mark.parametrize(\"piecefield_obj\", [BigfilePiecefield, BigfilePiecefieldPacked])\n    def testPiecefield(self, piecefield_obj, site):\n        testdatas = [\n            b\"\\x01\" * 100 + b\"\\x00\" * 900 + b\"\\x01\" * 4000 + b\"\\x00\" * 4999 + b\"\\x01\",\n            b\"\\x00\\x01\\x00\\x01\\x00\\x01\" * 10 + b\"\\x00\\x01\" * 90 + b\"\\x01\\x00\" * 400 + b\"\\x00\" * 4999,\n            b\"\\x01\" * 10000,\n            b\"\\x00\" * 10000\n        ]\n        for testdata in testdatas:\n            piecefield = piecefield_obj()\n\n            piecefield.frombytes(testdata)\n            assert piecefield.tobytes() == testdata\n            assert piecefield[0] == testdata[0]\n            assert piecefield[100] == testdata[100]\n            assert piecefield[1000] == testdata[1000]\n            assert piecefield[len(testdata) - 1] == testdata[len(testdata) - 1]\n\n            packed = piecefield.pack()\n            piecefield_new = piecefield_obj()\n            piecefield_new.unpack(packed)\n            assert piecefield.tobytes() == piecefield_new.tobytes()\n            assert piecefield_new.tobytes() == testdata\n\n    def testFileGet(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        site_temp.connection_server = FileServer(file_server.ip, 1545)\n        site_temp.connection_server.sites[site_temp.address] = site_temp\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        # Download second block\n        with site_temp.storage.openBigfile(inner_path) as f:\n            f.seek(1024 * 1024)\n            assert f.read(1024)[0:1] != b\"\\0\"\n\n        # Make sure first block not download\n        with site_temp.storage.open(inner_path) as f:\n            assert f.read(1024)[0:1] == b\"\\0\"\n\n        peer2 = site.addPeer(file_server.ip, 1545, return_peer=True)\n\n        # Should drop error on first block request\n        assert not peer2.getFile(site.address, \"%s|0-%s\" % (inner_path, 1024 * 1024 * 1))\n\n        # Should not drop error for second block request\n        assert peer2.getFile(site.address, \"%s|%s-%s\" % (inner_path, 1024 * 1024 * 1, 1024 * 1024 * 2))\n\n    def benchmarkPeerMemory(self, site, file_server):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        import psutil, os\n        meminfo = psutil.Process(os.getpid()).memory_info\n\n        mem_s = meminfo()[0]\n        s = time.time()\n        for i in range(25000):\n            site.addPeer(file_server.ip, i)\n        print(\"%.3fs MEM: + %sKB\" % (time.time() - s, (meminfo()[0] - mem_s) / 1024))  # 0.082s MEM: + 6800KB\n        print(list(site.peers.values())[0].piecefields)\n\n    def testUpdatePiecefield(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        server1 = file_server\n        server1.sites[site.address] = site\n        server2 = FileServer(file_server.ip, 1545)\n        server2.sites[site_temp.address] = site_temp\n        site_temp.connection_server = server2\n\n        # Add file_server as peer to client\n        server2_peer1 = site_temp.addPeer(file_server.ip, 1544)\n\n        # Testing piecefield sync\n        assert len(server2_peer1.piecefields) == 0\n        assert server2_peer1.updatePiecefields()  # Query piecefields from peer\n        assert len(server2_peer1.piecefields) > 0\n\n    def testWorkerManagerPiecefieldDeny(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        server1 = file_server\n        server1.sites[site.address] = site\n        server2 = FileServer(file_server.ip, 1545)\n        server2.sites[site_temp.address] = site_temp\n        site_temp.connection_server = server2\n\n        # Add file_server as peer to client\n        server2_peer1 = site_temp.addPeer(file_server.ip, 1544)  # Working\n\n        site_temp.downloadContent(\"content.json\", download_files=False)\n        site_temp.needFile(\"data/optional.any.iso.piecemap.msgpack\")\n\n        # Add fake peers with optional files downloaded\n        for i in range(5):\n            fake_peer = site_temp.addPeer(\"127.0.1.%s\" % i, 1544)\n            fake_peer.hashfield = site.content_manager.hashfield\n            fake_peer.has_hashfield = True\n\n        with Spy.Spy(WorkerManager, \"addWorker\") as requests:\n            site_temp.needFile(\"%s|%s-%s\" % (inner_path, 5 * 1024 * 1024, 6 * 1024 * 1024))\n            site_temp.needFile(\"%s|%s-%s\" % (inner_path, 6 * 1024 * 1024, 7 * 1024 * 1024))\n\n        # It should only request parts from peer1 as the other peers does not have the requested parts in piecefields\n        assert len([request[1] for request in requests if request[1] != server2_peer1]) == 0\n\n    def testWorkerManagerPiecefieldDownload(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        server1 = file_server\n        server1.sites[site.address] = site\n        server2 = FileServer(file_server.ip, 1545)\n        server2.sites[site_temp.address] = site_temp\n        site_temp.connection_server = server2\n        sha512 = site.content_manager.getFileInfo(inner_path)[\"sha512\"]\n\n        # Create 10 fake peer for each piece\n        for i in range(10):\n            peer = Peer(file_server.ip, 1544, site_temp, server2)\n            peer.piecefields[sha512][i] = b\"\\x01\"\n            peer.updateHashfield = mock.MagicMock(return_value=False)\n            peer.updatePiecefields = mock.MagicMock(return_value=False)\n            peer.findHashIds = mock.MagicMock(return_value={\"nope\": []})\n            peer.hashfield = site.content_manager.hashfield\n            peer.has_hashfield = True\n            peer.key = \"Peer:%s\" % i\n            site_temp.peers[\"Peer:%s\" % i] = peer\n\n        site_temp.downloadContent(\"content.json\", download_files=False)\n        site_temp.needFile(\"data/optional.any.iso.piecemap.msgpack\")\n\n        with Spy.Spy(Peer, \"getFile\") as requests:\n            for i in range(10):\n                site_temp.needFile(\"%s|%s-%s\" % (inner_path, i * 1024 * 1024, (i + 1) * 1024 * 1024))\n\n        assert len(requests) == 10\n        for i in range(10):\n            assert requests[i][0] == site_temp.peers[\"Peer:%s\" % i]  # Every part should be requested from piece owner peer\n\n    def testDownloadStats(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        # Open virtual file\n        assert not site_temp.storage.isFile(inner_path)\n\n        # Check size before downloads\n        assert site_temp.settings[\"size\"] < 10 * 1024 * 1024\n        assert site_temp.settings[\"optional_downloaded\"] == 0\n        size_piecemap = site_temp.content_manager.getFileInfo(inner_path + \".piecemap.msgpack\")[\"size\"]\n        size_bigfile = site_temp.content_manager.getFileInfo(inner_path)[\"size\"]\n\n        with site_temp.storage.openBigfile(inner_path) as f:\n            assert b\"\\0\" not in f.read(1024)\n            assert site_temp.settings[\"optional_downloaded\"] == size_piecemap + size_bigfile\n\n        with site_temp.storage.openBigfile(inner_path) as f:\n            # Don't count twice\n            assert b\"\\0\" not in f.read(1024)\n            assert site_temp.settings[\"optional_downloaded\"] == size_piecemap + size_bigfile\n\n            # Add second block\n            assert b\"\\0\" not in f.read(1024 * 1024)\n            assert site_temp.settings[\"optional_downloaded\"] == size_piecemap + size_bigfile\n\n    def testPrebuffer(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        # Open virtual file\n        assert not site_temp.storage.isFile(inner_path)\n\n        with site_temp.storage.openBigfile(inner_path, prebuffer=1024 * 1024 * 2) as f:\n            with Spy.Spy(FileRequest, \"route\") as requests:\n                f.seek(5 * 1024 * 1024)\n                assert f.read(7) == b\"Test524\"\n            # assert len(requests) == 3  # 1x piecemap + 1x getpiecefield + 1x for pieces\n            assert len([task for task in site_temp.worker_manager.tasks if task[\"inner_path\"].startswith(inner_path)]) == 2\n\n            time.sleep(0.5)  # Wait prebuffer download\n\n            sha512 = site.content_manager.getFileInfo(inner_path)[\"sha512\"]\n            assert site_temp.storage.piecefields[sha512].tostring() == \"0000011100\"\n\n            # No prebuffer beyond end of the file\n            f.seek(9 * 1024 * 1024)\n            assert b\"\\0\" not in f.read(7)\n\n            assert len([task for task in site_temp.worker_manager.tasks if task[\"inner_path\"].startswith(inner_path)]) == 0\n\n    def testDownloadAllPieces(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        # Open virtual file\n        assert not site_temp.storage.isFile(inner_path)\n\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site_temp.needFile(\"%s|all\" % inner_path)\n\n        assert len(requests) == 12  # piecemap.msgpack, getPiecefields, 10 x piece\n\n        # Don't re-download already got pieces\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site_temp.needFile(\"%s|all\" % inner_path)\n\n        assert len(requests) == 0\n\n    def testFileSize(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        # Open virtual file\n        assert not site_temp.storage.isFile(inner_path)\n\n        # Download first block\n        site_temp.needFile(\"%s|%s-%s\" % (inner_path, 0 * 1024 * 1024, 1 * 1024 * 1024))\n        assert site_temp.storage.getSize(inner_path) < 1000 * 1000 * 10  # Size on the disk should be smaller than the real size\n\n        site_temp.needFile(\"%s|%s-%s\" % (inner_path, 9 * 1024 * 1024, 10 * 1024 * 1024))\n        assert site_temp.storage.getSize(inner_path) == site.storage.getSize(inner_path)\n\n    def testFileRename(self, file_server, site, site_temp):\n        inner_path = self.createBigfile(site)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        site_temp.connection_server = FileServer(file_server.ip, 1545)\n        site_temp.connection_server.sites[site_temp.address] = site_temp\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site_temp.needFile(\"%s|%s-%s\" % (inner_path, 0, 1 * self.piece_size))\n\n        assert len([req for req in requests if req[1] == \"streamFile\"]) == 2  # 1 piece + piecemap\n\n        # Rename the file\n        inner_path_new = inner_path.replace(\".iso\", \"-new.iso\")\n        site.storage.rename(inner_path, inner_path_new)\n        site.storage.delete(\"data/optional.any.iso.piecemap.msgpack\")\n        assert site.content_manager.sign(\"content.json\", self.privatekey, remove_missing_optional=True)\n\n        files_optional = site.content_manager.contents[\"content.json\"][\"files_optional\"].keys()\n\n        assert \"data/optional.any-new.iso.piecemap.msgpack\" in files_optional\n        assert \"data/optional.any.iso.piecemap.msgpack\" not in files_optional\n        assert \"data/optional.any.iso\" not in files_optional\n\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.publish()\n            time.sleep(0.1)\n            site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)  # Wait for download\n\n            assert len([req[1] for req in requests if req[1] == \"streamFile\"]) == 0\n\n            with site_temp.storage.openBigfile(inner_path_new, prebuffer=0) as f:\n                f.read(1024)\n\n                # First piece already downloaded\n                assert [req for req in requests if req[1] == \"streamFile\"] == []\n\n                # Second piece needs to be downloaded + changed piecemap\n                f.seek(self.piece_size)\n                f.read(1024)\n                assert [req[3][\"inner_path\"] for req in requests if req[1] == \"streamFile\"] == [inner_path_new + \".piecemap.msgpack\", inner_path_new]\n\n    @pytest.mark.parametrize(\"size\", [1024 * 3, 1024 * 1024 * 3, 1024 * 1024 * 30])\n    def testNullFileRead(self, file_server, site, site_temp, size):\n        inner_path = \"data/optional.iso\"\n\n        f = site.storage.open(inner_path, \"w\")\n        f.write(\"\\0\" * size)\n        f.close()\n        assert site.content_manager.sign(\"content.json\", self.privatekey)\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        site_temp.connection_server = FileServer(file_server.ip, 1545)\n        site_temp.connection_server.sites[site_temp.address] = site_temp\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        site_temp.download(blind_includes=True, retry_bad_files=False).join(timeout=10)\n\n        if \"piecemap\" in site.content_manager.getFileInfo(inner_path):  # Bigfile\n            site_temp.needFile(inner_path + \"|all\")\n        else:\n            site_temp.needFile(inner_path)\n\n\n        assert site_temp.storage.getSize(inner_path) == size\n"
  },
  {
    "path": "plugins/Bigfile/Test/conftest.py",
    "content": "from src.Test.conftest import *\n"
  },
  {
    "path": "plugins/Bigfile/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/Bigfile/__init__.py",
    "content": "from . import BigfilePlugin\nfrom .BigfilePiecefield import BigfilePiecefield, BigfilePiecefieldPacked"
  },
  {
    "path": "plugins/Chart/ChartCollector.py",
    "content": "import time\nimport sys\nimport collections\nimport itertools\nimport logging\n\nimport gevent\nfrom util import helper\nfrom Config import config\n\n\nclass ChartCollector(object):\n    def __init__(self, db):\n        self.db = db\n        if config.action == \"main\":\n            gevent.spawn_later(60 * 3, self.collector)\n        self.log = logging.getLogger(\"ChartCollector\")\n        self.last_values = collections.defaultdict(dict)\n\n    def setInitialLastValues(self, sites):\n        # Recover last value of site bytes/sent\n        for site in sites:\n            self.last_values[\"site:\" + site.address][\"site_bytes_recv\"] = site.settings.get(\"bytes_recv\", 0)\n            self.last_values[\"site:\" + site.address][\"site_bytes_sent\"] = site.settings.get(\"bytes_sent\", 0)\n\n    def getCollectors(self):\n        collectors = {}\n        import main\n        file_server = main.file_server\n        sites = file_server.sites\n        if not sites:\n            return collectors\n        content_db = list(sites.values())[0].content_manager.contents.db\n\n        # Connection stats\n        collectors[\"connection\"] = lambda: len(file_server.connections)\n        collectors[\"connection_in\"] = (\n            lambda: len([1 for connection in file_server.connections if connection.type == \"in\"])\n        )\n        collectors[\"connection_onion\"] = (\n            lambda: len([1 for connection in file_server.connections if connection.ip.endswith(\".onion\")])\n        )\n        collectors[\"connection_ping_avg\"] = (\n            lambda: round(1000 * helper.avg(\n                [connection.last_ping_delay for connection in file_server.connections if connection.last_ping_delay]\n            ))\n        )\n        collectors[\"connection_ping_min\"] = (\n            lambda: round(1000 * min(\n                [connection.last_ping_delay for connection in file_server.connections if connection.last_ping_delay]\n            ))\n        )\n        collectors[\"connection_rev_avg\"] = (\n            lambda: helper.avg(\n                [connection.handshake[\"rev\"] for connection in file_server.connections if connection.handshake]\n            )\n        )\n\n        # Request stats\n        collectors[\"file_bytes_recv|change\"] = lambda: file_server.bytes_recv\n        collectors[\"file_bytes_sent|change\"] = lambda: file_server.bytes_sent\n        collectors[\"request_num_recv|change\"] = lambda: file_server.num_recv\n        collectors[\"request_num_sent|change\"] = lambda: file_server.num_sent\n\n        # Limit\n        collectors[\"optional_limit\"] = lambda: content_db.getOptionalLimitBytes()\n        collectors[\"optional_used\"] = lambda: content_db.getOptionalUsedBytes()\n        collectors[\"optional_downloaded\"] = lambda: sum([site.settings.get(\"optional_downloaded\", 0) for site in sites.values()])\n\n        # Peers\n        collectors[\"peer\"] = lambda peers: len(peers)\n        collectors[\"peer_onion\"] = lambda peers: len([True for peer in peers if \".onion\" in peer])\n\n        # Size\n        collectors[\"size\"] = lambda: sum([site.settings.get(\"size\", 0) for site in sites.values()])\n        collectors[\"size_optional\"] = lambda: sum([site.settings.get(\"size_optional\", 0) for site in sites.values()])\n        collectors[\"content\"] = lambda: sum([len(site.content_manager.contents) for site in sites.values()])\n\n        return collectors\n\n    def getSiteCollectors(self):\n        site_collectors = {}\n\n        # Size\n        site_collectors[\"site_size\"] = lambda site: site.settings.get(\"size\", 0)\n        site_collectors[\"site_size_optional\"] = lambda site: site.settings.get(\"size_optional\", 0)\n        site_collectors[\"site_optional_downloaded\"] = lambda site: site.settings.get(\"optional_downloaded\", 0)\n        site_collectors[\"site_content\"] = lambda site: len(site.content_manager.contents)\n\n        # Data transfer\n        site_collectors[\"site_bytes_recv|change\"] = lambda site: site.settings.get(\"bytes_recv\", 0)\n        site_collectors[\"site_bytes_sent|change\"] = lambda site: site.settings.get(\"bytes_sent\", 0)\n\n        # Peers\n        site_collectors[\"site_peer\"] = lambda site: len(site.peers)\n        site_collectors[\"site_peer_onion\"] = lambda site: len(\n            [True for peer in site.peers.values() if peer.ip.endswith(\".onion\")]\n        )\n        site_collectors[\"site_peer_connected\"] = lambda site: len([True for peer in site.peers.values() if peer.connection])\n\n        return site_collectors\n\n    def getUniquePeers(self):\n        import main\n        sites = main.file_server.sites\n        return set(itertools.chain.from_iterable(\n            [site.peers.keys() for site in sites.values()]\n        ))\n\n    def collectDatas(self, collectors, last_values, site=None):\n        if site is None:\n            peers = self.getUniquePeers()\n        datas = {}\n        for key, collector in collectors.items():\n            try:\n                if site:\n                    value = collector(site)\n                elif key.startswith(\"peer\"):\n                    value = collector(peers)\n                else:\n                    value = collector()\n            except ValueError:\n                value = None\n            except Exception as err:\n                self.log.info(\"Collector %s error: %s\" % (key, err))\n                value = None\n\n            if \"|change\" in key:  # Store changes relative to last value\n                key = key.replace(\"|change\", \"\")\n                last_value = last_values.get(key, 0)\n                last_values[key] = value\n                value = value - last_value\n\n            if value is None:\n                datas[key] = None\n            else:\n                datas[key] = round(value, 3)\n        return datas\n\n    def collectGlobal(self, collectors, last_values):\n        now = int(time.time())\n        s = time.time()\n        datas = self.collectDatas(collectors, last_values[\"global\"])\n        values = []\n        for key, value in datas.items():\n            values.append((self.db.getTypeId(key), value, now))\n        self.log.debug(\"Global collectors done in %.3fs\" % (time.time() - s))\n\n        s = time.time()\n        cur = self.db.getCursor()\n        cur.executemany(\"INSERT INTO data (type_id, value, date_added) VALUES (?, ?, ?)\", values)\n        self.log.debug(\"Global collectors inserted in %.3fs\" % (time.time() - s))\n\n    def collectSites(self, sites, collectors, last_values):\n        now = int(time.time())\n        s = time.time()\n        values = []\n        for address, site in list(sites.items()):\n            site_datas = self.collectDatas(collectors, last_values[\"site:%s\" % address], site)\n            for key, value in site_datas.items():\n                values.append((self.db.getTypeId(key), self.db.getSiteId(address), value, now))\n            time.sleep(0.001)\n        self.log.debug(\"Site collections done in %.3fs\" % (time.time() - s))\n\n        s = time.time()\n        cur = self.db.getCursor()\n        cur.executemany(\"INSERT INTO data (type_id, site_id, value, date_added) VALUES (?, ?, ?, ?)\", values)\n        self.log.debug(\"Site collectors inserted in %.3fs\" % (time.time() - s))\n\n    def collector(self):\n        collectors = self.getCollectors()\n        site_collectors = self.getSiteCollectors()\n        import main\n        sites = main.file_server.sites\n        i = 0\n        while 1:\n            self.collectGlobal(collectors, self.last_values)\n            if i % 12 == 0:  # Only collect sites data every hour\n                self.collectSites(sites, site_collectors, self.last_values)\n            time.sleep(60 * 5)\n            i += 1\n"
  },
  {
    "path": "plugins/Chart/ChartDb.py",
    "content": "from Config import config\nfrom Db.Db import Db\nimport time\n\n\nclass ChartDb(Db):\n    def __init__(self):\n        self.version = 2\n        super(ChartDb, self).__init__(self.getSchema(), \"%s/chart.db\" % config.data_dir)\n        self.foreign_keys = True\n        self.checkTables()\n        self.sites = self.loadSites()\n        self.types = self.loadTypes()\n\n    def getSchema(self):\n        schema = {}\n        schema[\"db_name\"] = \"Chart\"\n        schema[\"tables\"] = {}\n        schema[\"tables\"][\"data\"] = {\n            \"cols\": [\n                [\"data_id\", \"INTEGER PRIMARY KEY ASC AUTOINCREMENT NOT NULL UNIQUE\"],\n                [\"type_id\", \"INTEGER NOT NULL\"],\n                [\"site_id\", \"INTEGER\"],\n                [\"value\", \"INTEGER\"],\n                [\"date_added\", \"DATETIME DEFAULT (CURRENT_TIMESTAMP)\"]\n            ],\n            \"indexes\": [\n                \"CREATE INDEX site_id ON data (site_id)\",\n                \"CREATE INDEX date_added ON data (date_added)\"\n            ],\n            \"schema_changed\": 2\n        }\n        schema[\"tables\"][\"type\"] = {\n            \"cols\": [\n                [\"type_id\", \"INTEGER PRIMARY KEY NOT NULL UNIQUE\"],\n                [\"name\", \"TEXT\"]\n            ],\n            \"schema_changed\": 1\n        }\n        schema[\"tables\"][\"site\"] = {\n            \"cols\": [\n                [\"site_id\", \"INTEGER PRIMARY KEY NOT NULL UNIQUE\"],\n                [\"address\", \"TEXT\"]\n            ],\n            \"schema_changed\": 1\n        }\n        return schema\n\n    def getTypeId(self, name):\n        if name not in self.types:\n            res = self.execute(\"INSERT INTO type ?\", {\"name\": name})\n            self.types[name] = res.lastrowid\n\n        return self.types[name]\n\n    def getSiteId(self, address):\n        if address not in self.sites:\n            res = self.execute(\"INSERT INTO site ?\", {\"address\": address})\n            self.sites[address] = res.lastrowid\n\n        return self.sites[address]\n\n    def loadSites(self):\n        sites = {}\n        for row in self.execute(\"SELECT * FROM site\"):\n            sites[row[\"address\"]] = row[\"site_id\"]\n        return sites\n\n    def loadTypes(self):\n        types = {}\n        for row in self.execute(\"SELECT * FROM type\"):\n            types[row[\"name\"]] = row[\"type_id\"]\n        return types\n\n    def deleteSite(self, address):\n        if address in self.sites:\n            site_id = self.sites[address]\n            del self.sites[address]\n            self.execute(\"DELETE FROM site WHERE ?\", {\"site_id\": site_id})\n            self.execute(\"DELETE FROM data WHERE ?\", {\"site_id\": site_id})\n\n    def archive(self):\n        week_back = 1\n        while 1:\n            s = time.time()\n            date_added_from = time.time() - 60 * 60 * 24 * 7 * (week_back + 1)\n            date_added_to = date_added_from + 60 * 60 * 24 * 7\n            res = self.execute(\"\"\"\n                SELECT\n                 MAX(date_added) AS date_added,\n                 SUM(value) AS value,\n                 GROUP_CONCAT(data_id) AS data_ids,\n                 type_id,\n                 site_id,\n                 COUNT(*) AS num\n                FROM data\n                WHERE\n                 site_id IS NULL AND\n                 date_added > :date_added_from AND\n                 date_added < :date_added_to\n                GROUP BY strftime('%Y-%m-%d %H', date_added, 'unixepoch', 'localtime'), type_id\n            \"\"\", {\"date_added_from\": date_added_from, \"date_added_to\": date_added_to})\n\n            num_archived = 0\n            cur = self.getCursor()\n            for row in res:\n                if row[\"num\"] == 1:\n                    continue\n                cur.execute(\"INSERT INTO data ?\", {\n                    \"type_id\": row[\"type_id\"],\n                    \"site_id\": row[\"site_id\"],\n                    \"value\": row[\"value\"],\n                    \"date_added\": row[\"date_added\"]\n                })\n                cur.execute(\"DELETE FROM data WHERE data_id IN (%s)\" % row[\"data_ids\"])\n                num_archived += row[\"num\"]\n            self.log.debug(\"Archived %s data from %s weeks ago in %.3fs\" % (num_archived, week_back, time.time() - s))\n            week_back += 1\n            time.sleep(0.1)\n            if num_archived == 0:\n                break\n        # Only keep 6 month of global stats\n        self.execute(\n            \"DELETE FROM data WHERE site_id IS NULL AND date_added < :date_added_limit\",\n            {\"date_added_limit\": time.time() - 60 * 60 * 24 * 30 * 6 }\n        )\n        # Only keep 1 month of site stats\n        self.execute(\n            \"DELETE FROM data WHERE site_id IS NOT NULL AND date_added < :date_added_limit\",\n            {\"date_added_limit\": time.time() - 60 * 60 * 24 * 30 }\n        )\n        if week_back > 1:\n            self.execute(\"VACUUM\")\n"
  },
  {
    "path": "plugins/Chart/ChartPlugin.py",
    "content": "import time\nimport itertools\n\nimport gevent\n\nfrom Config import config\nfrom util import helper\nfrom util.Flag import flag\nfrom Plugin import PluginManager\nfrom .ChartDb import ChartDb\nfrom .ChartCollector import ChartCollector\n\nif \"db\" not in locals().keys():  # Share on reloads\n    db = ChartDb()\n    gevent.spawn_later(10 * 60, db.archive)\n    helper.timer(60 * 60 * 6, db.archive)\n    collector = ChartCollector(db)\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n    def load(self, *args, **kwargs):\n        back = super(SiteManagerPlugin, self).load(*args, **kwargs)\n        collector.setInitialLastValues(self.sites.values())\n        return back\n\n    def delete(self, address, *args, **kwargs):\n        db.deleteSite(address)\n        return super(SiteManagerPlugin, self).delete(address, *args, **kwargs)\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    @flag.admin\n    def actionChartDbQuery(self, to, query, params=None):\n        if config.debug or config.verbose:\n            s = time.time()\n        rows = []\n        try:\n            if not query.strip().upper().startswith(\"SELECT\"):\n                raise Exception(\"Only SELECT query supported\")\n            res = db.execute(query, params)\n        except Exception as err:  # Response the error to client\n            self.log.error(\"ChartDbQuery error: %s\" % err)\n            return {\"error\": str(err)}\n        # Convert result to dict\n        for row in res:\n            rows.append(dict(row))\n        if config.verbose and time.time() - s > 0.1:  # Log slow query\n            self.log.debug(\"Slow query: %s (%.3fs)\" % (query, time.time() - s))\n        return rows\n\n    @flag.admin\n    def actionChartGetPeerLocations(self, to):\n        peers = {}\n        for site in self.server.sites.values():\n            peers.update(site.peers)\n        peer_locations = self.getPeerLocations(peers)\n        return peer_locations\n"
  },
  {
    "path": "plugins/Chart/__init__.py",
    "content": "from . import ChartPlugin"
  },
  {
    "path": "plugins/Chart/plugin_info.json",
    "content": "{\n\t\"name\": \"Chart\",\n\t\"description\": \"Collect and provide stats of client information.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/ContentFilter/ContentFilterPlugin.py",
    "content": "import time\nimport re\nimport html\nimport os\n\nfrom Plugin import PluginManager\nfrom Translate import Translate\nfrom Config import config\nfrom util.Flag import flag\n\nfrom .ContentFilterStorage import ContentFilterStorage\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n    def load(self, *args, **kwargs):\n        global filter_storage\n        super(SiteManagerPlugin, self).load(*args, **kwargs)\n        filter_storage = ContentFilterStorage(site_manager=self)\n\n    def add(self, address, *args, **kwargs):\n        should_ignore_block = kwargs.get(\"ignore_block\") or kwargs.get(\"settings\")\n        if should_ignore_block:\n            block_details = None\n        elif filter_storage.isSiteblocked(address):\n            block_details = filter_storage.getSiteblockDetails(address)\n        else:\n            address_hashed = filter_storage.getSiteAddressHashed(address)\n            if filter_storage.isSiteblocked(address_hashed):\n                block_details = filter_storage.getSiteblockDetails(address_hashed)\n            else:\n                block_details = None\n\n        if block_details:\n            raise Exception(\"Site blocked: %s\" % html.escape(block_details.get(\"reason\", \"unknown reason\")))\n        else:\n            return super(SiteManagerPlugin, self).add(address, *args, **kwargs)\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    # Mute\n    def cbMuteAdd(self, to, auth_address, cert_user_id, reason):\n        filter_storage.file_content[\"mutes\"][auth_address] = {\n            \"cert_user_id\": cert_user_id, \"reason\": reason, \"source\": self.site.address, \"date_added\": time.time()\n        }\n        filter_storage.save()\n        filter_storage.changeDbs(auth_address, \"remove\")\n        self.response(to, \"ok\")\n\n    @flag.no_multiuser\n    def actionMuteAdd(self, to, auth_address, cert_user_id, reason):\n        if \"ADMIN\" in self.getPermissions(to):\n            self.cbMuteAdd(to, auth_address, cert_user_id, reason)\n        else:\n            self.cmd(\n                \"confirm\",\n                [_[\"Hide all content from <b>%s</b>?\"] % html.escape(cert_user_id), _[\"Mute\"]],\n                lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, reason)\n            )\n\n    @flag.no_multiuser\n    def cbMuteRemove(self, to, auth_address):\n        del filter_storage.file_content[\"mutes\"][auth_address]\n        filter_storage.save()\n        filter_storage.changeDbs(auth_address, \"load\")\n        self.response(to, \"ok\")\n\n    @flag.no_multiuser\n    def actionMuteRemove(self, to, auth_address):\n        if \"ADMIN\" in self.getPermissions(to):\n            self.cbMuteRemove(to, auth_address)\n        else:\n            cert_user_id = html.escape(filter_storage.file_content[\"mutes\"][auth_address][\"cert_user_id\"])\n            self.cmd(\n                \"confirm\",\n                [_[\"Unmute <b>%s</b>?\"] % cert_user_id, _[\"Unmute\"]],\n                lambda res: self.cbMuteRemove(to, auth_address)\n            )\n\n    @flag.admin\n    def actionMuteList(self, to):\n        self.response(to, filter_storage.file_content[\"mutes\"])\n\n    # Siteblock\n    @flag.no_multiuser\n    @flag.admin\n    def actionSiteblockIgnoreAddSite(self, to, site_address):\n        if site_address in filter_storage.site_manager.sites:\n            return {\"error\": \"Site already added\"}\n        else:\n            if filter_storage.site_manager.need(site_address, ignore_block=True):\n                return \"ok\"\n            else:\n                return {\"error\": \"Invalid address\"}\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionSiteblockAdd(self, to, site_address, reason=None):\n        filter_storage.file_content[\"siteblocks\"][site_address] = {\"date_added\": time.time(), \"reason\": reason}\n        filter_storage.save()\n        self.response(to, \"ok\")\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionSiteblockRemove(self, to, site_address):\n        del filter_storage.file_content[\"siteblocks\"][site_address]\n        filter_storage.save()\n        self.response(to, \"ok\")\n\n    @flag.admin\n    def actionSiteblockList(self, to):\n        self.response(to, filter_storage.file_content[\"siteblocks\"])\n\n    @flag.admin\n    def actionSiteblockGet(self, to, site_address):\n        if filter_storage.isSiteblocked(site_address):\n            res = filter_storage.getSiteblockDetails(site_address)\n        else:\n            site_address_hashed = filter_storage.getSiteAddressHashed(site_address)\n            if filter_storage.isSiteblocked(site_address_hashed):\n                res = filter_storage.getSiteblockDetails(site_address_hashed)\n            else:\n                res = {\"error\": \"Site block not found\"}\n        self.response(to, res)\n\n    # Include\n    @flag.no_multiuser\n    def actionFilterIncludeAdd(self, to, inner_path, description=None, address=None):\n        if address:\n            if \"ADMIN\" not in self.getPermissions(to):\n                return self.response(to, {\"error\": \"Forbidden: Only ADMIN sites can manage different site include\"})\n            site = self.server.sites[address]\n        else:\n            address = self.site.address\n            site = self.site\n\n        if \"ADMIN\" in self.getPermissions(to):\n            self.cbFilterIncludeAdd(to, True, address, inner_path, description)\n        else:\n            content = site.storage.loadJson(inner_path)\n            title = _[\"New shared global content filter: <b>%s</b> (%s sites, %s users)\"] % (\n                html.escape(inner_path), len(content.get(\"siteblocks\", {})), len(content.get(\"mutes\", {}))\n            )\n\n            self.cmd(\n                \"confirm\",\n                [title, \"Add\"],\n                lambda res: self.cbFilterIncludeAdd(to, res, address, inner_path, description)\n            )\n\n    def cbFilterIncludeAdd(self, to, res, address, inner_path, description):\n        if not res:\n            self.response(to, res)\n            return False\n\n        filter_storage.includeAdd(address, inner_path, description)\n        self.response(to, \"ok\")\n\n    @flag.no_multiuser\n    def actionFilterIncludeRemove(self, to, inner_path, address=None):\n        if address:\n            if \"ADMIN\" not in self.getPermissions(to):\n                return self.response(to, {\"error\": \"Forbidden: Only ADMIN sites can manage different site include\"})\n        else:\n            address = self.site.address\n\n        key = \"%s/%s\" % (address, inner_path)\n        if key not in filter_storage.file_content[\"includes\"]:\n            self.response(to, {\"error\": \"Include not found\"})\n        filter_storage.includeRemove(address, inner_path)\n        self.response(to, \"ok\")\n\n    def actionFilterIncludeList(self, to, all_sites=False, filters=False):\n        if all_sites and \"ADMIN\" not in self.getPermissions(to):\n            return self.response(to, {\"error\": \"Forbidden: Only ADMIN sites can list all sites includes\"})\n\n        back = []\n        includes = filter_storage.file_content.get(\"includes\", {}).values()\n        for include in includes:\n            if not all_sites and include[\"address\"] != self.site.address:\n                continue\n            if filters:\n                include = dict(include)  # Don't modify original file_content\n                include_site = filter_storage.site_manager.get(include[\"address\"])\n                if not include_site:\n                    continue\n                content = include_site.storage.loadJson(include[\"inner_path\"])\n                include[\"mutes\"] = content.get(\"mutes\", {})\n                include[\"siteblocks\"] = content.get(\"siteblocks\", {})\n            back.append(include)\n        self.response(to, back)\n\n\n@PluginManager.registerTo(\"SiteStorage\")\nclass SiteStoragePlugin(object):\n    def updateDbFile(self, inner_path, file=None, cur=None):\n        if file is not False:  # File deletion always allowed\n            # Find for bitcoin addresses in file path\n            matches = re.findall(\"/(1[A-Za-z0-9]{26,35})/\", inner_path)\n            # Check if any of the adresses are in the mute list\n            for auth_address in matches:\n                if filter_storage.isMuted(auth_address):\n                    self.log.debug(\"Mute match: %s, ignoring %s\" % (auth_address, inner_path))\n                    return False\n\n        return super(SiteStoragePlugin, self).updateDbFile(inner_path, file=file, cur=cur)\n\n    def onUpdated(self, inner_path, file=None):\n        file_path = \"%s/%s\" % (self.site.address, inner_path)\n        if file_path in filter_storage.file_content[\"includes\"]:\n            self.log.debug(\"Filter file updated: %s\" % inner_path)\n            filter_storage.includeUpdateAll()\n        return super(SiteStoragePlugin, self).onUpdated(inner_path, file=file)\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def actionWrapper(self, path, extra_headers=None):\n        match = re.match(r\"/(?P<address>[A-Za-z0-9\\._-]+)(?P<inner_path>/.*|$)\", path)\n        if not match:\n            return False\n        address = match.group(\"address\")\n\n        if self.server.site_manager.get(address):  # Site already exists\n            return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)\n\n        if self.isDomain(address):\n            address = self.resolveDomain(address)\n\n        if address:\n            address_hashed = filter_storage.getSiteAddressHashed(address)\n        else:\n            address_hashed = None\n\n        if filter_storage.isSiteblocked(address) or filter_storage.isSiteblocked(address_hashed):\n            site = self.server.site_manager.get(config.homepage)\n            if not extra_headers:\n                extra_headers = {}\n\n            script_nonce = self.getScriptNonce()\n\n            self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)\n            return iter([super(UiRequestPlugin, self).renderWrapper(\n                site, path, \"uimedia/plugins/contentfilter/blocklisted.html?address=\" + address,\n                \"Blacklisted site\", extra_headers, show_loadingscreen=False, script_nonce=script_nonce\n            )])\n        else:\n            return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)\n\n    def actionUiMedia(self, path, *args, **kwargs):\n        if path.startswith(\"/uimedia/plugins/contentfilter/\"):\n            file_path = path.replace(\"/uimedia/plugins/contentfilter/\", plugin_dir + \"/media/\")\n            return self.actionFile(file_path)\n        else:\n            return super(UiRequestPlugin, self).actionUiMedia(path)\n"
  },
  {
    "path": "plugins/ContentFilter/ContentFilterStorage.py",
    "content": "import os\nimport json\nimport logging\nimport collections\nimport time\nimport hashlib\n\nfrom Debug import Debug\nfrom Plugin import PluginManager\nfrom Config import config\nfrom util import helper\n\n\nclass ContentFilterStorage(object):\n    def __init__(self, site_manager):\n        self.log = logging.getLogger(\"ContentFilterStorage\")\n        self.file_path = \"%s/filters.json\" % config.data_dir\n        self.site_manager = site_manager\n        self.file_content = self.load()\n\n        # Set default values for filters.json\n        if not self.file_content:\n            self.file_content = {}\n\n        # Site blacklist renamed to site blocks\n        if \"site_blacklist\" in self.file_content:\n            self.file_content[\"siteblocks\"] = self.file_content[\"site_blacklist\"]\n            del self.file_content[\"site_blacklist\"]\n\n        for key in [\"mutes\", \"siteblocks\", \"includes\"]:\n            if key not in self.file_content:\n                self.file_content[key] = {}\n\n        self.include_filters = collections.defaultdict(set)  # Merged list of mutes and blacklists from all include\n        self.includeUpdateAll(update_site_dbs=False)\n\n    def load(self):\n        # Rename previously used mutes.json -> filters.json\n        if os.path.isfile(\"%s/mutes.json\" % config.data_dir):\n            self.log.info(\"Renaming mutes.json to filters.json...\")\n            os.rename(\"%s/mutes.json\" % config.data_dir, self.file_path)\n        if os.path.isfile(self.file_path):\n            try:\n                return json.load(open(self.file_path))\n            except Exception as err:\n                self.log.error(\"Error loading filters.json: %s\" % err)\n                return None\n        else:\n            return None\n\n    def includeUpdateAll(self, update_site_dbs=True):\n        s = time.time()\n        new_include_filters = collections.defaultdict(set)\n\n        # Load all include files data into a merged set\n        for include_path in self.file_content[\"includes\"]:\n            address, inner_path = include_path.split(\"/\", 1)\n            try:\n                content = self.site_manager.get(address).storage.loadJson(inner_path)\n            except Exception as err:\n                self.log.warning(\n                    \"Error loading include %s: %s\" %\n                    (include_path, Debug.formatException(err))\n                )\n                continue\n\n            for key, val in content.items():\n                if type(val) is not dict:\n                    continue\n\n                new_include_filters[key].update(val.keys())\n\n        mutes_added = new_include_filters[\"mutes\"].difference(self.include_filters[\"mutes\"])\n        mutes_removed = self.include_filters[\"mutes\"].difference(new_include_filters[\"mutes\"])\n\n        self.include_filters = new_include_filters\n\n        if update_site_dbs:\n            for auth_address in mutes_added:\n                self.changeDbs(auth_address, \"remove\")\n\n            for auth_address in mutes_removed:\n                if not self.isMuted(auth_address):\n                    self.changeDbs(auth_address, \"load\")\n\n        num_mutes = len(self.include_filters[\"mutes\"])\n        num_siteblocks = len(self.include_filters[\"siteblocks\"])\n        self.log.debug(\n            \"Loaded %s mutes, %s blocked sites from %s includes in %.3fs\" %\n            (num_mutes, num_siteblocks, len(self.file_content[\"includes\"]), time.time() - s)\n        )\n\n    def includeAdd(self, address, inner_path, description=None):\n        self.file_content[\"includes\"][\"%s/%s\" % (address, inner_path)] = {\n            \"date_added\": time.time(),\n            \"address\": address,\n            \"description\": description,\n            \"inner_path\": inner_path\n        }\n        self.includeUpdateAll()\n        self.save()\n\n    def includeRemove(self, address, inner_path):\n        del self.file_content[\"includes\"][\"%s/%s\" % (address, inner_path)]\n        self.includeUpdateAll()\n        self.save()\n\n    def save(self):\n        s = time.time()\n        helper.atomicWrite(self.file_path, json.dumps(self.file_content, indent=2, sort_keys=True).encode(\"utf8\"))\n        self.log.debug(\"Saved in %.3fs\" % (time.time() - s))\n\n    def isMuted(self, auth_address):\n        if auth_address in self.file_content[\"mutes\"] or auth_address in self.include_filters[\"mutes\"]:\n            return True\n        else:\n            return False\n\n    def getSiteAddressHashed(self, address):\n        return \"0x\" + hashlib.sha256(address.encode(\"ascii\")).hexdigest()\n\n    def isSiteblocked(self, address):\n        if address in self.file_content[\"siteblocks\"] or address in self.include_filters[\"siteblocks\"]:\n            return True\n        return False\n\n    def getSiteblockDetails(self, address):\n        details = self.file_content[\"siteblocks\"].get(address)\n        if not details:\n            address_sha256 = self.getSiteAddressHashed(address)\n            details = self.file_content[\"siteblocks\"].get(address_sha256)\n\n        if not details:\n            includes = self.file_content.get(\"includes\", {}).values()\n            for include in includes:\n                include_site = self.site_manager.get(include[\"address\"])\n                if not include_site:\n                    continue\n                content = include_site.storage.loadJson(include[\"inner_path\"])\n                details = content.get(\"siteblocks\", {}).get(address)\n                if details:\n                    details[\"include\"] = include\n                    break\n\n        return details\n\n    # Search and remove or readd files of an user\n    def changeDbs(self, auth_address, action):\n        self.log.debug(\"Mute action %s on user %s\" % (action, auth_address))\n        res = list(self.site_manager.list().values())[0].content_manager.contents.db.execute(\n            \"SELECT * FROM content LEFT JOIN site USING (site_id) WHERE inner_path LIKE :inner_path\",\n            {\"inner_path\": \"%%/%s/%%\" % auth_address}\n        )\n        for row in res:\n            site = self.site_manager.sites.get(row[\"address\"])\n            if not site:\n                continue\n            dir_inner_path = helper.getDirname(row[\"inner_path\"])\n            for file_name in site.storage.walk(dir_inner_path):\n                if action == \"remove\":\n                    site.storage.onUpdated(dir_inner_path + file_name, False)\n                else:\n                    site.storage.onUpdated(dir_inner_path + file_name)\n                site.onFileDone(dir_inner_path + file_name)\n"
  },
  {
    "path": "plugins/ContentFilter/Test/TestContentFilter.py",
    "content": "import pytest\nfrom ContentFilter import ContentFilterPlugin\nfrom Site import SiteManager\n\n\n@pytest.fixture\ndef filter_storage():\n    ContentFilterPlugin.filter_storage = ContentFilterPlugin.ContentFilterStorage(SiteManager.site_manager)\n    return ContentFilterPlugin.filter_storage\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestContentFilter:\n    def createInclude(self, site):\n        site.storage.writeJson(\"filters.json\", {\n            \"mutes\": {\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\": {}},\n            \"siteblocks\": {site.address: {}}\n        })\n\n    def testIncludeLoad(self, site, filter_storage):\n        self.createInclude(site)\n        filter_storage.file_content[\"includes\"][\"%s/%s\" % (site.address, \"filters.json\")] = {\n            \"date_added\": 1528295893,\n        }\n\n        assert not filter_storage.include_filters[\"mutes\"]\n        assert not filter_storage.isMuted(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\")\n        assert not filter_storage.isSiteblocked(site.address)\n        filter_storage.includeUpdateAll(update_site_dbs=False)\n        assert len(filter_storage.include_filters[\"mutes\"]) == 1\n        assert filter_storage.isMuted(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\")\n        assert filter_storage.isSiteblocked(site.address)\n\n    def testIncludeAdd(self, site, filter_storage):\n        self.createInclude(site)\n        query_num_json = \"SELECT COUNT(*) AS num FROM json WHERE directory = 'users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C'\"\n        assert not filter_storage.isSiteblocked(site.address)\n        assert not filter_storage.isMuted(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\")\n        assert site.storage.query(query_num_json).fetchone()[\"num\"] == 2\n\n        # Add include\n        filter_storage.includeAdd(site.address, \"filters.json\")\n\n        assert filter_storage.isSiteblocked(site.address)\n        assert filter_storage.isMuted(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\")\n        assert site.storage.query(query_num_json).fetchone()[\"num\"] == 0\n\n        # Remove include\n        filter_storage.includeRemove(site.address, \"filters.json\")\n\n        assert not filter_storage.isSiteblocked(site.address)\n        assert not filter_storage.isMuted(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\")\n        assert site.storage.query(query_num_json).fetchone()[\"num\"] == 2\n\n    def testIncludeChange(self, site, filter_storage):\n        self.createInclude(site)\n        filter_storage.includeAdd(site.address, \"filters.json\")\n        assert filter_storage.isSiteblocked(site.address)\n        assert filter_storage.isMuted(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\")\n\n        # Add new blocked site\n        assert not filter_storage.isSiteblocked(\"1Hello\")\n\n        filter_content = site.storage.loadJson(\"filters.json\")\n        filter_content[\"siteblocks\"][\"1Hello\"] = {}\n        site.storage.writeJson(\"filters.json\", filter_content)\n\n        assert filter_storage.isSiteblocked(\"1Hello\")\n\n        # Add new muted user\n        query_num_json = \"SELECT COUNT(*) AS num FROM json WHERE directory = 'users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q'\"\n        assert not filter_storage.isMuted(\"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\")\n        assert site.storage.query(query_num_json).fetchone()[\"num\"] == 2\n\n        filter_content[\"mutes\"][\"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\"] = {}\n        site.storage.writeJson(\"filters.json\", filter_content)\n\n        assert filter_storage.isMuted(\"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\")\n        assert site.storage.query(query_num_json).fetchone()[\"num\"] == 0\n\n\n"
  },
  {
    "path": "plugins/ContentFilter/Test/conftest.py",
    "content": "from src.Test.conftest import *\n"
  },
  {
    "path": "plugins/ContentFilter/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/ContentFilter/__init__.py",
    "content": "from . import ContentFilterPlugin\n"
  },
  {
    "path": "plugins/ContentFilter/languages/hu.json",
    "content": "{\r\n\t\"Hide all content from <b>%s</b>?\": \"<b>%s</b> tartalmaniak elrejtése?\",\r\n\t\"Mute\": \"Elnémítás\",\r\n\t\"Unmute <b>%s</b>?\": \"<b>%s</b> tartalmaniak megjelenítése?\",\r\n\t\"Unmute\": \"Némítás visszavonása\"\r\n}\r\n"
  },
  {
    "path": "plugins/ContentFilter/languages/it.json",
    "content": "{\r\n\t\"Hide all content from <b>%s</b>?\": \"<b>%s</b> Vuoi nascondere i contenuti di questo utente ?\",\r\n\t\"Mute\": \"Attiva Silenzia\",\r\n\t\"Unmute <b>%s</b>?\": \"<b>%s</b> Vuoi mostrare i contenuti di questo utente ?\",\r\n\t\"Unmute\": \"Disattiva Silenzia\"\r\n}\r\n"
  },
  {
    "path": "plugins/ContentFilter/languages/jp.json",
    "content": "{\n\t\"Hide all content from <b>%s</b>?\": \"<b>%s</b> のコンテンツをすべて隠しますか？\",\n\t\"Mute\": \"ミュート\",\n\t\"Unmute <b>%s</b>?\": \"<b>%s</b> のミュートを解除しますか？\",\n\t\"Unmute\": \"ミュート解除\"\n}\n"
  },
  {
    "path": "plugins/ContentFilter/languages/pt-br.json",
    "content": "{\n\t\"Hide all content from <b>%s</b>?\": \"<b>%s</b> Ocultar todo o conteúdo de ?\",\n\t\"Mute\": \"Ativar o Silêncio\",\n\t\"Unmute <b>%s</b>?\": \"<b>%s</b> Você quer mostrar o conteúdo deste usuário ?\",\n\t\"Unmute\": \"Desligar o silêncio\"\n}\n"
  },
  {
    "path": "plugins/ContentFilter/languages/zh-tw.json",
    "content": "{\r\n\t\"Hide all content from <b>%s</b>?\": \"屏蔽 <b>%s</b> 的所有內容？\",\r\n\t\"Mute\": \"屏蔽\",\r\n\t\"Unmute <b>%s</b>?\": \"對 <b>%s</b> 解除屏蔽？\",\r\n\t\"Unmute\": \"解除屏蔽\"\r\n}\r\n"
  },
  {
    "path": "plugins/ContentFilter/languages/zh.json",
    "content": "{\r\n\t\"Hide all content from <b>%s</b>?\": \"屏蔽 <b>%s</b> 的所有内容？\",\r\n\t\"Mute\": \"屏蔽\",\r\n\t\"Unmute <b>%s</b>?\": \"对 <b>%s</b> 解除屏蔽？\",\r\n\t\"Unmute\": \"解除屏蔽\"\r\n}\r\n"
  },
  {
    "path": "plugins/ContentFilter/media/blocklisted.html",
    "content": "<html>\n<body>\n\n<style>\n.content { line-height: 24px; font-family: monospace; font-size: 14px; color: #636363; text-transform: uppercase; top: 38%; position: relative; text-align: center; perspective: 1000px }\n.content h1, .content h2 { font-weight: normal; letter-spacing: 1px; }\n.content h2 { font-size: 15px; }\n.content #details {\n    text-align: left; display: inline-block; width: 350px; background-color: white; padding: 17px 27px; border-radius: 0px;\n    box-shadow: 0px 2px 7px -1px #d8d8d8; text-transform: none; margin: 15px; transform: scale(0) rotateX(90deg); transition: all 0.6s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.content #details #added { font-size: 12px; text-align: right; color: #a9a9a9; }\n\n#button { transition: all 1s cubic-bezier(0.075, 0.82, 0.165, 1); opacity: 0; transform: translateY(50px); transition-delay: 0.5s }\n.button {\n    padding: 8px 20px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; border-radius: 2px;\n    text-decoration: none; transition: all 0.5s; background-position: left center; display: inline-block; margin-top: 10px; color: black;\n}\n.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; transition: none; }\n.button:active { position: relative; top: 1px; }\n.button:focus { outline: none; }\n\n.textbutton { color: #999; margin-top: 25px; display: inline-block; text-transform: none; font-family: Arial, Helvetica; text-decoration: none; padding: 5px 15px; }\n.textbutton-main { background-color: #FFF; color: #333; border-radius: 5px; }\n.textbutton:hover { text-decoration: underline; color: #333; transition: none !important; }\n.textbutton:active { background-color: #fafbfc; }\n</style>\n\n<div class=\"content\">\n <h1>Site blocked</h1>\n <h2>This site is on your blocklist:</h2>\n <div id=\"details\">\n  <div id=\"reason\">Too much image</div>\n  <div id=\"added\">on 2015-01-25 12:32:11</div>\n </div>\n <div id=\"buttons\">\n  <a href=\"/\" class=\"textbutton textbutton-main\" id=\"back\">Back to homepage</a>\n  <a href=\"#Visit+Site\" class=\"textbutton\" id=\"visit\">Remove from blocklist and visit the site</a>\n </div>\n</div>\n\n<script type=\"text/javascript\" src=\"js/ZeroFrame.js\"></script>\n\n<script>\nfunction buf2hex(buffer) {\n    return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');\n}\n\nasync function sha256hex(s) {\n    var buff = new TextEncoder(\"utf-8\").encode(s)\n    return \"0x\" + buf2hex(await crypto.subtle.digest(\"SHA-256\", buff))\n}\n\nclass Page extends ZeroFrame {\n    onOpenWebsocket () {\n    \tthis.cmd(\"wrapperSetTitle\", \"Visiting a blocked site - ZeroNet\")\n        this.cmd(\"siteInfo\", {}, (site_info) => {\n            this.site_info = site_info\n        })\n        var address = document.location.search.match(/address=(.*?)[&\\?]/)[1]\n        this.updateSiteblockDetails(address)\n    }\n\n    async updateSiteblockDetails(address) {\n        var block = await this.cmdp(\"siteblockGet\", address)\n        var reason = block[\"reason\"]\n        if (!reason) reason = \"Unknown reason\"\n        var date = new Date(block[\"date_added\"] * 1000)\n        document.getElementById(\"reason\").innerText = reason\n        document.getElementById(\"added\").innerText = \"at \" + date.toLocaleDateString() + \" \" + date.toLocaleTimeString()\n        if (block[\"include\"]) {\n            document.getElementById(\"added\").innerText += \" from a shared blocklist\"\n            document.getElementById(\"visit\").innerText = \"Ignore blocking and visit the site\"\n        }\n        document.getElementById(\"details\").style.transform = \"scale(1) rotateX(0deg)\"\n        document.getElementById(\"visit\").style.transform = \"translateY(0)\"\n        document.getElementById(\"visit\").style.opacity = \"1\"\n        document.getElementById(\"visit\").onclick = () => {\n            if (block[\"include\"])\n                this.cmd(\"siteblockIgnoreAddSite\", address, () => { this.cmd(\"wrapperReload\") })\n            else\n                this.cmd(\"siteblockRemove\", address, () => { this.cmd(\"wrapperReload\") })\n        }\n    }\n}\npage = new Page()\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "plugins/ContentFilter/media/js/ZeroFrame.js",
    "content": "// Version 1.0.0 - Initial release\n// Version 1.1.0 (2017-08-02) - Added cmdp function that returns promise instead of using callback\n// Version 1.2.0 (2017-08-02) - Added Ajax monkey patch to emulate XMLHttpRequest over ZeroFrame API\n\nconst CMD_INNER_READY = 'innerReady'\nconst CMD_RESPONSE = 'response'\nconst CMD_WRAPPER_READY = 'wrapperReady'\nconst CMD_PING = 'ping'\nconst CMD_PONG = 'pong'\nconst CMD_WRAPPER_OPENED_WEBSOCKET = 'wrapperOpenedWebsocket'\nconst CMD_WRAPPER_CLOSE_WEBSOCKET = 'wrapperClosedWebsocket'\n\nclass ZeroFrame {\n    constructor(url) {\n        this.url = url\n        this.waiting_cb = {}\n        this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\")\n        this.connect()\n        this.next_message_id = 1\n        this.init()\n    }\n\n    init() {\n        return this\n    }\n\n    connect() {\n        this.target = window.parent\n        window.addEventListener('message', e => this.onMessage(e), false)\n        this.cmd(CMD_INNER_READY)\n    }\n\n    onMessage(e) {\n        let message = e.data\n        let cmd = message.cmd\n        if (cmd === CMD_RESPONSE) {\n            if (this.waiting_cb[message.to] !== undefined) {\n                this.waiting_cb[message.to](message.result)\n            }\n            else {\n                this.log(\"Websocket callback not found:\", message)\n            }\n        } else if (cmd === CMD_WRAPPER_READY) {\n            this.cmd(CMD_INNER_READY)\n        } else if (cmd === CMD_PING) {\n            this.response(message.id, CMD_PONG)\n        } else if (cmd === CMD_WRAPPER_OPENED_WEBSOCKET) {\n            this.onOpenWebsocket()\n        } else if (cmd === CMD_WRAPPER_CLOSE_WEBSOCKET) {\n            this.onCloseWebsocket()\n        } else {\n            this.onRequest(cmd, message)\n        }\n    }\n\n    onRequest(cmd, message) {\n        this.log(\"Unknown request\", message)\n    }\n\n    response(to, result) {\n        this.send({\n            cmd: CMD_RESPONSE,\n            to: to,\n            result: result\n        })\n    }\n\n    cmd(cmd, params={}, cb=null) {\n        this.send({\n            cmd: cmd,\n            params: params\n        }, cb)\n    }\n\n    cmdp(cmd, params={}) {\n        return new Promise((resolve, reject) => {\n            this.cmd(cmd, params, (res) => {\n                if (res && res.error) {\n                    reject(res.error)\n                } else {\n                    resolve(res)\n                }\n            })\n        })\n    }\n\n    send(message, cb=null) {\n        message.wrapper_nonce = this.wrapper_nonce\n        message.id = this.next_message_id\n        this.next_message_id++\n        this.target.postMessage(message, '*')\n        if (cb) {\n            this.waiting_cb[message.id] = cb\n        }\n    }\n\n    log(...args) {\n        console.log.apply(console, ['[ZeroFrame]'].concat(args))\n    }\n\n    onOpenWebsocket() {\n        this.log('Websocket open')\n    }\n\n    onCloseWebsocket() {\n        this.log('Websocket close')\n    }\n\n    monkeyPatchAjax() {\n        var page = this\n        XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open\n        this.cmd(\"wrapperGetAjaxKey\", [], (res) => { this.ajax_key = res })\n        var newOpen = function (method, url, async) {\n            url += \"?ajax_key=\" + page.ajax_key\n            return this.realOpen(method, url, async)\n        }\n        XMLHttpRequest.prototype.open = newOpen\n    }\n}\n"
  },
  {
    "path": "plugins/ContentFilter/plugin_info.json",
    "content": "{\n\t\"name\": \"ContentFilter\",\n\t\"description\": \"Manage site and user block list.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/Cors/CorsPlugin.py",
    "content": "import re\nimport html\nimport copy\nimport os\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Translate import Translate\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\ndef getCorsPath(site, inner_path):\n    match = re.match(\"^cors-([A-Za-z0-9]{26,35})/(.*)\", inner_path)\n    if not match:\n        raise Exception(\"Invalid cors path: %s\" % inner_path)\n    cors_address = match.group(1)\n    cors_inner_path = match.group(2)\n\n    if not \"Cors:%s\" % cors_address in site.settings[\"permissions\"]:\n        raise Exception(\"This site has no permission to access site %s\" % cors_address)\n\n    return cors_address, cors_inner_path\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def hasSitePermission(self, address, cmd=None):\n        if super(UiWebsocketPlugin, self).hasSitePermission(address, cmd=cmd):\n            return True\n\n        allowed_commands = [\n            \"fileGet\", \"fileList\", \"dirList\", \"fileRules\", \"optionalFileInfo\",\n            \"fileQuery\", \"dbQuery\", \"userGetSettings\", \"siteInfo\"\n        ]\n        if not \"Cors:%s\" % address in self.site.settings[\"permissions\"] or cmd not in allowed_commands:\n            return False\n        else:\n            return True\n\n    # Add cors support for file commands\n    def corsFuncWrapper(self, func_name, to, inner_path, *args, **kwargs):\n        if inner_path.startswith(\"cors-\"):\n            cors_address, cors_inner_path = getCorsPath(self.site, inner_path)\n\n            req_self = copy.copy(self)\n            req_self.site = self.server.sites.get(cors_address)  # Change the site to the merged one\n            if not req_self.site:\n                return {\"error\": \"No site found\"}\n\n            func = getattr(super(UiWebsocketPlugin, req_self), func_name)\n            back = func(to, cors_inner_path, *args, **kwargs)\n            return back\n        else:\n            func = getattr(super(UiWebsocketPlugin, self), func_name)\n            return func(to, inner_path, *args, **kwargs)\n\n    def actionFileGet(self, to, inner_path, *args, **kwargs):\n        return self.corsFuncWrapper(\"actionFileGet\", to, inner_path, *args, **kwargs)\n\n    def actionFileList(self, to, inner_path, *args, **kwargs):\n        return self.corsFuncWrapper(\"actionFileList\", to, inner_path, *args, **kwargs)\n\n    def actionDirList(self, to, inner_path, *args, **kwargs):\n        return self.corsFuncWrapper(\"actionDirList\", to, inner_path, *args, **kwargs)\n\n    def actionFileRules(self, to, inner_path, *args, **kwargs):\n        return self.corsFuncWrapper(\"actionFileRules\", to, inner_path, *args, **kwargs)\n\n    def actionOptionalFileInfo(self, to, inner_path, *args, **kwargs):\n        return self.corsFuncWrapper(\"actionOptionalFileInfo\", to, inner_path, *args, **kwargs)\n\n    def actionCorsPermission(self, to, address):\n        if isinstance(address, list):\n            addresses = address\n        else:\n            addresses = [address]\n\n        button_title = _[\"Grant\"]\n        site_names = []\n        site_addresses = []\n        for address in addresses:\n            site = self.server.sites.get(address)\n            if site:\n                site_name = site.content_manager.contents.get(\"content.json\", {}).get(\"title\", address)\n            else:\n                site_name = address\n                # If at least one site is not downloaded yet, show \"Grant & Add\" instead\n                button_title = _[\"Grant & Add\"]\n\n            if not (site and \"Cors:\" + address in self.permissions):\n                # No site or no permission\n                site_names.append(site_name)\n                site_addresses.append(address)\n\n        if len(site_names) == 0:\n            return \"ignored\"\n\n        self.cmd(\n            \"confirm\",\n            [_[\"This site requests <b>read</b> permission to: <b>%s</b>\"] % \", \".join(map(html.escape, site_names)), button_title],\n            lambda res: self.cbCorsPermission(to, site_addresses)\n        )\n\n    def cbCorsPermission(self, to, addresses):\n        # Add permissions\n        for address in addresses:\n            permission = \"Cors:\" + address\n            if permission not in self.site.settings[\"permissions\"]:\n                self.site.settings[\"permissions\"].append(permission)\n\n        self.site.saveSettings()\n        self.site.updateWebsocket(permission_added=permission)\n\n        self.response(to, \"ok\")\n\n        for address in addresses:\n            site = self.server.sites.get(address)\n            if not site:\n                gevent.spawn(self.server.site_manager.need, address)\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    # Allow to load cross origin files using /cors-address/file.jpg\n    def parsePath(self, path):\n        path_parts = super(UiRequestPlugin, self).parsePath(path)\n        if \"cors-\" not in path:  # Optimization\n            return path_parts\n        site = self.server.sites[path_parts[\"address\"]]\n        try:\n            path_parts[\"address\"], path_parts[\"inner_path\"] = getCorsPath(site, path_parts[\"inner_path\"])\n        except Exception:\n            return None\n        return path_parts\n"
  },
  {
    "path": "plugins/Cors/__init__.py",
    "content": "from . import CorsPlugin"
  },
  {
    "path": "plugins/Cors/plugin_info.json",
    "content": "{\n\t\"name\": \"Cors\",\n\t\"description\": \"Cross site resource read.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/CryptMessage/CryptMessage.py",
    "content": "import hashlib\nimport base64\nimport struct\nfrom lib import sslcrypto\nfrom Crypt import Crypt\n\n\ncurve = sslcrypto.ecc.get_curve(\"secp256k1\")\n\n\ndef eciesEncrypt(data, pubkey, ciphername=\"aes-256-cbc\"):\n    ciphertext, key_e = curve.encrypt(\n        data,\n        base64.b64decode(pubkey),\n        algo=ciphername,\n        derivation=\"sha512\",\n        return_aes_key=True\n    )\n    return key_e, ciphertext\n\n\n@Crypt.thread_pool_crypt.wrap\ndef eciesDecryptMulti(encrypted_datas, privatekey):\n    texts = []  # Decoded texts\n    for encrypted_data in encrypted_datas:\n        try:\n            text = eciesDecrypt(encrypted_data, privatekey).decode(\"utf8\")\n            texts.append(text)\n        except Exception:\n            texts.append(None)\n    return texts\n\n\ndef eciesDecrypt(ciphertext, privatekey):\n    return curve.decrypt(base64.b64decode(ciphertext), curve.wif_to_private(privatekey.encode()), derivation=\"sha512\")\n\n\ndef decodePubkey(pubkey):\n    i = 0\n    curve = struct.unpack('!H', pubkey[i:i + 2])[0]\n    i += 2\n    tmplen = struct.unpack('!H', pubkey[i:i + 2])[0]\n    i += 2\n    pubkey_x = pubkey[i:i + tmplen]\n    i += tmplen\n    tmplen = struct.unpack('!H', pubkey[i:i + 2])[0]\n    i += 2\n    pubkey_y = pubkey[i:i + tmplen]\n    i += tmplen\n    return curve, pubkey_x, pubkey_y, i\n\n\ndef split(encrypted):\n    iv = encrypted[0:16]\n    curve, pubkey_x, pubkey_y, i = decodePubkey(encrypted[16:])\n    ciphertext = encrypted[16 + i:-32]\n\n    return iv, ciphertext\n"
  },
  {
    "path": "plugins/CryptMessage/CryptMessagePlugin.py",
    "content": "import base64\nimport os\n\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Crypt import CryptBitcoin, CryptHash\nfrom Config import config\nimport sslcrypto\n\nfrom . import CryptMessage\n\ncurve = sslcrypto.ecc.get_curve(\"secp256k1\")\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    # - Actions -\n\n    # Returns user's public key unique to site\n    # Return: Public key\n    def actionUserPublickey(self, to, index=0):\n        self.response(to, self.user.getEncryptPublickey(self.site.address, index))\n\n    # Encrypt a text using the publickey or user's sites unique publickey\n    # Return: Encrypted text using base64 encoding\n    def actionEciesEncrypt(self, to, text, publickey=0, return_aes_key=False):\n        if type(publickey) is int:  # Encrypt using user's publickey\n            publickey = self.user.getEncryptPublickey(self.site.address, publickey)\n        aes_key, encrypted = CryptMessage.eciesEncrypt(text.encode(\"utf8\"), publickey)\n        if return_aes_key:\n            self.response(to, [base64.b64encode(encrypted).decode(\"utf8\"), base64.b64encode(aes_key).decode(\"utf8\")])\n        else:\n            self.response(to, base64.b64encode(encrypted).decode(\"utf8\"))\n\n    # Decrypt a text using privatekey or the user's site unique private key\n    # Return: Decrypted text or list of decrypted texts\n    def actionEciesDecrypt(self, to, param, privatekey=0):\n        if type(privatekey) is int:  # Decrypt using user's privatekey\n            privatekey = self.user.getEncryptPrivatekey(self.site.address, privatekey)\n\n        if type(param) == list:\n            encrypted_texts = param\n        else:\n            encrypted_texts = [param]\n\n        texts = CryptMessage.eciesDecryptMulti(encrypted_texts, privatekey)\n\n        if type(param) == list:\n            self.response(to, texts)\n        else:\n            self.response(to, texts[0])\n\n    # Encrypt a text using AES\n    # Return: Iv, AES key, Encrypted text\n    def actionAesEncrypt(self, to, text, key=None):\n        if key:\n            key = base64.b64decode(key)\n        else:\n            key = sslcrypto.aes.new_key()\n\n        if text:\n            encrypted, iv = sslcrypto.aes.encrypt(text.encode(\"utf8\"), key)\n        else:\n            encrypted, iv = b\"\", b\"\"\n\n        res = [base64.b64encode(item).decode(\"utf8\") for item in [key, iv, encrypted]]\n        self.response(to, res)\n\n    # Decrypt a text using AES\n    # Return: Decrypted text\n    def actionAesDecrypt(self, to, *args):\n        if len(args) == 3:  # Single decrypt\n            encrypted_texts = [(args[0], args[1])]\n            keys = [args[2]]\n        else:  # Batch decrypt\n            encrypted_texts, keys = args\n\n        texts = []  # Decoded texts\n        for iv, encrypted_text in encrypted_texts:\n            encrypted_text = base64.b64decode(encrypted_text)\n            iv = base64.b64decode(iv)\n            text = None\n            for key in keys:\n                try:\n                    decrypted = sslcrypto.aes.decrypt(encrypted_text, iv, base64.b64decode(key))\n                    if decrypted and decrypted.decode(\"utf8\"):  # Valid text decoded\n                        text = decrypted.decode(\"utf8\")\n                except Exception as err:\n                    pass\n            texts.append(text)\n\n        if len(args) == 3:\n            self.response(to, texts[0])\n        else:\n            self.response(to, texts)\n\n    # Sign data using ECDSA\n    # Return: Signature\n    def actionEcdsaSign(self, to, data, privatekey=None):\n        if privatekey is None:  # Sign using user's privatekey\n            privatekey = self.user.getAuthPrivatekey(self.site.address)\n\n        self.response(to, CryptBitcoin.sign(data, privatekey))\n\n    # Verify data using ECDSA (address is either a address or array of addresses)\n    # Return: bool\n    def actionEcdsaVerify(self, to, data, address, signature):\n        self.response(to, CryptBitcoin.verify(data, address, signature))\n\n    # Gets the publickey of a given privatekey\n    def actionEccPrivToPub(self, to, privatekey):\n        self.response(to, curve.private_to_public(curve.wif_to_private(privatekey.encode())))\n\n    # Gets the address of a given publickey\n    def actionEccPubToAddr(self, to, publickey):\n        self.response(to, curve.public_to_address(bytes.fromhex(publickey)))\n\n\n@PluginManager.registerTo(\"User\")\nclass UserPlugin(object):\n    def getEncryptPrivatekey(self, address, param_index=0):\n        if param_index < 0 or param_index > 1000:\n            raise Exception(\"Param_index out of range\")\n\n        site_data = self.getSiteData(address)\n\n        if site_data.get(\"cert\"):  # Different privatekey for different cert provider\n            index = param_index + self.getAddressAuthIndex(site_data[\"cert\"])\n        else:\n            index = param_index\n\n        if \"encrypt_privatekey_%s\" % index not in site_data:\n            address_index = self.getAddressAuthIndex(address)\n            crypt_index = address_index + 1000 + index\n            site_data[\"encrypt_privatekey_%s\" % index] = CryptBitcoin.hdPrivatekey(self.master_seed, crypt_index)\n            self.log.debug(\"New encrypt privatekey generated for %s:%s\" % (address, index))\n        return site_data[\"encrypt_privatekey_%s\" % index]\n\n    def getEncryptPublickey(self, address, param_index=0):\n        if param_index < 0 or param_index > 1000:\n            raise Exception(\"Param_index out of range\")\n\n        site_data = self.getSiteData(address)\n\n        if site_data.get(\"cert\"):  # Different privatekey for different cert provider\n            index = param_index + self.getAddressAuthIndex(site_data[\"cert\"])\n        else:\n            index = param_index\n\n        if \"encrypt_publickey_%s\" % index not in site_data:\n            privatekey = self.getEncryptPrivatekey(address, param_index).encode()\n            publickey = curve.private_to_public(curve.wif_to_private(privatekey) + b\"\\x01\")\n            site_data[\"encrypt_publickey_%s\" % index] = base64.b64encode(publickey).decode(\"utf8\")\n        return site_data[\"encrypt_publickey_%s\" % index]\n\n\n@PluginManager.registerTo(\"Actions\")\nclass ActionsPlugin:\n    publickey = \"A3HatibU4S6eZfIQhVs2u7GLN5G9wXa9WwlkyYIfwYaj\"\n    privatekey = \"5JBiKFYBm94EUdbxtnuLi6cvNcPzcKymCUHBDf2B6aq19vvG3rL\"\n    utf8_text = '\\xc1rv\\xedzt\\xfbr\\xf5t\\xfck\\xf6rf\\xfar\\xf3g\\xe9p'\n\n    def getBenchmarkTests(self, online=False):\n        if hasattr(super(), \"getBenchmarkTests\"):\n            tests = super().getBenchmarkTests(online)\n        else:\n            tests = []\n\n        aes_key, encrypted = CryptMessage.eciesEncrypt(self.utf8_text.encode(\"utf8\"), self.publickey)  # Warm-up\n        tests.extend([\n            {\"func\": self.testCryptEciesEncrypt, \"kwargs\": {}, \"num\": 100, \"time_standard\": 1.2},\n            {\"func\": self.testCryptEciesDecrypt, \"kwargs\": {}, \"num\": 500, \"time_standard\": 1.3},\n            {\"func\": self.testCryptEciesDecryptMulti, \"kwargs\": {}, \"num\": 5, \"time_standard\": 0.68},\n            {\"func\": self.testCryptAesEncrypt, \"kwargs\": {}, \"num\": 10000, \"time_standard\": 0.27},\n            {\"func\": self.testCryptAesDecrypt, \"kwargs\": {}, \"num\": 10000, \"time_standard\": 0.25}\n        ])\n        return tests\n\n    def testCryptEciesEncrypt(self, num_run=1):\n        for i in range(num_run):\n            aes_key, encrypted = CryptMessage.eciesEncrypt(self.utf8_text.encode(\"utf8\"), self.publickey)\n            assert len(aes_key) == 32\n            yield \".\"\n\n    def testCryptEciesDecrypt(self, num_run=1):\n        aes_key, encrypted = CryptMessage.eciesEncrypt(self.utf8_text.encode(\"utf8\"), self.publickey)\n        for i in range(num_run):\n            assert len(aes_key) == 32\n            decrypted = CryptMessage.eciesDecrypt(base64.b64encode(encrypted), self.privatekey)\n            assert decrypted == self.utf8_text.encode(\"utf8\"), \"%s != %s\" % (decrypted, self.utf8_text.encode(\"utf8\"))\n            yield \".\"\n\n    def testCryptEciesDecryptMulti(self, num_run=1):\n        yield \"x 100 (%s threads) \" % config.threads_crypt\n        aes_key, encrypted = CryptMessage.eciesEncrypt(self.utf8_text.encode(\"utf8\"), self.publickey)\n\n        threads = []\n        for i in range(num_run):\n            assert len(aes_key) == 32\n            threads.append(gevent.spawn(\n                CryptMessage.eciesDecryptMulti, [base64.b64encode(encrypted)] * 100, self.privatekey\n            ))\n\n        for thread in threads:\n            res = thread.get()\n            assert res[0] == self.utf8_text, \"%s != %s\" % (res[0], self.utf8_text)\n            assert res[0] == res[-1], \"%s != %s\" % (res[0], res[-1])\n            yield \".\"\n        gevent.joinall(threads)\n\n    def testCryptAesEncrypt(self, num_run=1):\n        for i in range(num_run):\n            key = os.urandom(32)\n            encrypted = sslcrypto.aes.encrypt(self.utf8_text.encode(\"utf8\"), key)\n            yield \".\"\n\n    def testCryptAesDecrypt(self, num_run=1):\n        key = os.urandom(32)\n        encrypted_text, iv = sslcrypto.aes.encrypt(self.utf8_text.encode(\"utf8\"), key)\n\n        for i in range(num_run):\n            decrypted = sslcrypto.aes.decrypt(encrypted_text, iv, key).decode(\"utf8\")\n            assert decrypted == self.utf8_text\n            yield \".\"\n"
  },
  {
    "path": "plugins/CryptMessage/Test/TestCrypt.py",
    "content": "import pytest\nimport base64\nfrom CryptMessage import CryptMessage\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestCrypt:\n    publickey = \"A3HatibU4S6eZfIQhVs2u7GLN5G9wXa9WwlkyYIfwYaj\"\n    privatekey = \"5JBiKFYBm94EUdbxtnuLi6cvNcPzcKymCUHBDf2B6aq19vvG3rL\"\n    utf8_text = '\\xc1rv\\xedzt\\xfbr\\xf5t\\xfck\\xf6rf\\xfar\\xf3g\\xe9'\n    ecies_encrypted_text = \"R5J1RFIDOzE5bnWopvccmALKACCk/CRcd/KSE9OgExJKASyMbZ57JVSUenL2TpABMmcT+wAgr2UrOqClxpOWvIUwvwwupXnMbRTzthhIJJrTRW3sCJVaYlGEMn9DAcvbflgEkQX/MVVdLV3tWKySs1Vk8sJC/y+4pGYCrZz7vwDNEEERaqU=\"\n\n    @pytest.mark.parametrize(\"text\", [b\"hello\", '\\xc1rv\\xedzt\\xfbr\\xf5t\\xfck\\xf6rf\\xfar\\xf3g\\xe9'.encode(\"utf8\")])\n    @pytest.mark.parametrize(\"text_repeat\", [1, 10, 128, 1024])\n    def testEncryptEcies(self, text, text_repeat):\n        text_repeated = text * text_repeat\n        aes_key, encrypted = CryptMessage.eciesEncrypt(text_repeated, self.publickey)\n        assert len(aes_key) == 32\n        # assert len(encrypted) == 134 + int(len(text) / 16) * 16  # Not always true\n\n        assert CryptMessage.eciesDecrypt(base64.b64encode(encrypted), self.privatekey) == text_repeated\n\n    def testDecryptEcies(self, user):\n        assert CryptMessage.eciesDecrypt(self.ecies_encrypted_text, self.privatekey) == b\"hello\"\n\n    def testPublickey(self, ui_websocket):\n        pub = ui_websocket.testAction(\"UserPublickey\", 0)\n        assert len(pub) == 44  # Compressed, b64 encoded publickey\n\n        # Different pubkey for specificed index\n        assert ui_websocket.testAction(\"UserPublickey\", 1) != ui_websocket.testAction(\"UserPublickey\", 0)\n\n        # Same publickey for same index\n        assert ui_websocket.testAction(\"UserPublickey\", 2) == ui_websocket.testAction(\"UserPublickey\", 2)\n\n        # Different publickey for different cert\n        site_data = ui_websocket.user.getSiteData(ui_websocket.site.address)\n        site_data[\"cert\"] = None\n        pub1 = ui_websocket.testAction(\"UserPublickey\", 0)\n\n        site_data = ui_websocket.user.getSiteData(ui_websocket.site.address)\n        site_data[\"cert\"] = \"zeroid.bit\"\n        pub2 = ui_websocket.testAction(\"UserPublickey\", 0)\n        assert pub1 != pub2\n\n    def testEcies(self, ui_websocket):\n        pub = ui_websocket.testAction(\"UserPublickey\")\n\n        encrypted = ui_websocket.testAction(\"EciesEncrypt\", \"hello\", pub)\n        assert len(encrypted) == 180\n\n        # Don't allow decrypt using other privatekey index\n        decrypted = ui_websocket.testAction(\"EciesDecrypt\", encrypted, 123)\n        assert decrypted != \"hello\"\n\n        # Decrypt using correct privatekey\n        decrypted = ui_websocket.testAction(\"EciesDecrypt\", encrypted)\n        assert decrypted == \"hello\"\n\n        # Decrypt incorrect text\n        decrypted = ui_websocket.testAction(\"EciesDecrypt\", \"baad\")\n        assert decrypted is None\n\n        # Decrypt batch\n        decrypted = ui_websocket.testAction(\"EciesDecrypt\", [encrypted, \"baad\", encrypted])\n        assert decrypted == [\"hello\", None, \"hello\"]\n\n    def testEciesUtf8(self, ui_websocket):\n        # Utf8 test\n        ui_websocket.actionEciesEncrypt(0, self.utf8_text)\n        encrypted = ui_websocket.ws.getResult()\n\n        ui_websocket.actionEciesDecrypt(0, encrypted)\n        assert ui_websocket.ws.getResult() == self.utf8_text\n\n    def testEciesAes(self, ui_websocket):\n        ui_websocket.actionEciesEncrypt(0, \"hello\", return_aes_key=True)\n        ecies_encrypted, aes_key = ui_websocket.ws.getResult()\n\n        # Decrypt using Ecies\n        ui_websocket.actionEciesDecrypt(0, ecies_encrypted)\n        assert ui_websocket.ws.getResult() == \"hello\"\n\n        # Decrypt using AES\n        aes_iv, aes_encrypted = CryptMessage.split(base64.b64decode(ecies_encrypted))\n\n        ui_websocket.actionAesDecrypt(0, base64.b64encode(aes_iv), base64.b64encode(aes_encrypted), aes_key)\n        assert ui_websocket.ws.getResult() == \"hello\"\n\n    def testEciesAesLongpubkey(self, ui_websocket):\n        privatekey = \"5HwVS1bTFnveNk9EeGaRenWS1QFzLFb5kuncNbiY3RiHZrVR6ok\"\n\n        ecies_encrypted, aes_key = [\"lWiXfEikIjw1ac3J/RaY/gLKACALRUfksc9rXYRFyKDSaxhwcSFBYCgAdIyYlY294g/6VgAf/68PYBVMD3xKH1n7Zbo+ge8b4i/XTKmCZRJvy0eutMKWckYCMVcxgIYNa/ZL1BY1kvvH7omgzg1wBraoLfdbNmVtQgdAZ9XS8PwRy6OB2Q==\", \"Rvlf7zsMuBFHZIGHcbT1rb4If+YTmsWDv6kGwcvSeMM=\"]\n\n        # Decrypt using Ecies\n        ui_websocket.actionEciesDecrypt(0, ecies_encrypted, privatekey)\n        assert ui_websocket.ws.getResult() == \"hello\"\n\n        # Decrypt using AES\n        aes_iv, aes_encrypted = CryptMessage.split(base64.b64decode(ecies_encrypted))\n\n        ui_websocket.actionAesDecrypt(0, base64.b64encode(aes_iv), base64.b64encode(aes_encrypted), aes_key)\n        assert ui_websocket.ws.getResult() == \"hello\"\n\n    def testAes(self, ui_websocket):\n        ui_websocket.actionAesEncrypt(0, \"hello\")\n        key, iv, encrypted = ui_websocket.ws.getResult()\n\n        assert len(key) == 44\n        assert len(iv) == 24\n        assert len(encrypted) == 24\n\n        # Single decrypt\n        ui_websocket.actionAesDecrypt(0, iv, encrypted, key)\n        assert ui_websocket.ws.getResult() == \"hello\"\n\n        # Batch decrypt\n        ui_websocket.actionAesEncrypt(0, \"hello\")\n        key2, iv2, encrypted2 = ui_websocket.ws.getResult()\n\n        assert [key, iv, encrypted] != [key2, iv2, encrypted2]\n\n        # 2 correct key\n        ui_websocket.actionAesDecrypt(0, [[iv, encrypted], [iv, encrypted], [iv, \"baad\"], [iv2, encrypted2]], [key])\n        assert ui_websocket.ws.getResult() == [\"hello\", \"hello\", None, None]\n\n        # 3 key\n        ui_websocket.actionAesDecrypt(0, [[iv, encrypted], [iv, encrypted], [iv, \"baad\"], [iv2, encrypted2]], [key, key2])\n        assert ui_websocket.ws.getResult() == [\"hello\", \"hello\", None, \"hello\"]\n\n    def testAesUtf8(self, ui_websocket):\n        ui_websocket.actionAesEncrypt(0, self.utf8_text)\n        key, iv, encrypted = ui_websocket.ws.getResult()\n\n        ui_websocket.actionAesDecrypt(0, iv, encrypted, key)\n        assert ui_websocket.ws.getResult() == self.utf8_text\n"
  },
  {
    "path": "plugins/CryptMessage/Test/conftest.py",
    "content": "from src.Test.conftest import *"
  },
  {
    "path": "plugins/CryptMessage/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/CryptMessage/__init__.py",
    "content": "from . import CryptMessagePlugin"
  },
  {
    "path": "plugins/CryptMessage/plugin_info.json",
    "content": "{\n\t\"name\": \"CryptMessage\",\n\t\"description\": \"Cryptographic functions of ECIES and AES data encryption/decryption.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/FilePack/FilePackPlugin.py",
    "content": "import os\nimport re\n\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\n\n# Keep archive open for faster reponse times for large sites\narchive_cache = {}\n\n\ndef closeArchive(archive_path):\n    if archive_path in archive_cache:\n        del archive_cache[archive_path]\n\n\ndef openArchive(archive_path, file_obj=None):\n    if archive_path not in archive_cache:\n        if archive_path.endswith(\"tar.gz\"):\n            import tarfile\n            archive_cache[archive_path] = tarfile.open(archive_path, fileobj=file_obj, mode=\"r:gz\")\n        else:\n            import zipfile\n            archive_cache[archive_path] = zipfile.ZipFile(file_obj or archive_path)\n        gevent.spawn_later(5, lambda: closeArchive(archive_path))  # Close after 5 sec\n\n    archive = archive_cache[archive_path]\n    return archive\n\n\ndef openArchiveFile(archive_path, path_within, file_obj=None):\n    archive = openArchive(archive_path, file_obj=file_obj)\n    if archive_path.endswith(\".zip\"):\n        return archive.open(path_within)\n    else:\n        return archive.extractfile(path_within)\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def actionSiteMedia(self, path, **kwargs):\n        if \".zip/\" in path or \".tar.gz/\" in path:\n            file_obj = None\n            path_parts = self.parsePath(path)\n            file_path = \"%s/%s/%s\" % (config.data_dir, path_parts[\"address\"], path_parts[\"inner_path\"])\n            match = re.match(\"^(.*\\.(?:tar.gz|zip))/(.*)\", file_path)\n            archive_path, path_within = match.groups()\n            if archive_path not in archive_cache:\n                site = self.server.site_manager.get(path_parts[\"address\"])\n                if not site:\n                    return self.actionSiteAddPrompt(path)\n                archive_inner_path = site.storage.getInnerPath(archive_path)\n                if not os.path.isfile(archive_path):\n                    # Wait until file downloads\n                    result = site.needFile(archive_inner_path, priority=10)\n                    # Send virutal file path download finished event to remove loading screen\n                    site.updateWebsocket(file_done=archive_inner_path)\n                    if not result:\n                        return self.error404(archive_inner_path)\n                file_obj = site.storage.openBigfile(archive_inner_path)\n                if file_obj == False:\n                    file_obj = None\n\n            header_allow_ajax = False\n            if self.get.get(\"ajax_key\"):\n                requester_site = self.server.site_manager.get(path_parts[\"request_address\"])\n                if self.get[\"ajax_key\"] == requester_site.settings[\"ajax_key\"]:\n                    header_allow_ajax = True\n                else:\n                    return self.error403(\"Invalid ajax_key\")\n\n            try:\n                file = openArchiveFile(archive_path, path_within, file_obj=file_obj)\n                content_type = self.getContentType(file_path)\n                self.sendHeader(200, content_type=content_type, noscript=kwargs.get(\"header_noscript\", False), allow_ajax=header_allow_ajax)\n                return self.streamFile(file)\n            except Exception as err:\n                self.log.debug(\"Error opening archive file: %s\" % Debug.formatException(err))\n                return self.error404(path)\n\n        return super(UiRequestPlugin, self).actionSiteMedia(path, **kwargs)\n\n    def streamFile(self, file):\n        for i in range(100):  # Read max 6MB\n            try:\n                block = file.read(60 * 1024)\n                if block:\n                    yield block\n                else:\n                    raise StopIteration\n            except StopIteration:\n                file.close()\n                break\n\n\n@PluginManager.registerTo(\"SiteStorage\")\nclass SiteStoragePlugin(object):\n    def isFile(self, inner_path):\n        if \".zip/\" in inner_path or \".tar.gz/\" in inner_path:\n            match = re.match(\"^(.*\\.(?:tar.gz|zip))/(.*)\", inner_path)\n            archive_inner_path, path_within = match.groups()\n            return super(SiteStoragePlugin, self).isFile(archive_inner_path)\n        else:\n            return super(SiteStoragePlugin, self).isFile(inner_path)\n\n    def openArchive(self, inner_path):\n        archive_path = self.getPath(inner_path)\n        file_obj = None\n        if archive_path not in archive_cache:\n            if not os.path.isfile(archive_path):\n                result = self.site.needFile(inner_path, priority=10)\n                self.site.updateWebsocket(file_done=inner_path)\n                if not result:\n                    raise Exception(\"Unable to download file\")\n            file_obj = self.site.storage.openBigfile(inner_path)\n            if file_obj == False:\n                file_obj = None\n\n        try:\n            archive = openArchive(archive_path, file_obj=file_obj)\n        except Exception as err:\n            raise Exception(\"Unable to download file: %s\" % Debug.formatException(err))\n\n        return archive\n\n    def walk(self, inner_path, *args, **kwags):\n        if \".zip\" in inner_path or \".tar.gz\" in inner_path:\n            match = re.match(\"^(.*\\.(?:tar.gz|zip))(.*)\", inner_path)\n            archive_inner_path, path_within = match.groups()\n            archive = self.openArchive(archive_inner_path)\n            path_within = path_within.lstrip(\"/\")\n\n            if archive_inner_path.endswith(\".zip\"):\n                namelist = [name for name in archive.namelist() if not name.endswith(\"/\")]\n            else:\n                namelist = [item.name for item in archive.getmembers() if not item.isdir()]\n\n            namelist_relative = []\n            for name in namelist:\n                if not name.startswith(path_within):\n                    continue\n                name_relative = name.replace(path_within, \"\", 1).rstrip(\"/\")\n                namelist_relative.append(name_relative)\n\n            return namelist_relative\n\n        else:\n            return super(SiteStoragePlugin, self).walk(inner_path, *args, **kwags)\n\n    def list(self, inner_path, *args, **kwags):\n        if \".zip\" in inner_path or \".tar.gz\" in inner_path:\n            match = re.match(\"^(.*\\.(?:tar.gz|zip))(.*)\", inner_path)\n            archive_inner_path, path_within = match.groups()\n            archive = self.openArchive(archive_inner_path)\n            path_within = path_within.lstrip(\"/\")\n\n            if archive_inner_path.endswith(\".zip\"):\n                namelist = [name for name in archive.namelist()]\n            else:\n                namelist = [item.name for item in archive.getmembers()]\n\n            namelist_relative = []\n            for name in namelist:\n                if not name.startswith(path_within):\n                    continue\n                name_relative = name.replace(path_within, \"\", 1).rstrip(\"/\")\n\n                if \"/\" in name_relative:  # File is in sub-directory\n                    continue\n\n                namelist_relative.append(name_relative)\n            return namelist_relative\n\n        else:\n            return super(SiteStoragePlugin, self).list(inner_path, *args, **kwags)\n\n    def read(self, inner_path, mode=\"rb\", **kwargs):\n        if \".zip/\" in inner_path or \".tar.gz/\" in inner_path:\n            match = re.match(\"^(.*\\.(?:tar.gz|zip))(.*)\", inner_path)\n            archive_inner_path, path_within = match.groups()\n            archive = self.openArchive(archive_inner_path)\n            path_within = path_within.lstrip(\"/\")\n\n            if archive_inner_path.endswith(\".zip\"):\n                return archive.open(path_within).read()\n            else:\n                return archive.extractfile(path_within).read()\n\n        else:\n            return super(SiteStoragePlugin, self).read(inner_path, mode, **kwargs)\n\n"
  },
  {
    "path": "plugins/FilePack/__init__.py",
    "content": "from . import FilePackPlugin"
  },
  {
    "path": "plugins/FilePack/plugin_info.json",
    "content": "{\n\t\"name\": \"FilePack\",\n\t\"description\": \"Transparent web access for Zip and Tar.gz files.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/MergerSite/MergerSitePlugin.py",
    "content": "import re\nimport time\nimport copy\nimport os\n\nfrom Plugin import PluginManager\nfrom Translate import Translate\nfrom util import RateLimit\nfrom util import helper\nfrom util.Flag import flag\nfrom Debug import Debug\ntry:\n    import OptionalManager.UiWebsocketPlugin  # To make optioanlFileInfo merger sites compatible\nexcept Exception:\n    pass\n\nif \"merger_db\" not in locals().keys():  # To keep merger_sites between module reloads\n    merger_db = {}  # Sites that allowed to list other sites {address: [type1, type2...]}\n    merged_db = {}  # Sites that allowed to be merged to other sites {address: type, ...}\n    merged_to_merger = {}  # {address: [site1, site2, ...]} cache\n    site_manager = None  # Site manager for merger sites\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n# Check if the site has permission to this merger site\ndef checkMergerPath(address, inner_path):\n    merged_match = re.match(\"^merged-(.*?)/([A-Za-z0-9]{26,35})/\", inner_path)\n    if merged_match:\n        merger_type = merged_match.group(1)\n        # Check if merged site is allowed to include other sites\n        if merger_type in merger_db.get(address, []):\n            # Check if included site allows to include\n            merged_address = merged_match.group(2)\n            if merged_db.get(merged_address) == merger_type:\n                inner_path = re.sub(\"^merged-(.*?)/([A-Za-z0-9]{26,35})/\", \"\", inner_path)\n                return merged_address, inner_path\n            else:\n                raise Exception(\n                    \"Merger site (%s) does not have permission for merged site: %s (%s)\" %\n                    (merger_type, merged_address, merged_db.get(merged_address))\n                )\n        else:\n            raise Exception(\"No merger (%s) permission to load: <br>%s (%s not in %s)\" % (\n                address, inner_path, merger_type, merger_db.get(address, []))\n            )\n    else:\n        raise Exception(\"Invalid merger path: %s\" % inner_path)\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    # Download new site\n    def actionMergerSiteAdd(self, to, addresses):\n        if type(addresses) != list:\n            # Single site add\n            addresses = [addresses]\n        # Check if the site has merger permission\n        merger_types = merger_db.get(self.site.address)\n        if not merger_types:\n            return self.response(to, {\"error\": \"Not a merger site\"})\n\n        if RateLimit.isAllowed(self.site.address + \"-MergerSiteAdd\", 10) and len(addresses) == 1:\n            # Without confirmation if only one site address and not called in last 10 sec\n            self.cbMergerSiteAdd(to, addresses)\n        else:\n            self.cmd(\n                \"confirm\",\n                [_[\"Add <b>%s</b> new site?\"] % len(addresses), \"Add\"],\n                lambda res: self.cbMergerSiteAdd(to, addresses)\n            )\n        self.response(to, \"ok\")\n\n    # Callback of adding new site confirmation\n    def cbMergerSiteAdd(self, to, addresses):\n        added = 0\n        for address in addresses:\n            try:\n                site_manager.need(address)\n                added += 1\n            except Exception as err:\n                self.cmd(\"notification\", [\"error\", _[\"Adding <b>%s</b> failed: %s\"] % (address, err)])\n        if added:\n            self.cmd(\"notification\", [\"done\", _[\"Added <b>%s</b> new site\"] % added, 5000])\n        RateLimit.called(self.site.address + \"-MergerSiteAdd\")\n        site_manager.updateMergerSites()\n\n    # Delete a merged site\n    @flag.no_multiuser\n    def actionMergerSiteDelete(self, to, address):\n        site = self.server.sites.get(address)\n        if not site:\n            return self.response(to, {\"error\": \"No site found: %s\" % address})\n\n        merger_types = merger_db.get(self.site.address)\n        if not merger_types:\n            return self.response(to, {\"error\": \"Not a merger site\"})\n        if merged_db.get(address) not in merger_types:\n            return self.response(to, {\"error\": \"Merged type (%s) not in %s\" % (merged_db.get(address), merger_types)})\n\n        self.cmd(\"notification\", [\"done\", _[\"Site deleted: <b>%s</b>\"] % address, 5000])\n        self.response(to, \"ok\")\n\n    # Lists merged sites\n    def actionMergerSiteList(self, to, query_site_info=False):\n        merger_types = merger_db.get(self.site.address)\n        ret = {}\n        if not merger_types:\n            return self.response(to, {\"error\": \"Not a merger site\"})\n        for address, merged_type in merged_db.items():\n            if merged_type not in merger_types:\n                continue  # Site not for us\n            if query_site_info:\n                site = self.server.sites.get(address)\n                ret[address] = self.formatSiteInfo(site, create_user=False)\n            else:\n                ret[address] = merged_type\n        self.response(to, ret)\n\n    def hasSitePermission(self, address, *args, **kwargs):\n        if super(UiWebsocketPlugin, self).hasSitePermission(address, *args, **kwargs):\n            return True\n        else:\n            if self.site.address in [merger_site.address for merger_site in merged_to_merger.get(address, [])]:\n                return True\n            else:\n                return False\n\n    # Add support merger sites for file commands\n    def mergerFuncWrapper(self, func_name, to, inner_path, *args, **kwargs):\n        if inner_path.startswith(\"merged-\"):\n            merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)\n\n            # Set the same cert for merged site\n            merger_cert = self.user.getSiteData(self.site.address).get(\"cert\")\n            if merger_cert and self.user.getSiteData(merged_address).get(\"cert\") != merger_cert:\n                self.user.setCert(merged_address, merger_cert)\n\n            req_self = copy.copy(self)\n            req_self.site = self.server.sites.get(merged_address)  # Change the site to the merged one\n\n            func = getattr(super(UiWebsocketPlugin, req_self), func_name)\n            return func(to, merged_inner_path, *args, **kwargs)\n        else:\n            func = getattr(super(UiWebsocketPlugin, self), func_name)\n            return func(to, inner_path, *args, **kwargs)\n\n    def actionFileList(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionFileList\", to, inner_path, *args, **kwargs)\n\n    def actionDirList(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionDirList\", to, inner_path, *args, **kwargs)\n\n    def actionFileGet(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionFileGet\", to, inner_path, *args, **kwargs)\n\n    def actionFileWrite(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionFileWrite\", to, inner_path, *args, **kwargs)\n\n    def actionFileDelete(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionFileDelete\", to, inner_path, *args, **kwargs)\n\n    def actionFileRules(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionFileRules\", to, inner_path, *args, **kwargs)\n\n    def actionFileNeed(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionFileNeed\", to, inner_path, *args, **kwargs)\n\n    def actionOptionalFileInfo(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionOptionalFileInfo\", to, inner_path, *args, **kwargs)\n\n    def actionOptionalFileDelete(self, to, inner_path, *args, **kwargs):\n        return self.mergerFuncWrapper(\"actionOptionalFileDelete\", to, inner_path, *args, **kwargs)\n\n    def actionBigfileUploadInit(self, to, inner_path, *args, **kwargs):\n        back = self.mergerFuncWrapper(\"actionBigfileUploadInit\", to, inner_path, *args, **kwargs)\n        if inner_path.startswith(\"merged-\"):\n            merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)\n            back[\"inner_path\"] = \"merged-%s/%s/%s\" % (merged_db[merged_address], merged_address, back[\"inner_path\"])\n        return back\n\n    # Add support merger sites for file commands with privatekey parameter\n    def mergerFuncWrapperWithPrivatekey(self, func_name, to, privatekey, inner_path, *args, **kwargs):\n        func = getattr(super(UiWebsocketPlugin, self), func_name)\n        if inner_path.startswith(\"merged-\"):\n            merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)\n            merged_site = self.server.sites.get(merged_address)\n\n            # Set the same cert for merged site\n            merger_cert = self.user.getSiteData(self.site.address).get(\"cert\")\n            if merger_cert:\n                self.user.setCert(merged_address, merger_cert)\n\n            site_before = self.site  # Save to be able to change it back after we ran the command\n            self.site = merged_site  # Change the site to the merged one\n            try:\n                back = func(to, privatekey, merged_inner_path, *args, **kwargs)\n            finally:\n                self.site = site_before  # Change back to original site\n            return back\n        else:\n            return func(to, privatekey, inner_path, *args, **kwargs)\n\n    def actionSiteSign(self, to, privatekey=None, inner_path=\"content.json\", *args, **kwargs):\n        return self.mergerFuncWrapperWithPrivatekey(\"actionSiteSign\", to, privatekey, inner_path, *args, **kwargs)\n\n    def actionSitePublish(self, to, privatekey=None, inner_path=\"content.json\", *args, **kwargs):\n        return self.mergerFuncWrapperWithPrivatekey(\"actionSitePublish\", to, privatekey, inner_path, *args, **kwargs)\n\n    def actionPermissionAdd(self, to, permission):\n        super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)\n        if permission.startswith(\"Merger\"):\n            self.site.storage.rebuildDb()\n\n    def actionPermissionDetails(self, to, permission):\n        if not permission.startswith(\"Merger\"):\n            return super(UiWebsocketPlugin, self).actionPermissionDetails(to, permission)\n\n        merger_type = permission.replace(\"Merger:\", \"\")\n        if not re.match(\"^[A-Za-z0-9-]+$\", merger_type):\n            raise Exception(\"Invalid merger_type: %s\" % merger_type)\n        merged_sites = []\n        for address, merged_type in merged_db.items():\n            if merged_type != merger_type:\n                continue\n            site = self.server.sites.get(address)\n            try:\n                merged_sites.append(site.content_manager.contents.get(\"content.json\").get(\"title\", address))\n            except Exception:\n                merged_sites.append(address)\n\n        details = _[\"Read and write permissions to sites with merged type of <b>%s</b> \"] % merger_type\n        details += _[\"(%s sites)\"] % len(merged_sites)\n        details += \"<div style='white-space: normal; max-width: 400px'>%s</div>\" % \", \".join(merged_sites)\n        self.response(to, details)\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    # Allow to load merged site files using /merged-ZeroMe/address/file.jpg\n    def parsePath(self, path):\n        path_parts = super(UiRequestPlugin, self).parsePath(path)\n        if \"merged-\" not in path:  # Optimization\n            return path_parts\n        path_parts[\"address\"], path_parts[\"inner_path\"] = checkMergerPath(path_parts[\"address\"], path_parts[\"inner_path\"])\n        return path_parts\n\n\n@PluginManager.registerTo(\"SiteStorage\")\nclass SiteStoragePlugin(object):\n    # Also rebuild from merged sites\n    def getDbFiles(self):\n        merger_types = merger_db.get(self.site.address)\n\n        # First return the site's own db files\n        for item in super(SiteStoragePlugin, self).getDbFiles():\n            yield item\n\n        # Not a merger site, that's all\n        if not merger_types:\n            return\n\n        merged_sites = [\n            site_manager.sites[address]\n            for address, merged_type in merged_db.items()\n            if merged_type in merger_types\n        ]\n        found = 0\n        for merged_site in merged_sites:\n            self.log.debug(\"Loading merged site: %s\" % merged_site)\n            merged_type = merged_db[merged_site.address]\n            for content_inner_path, content in merged_site.content_manager.contents.items():\n                # content.json file itself\n                if merged_site.storage.isFile(content_inner_path):  # Missing content.json file\n                    merged_inner_path = \"merged-%s/%s/%s\" % (merged_type, merged_site.address, content_inner_path)\n                    yield merged_inner_path, merged_site.storage.getPath(content_inner_path)\n                else:\n                    merged_site.log.error(\"[MISSING] %s\" % content_inner_path)\n                # Data files in content.json\n                content_inner_path_dir = helper.getDirname(content_inner_path)  # Content.json dir relative to site\n                for file_relative_path in list(content.get(\"files\", {}).keys()) + list(content.get(\"files_optional\", {}).keys()):\n                    if not file_relative_path.endswith(\".json\"):\n                        continue  # We only interesed in json files\n                    file_inner_path = content_inner_path_dir + file_relative_path  # File Relative to site dir\n                    file_inner_path = file_inner_path.strip(\"/\")  # Strip leading /\n                    if merged_site.storage.isFile(file_inner_path):\n                        merged_inner_path = \"merged-%s/%s/%s\" % (merged_type, merged_site.address, file_inner_path)\n                        yield merged_inner_path, merged_site.storage.getPath(file_inner_path)\n                    else:\n                        merged_site.log.error(\"[MISSING] %s\" % file_inner_path)\n                    found += 1\n                    if found % 100 == 0:\n                        time.sleep(0.001)  # Context switch to avoid UI block\n\n    # Also notice merger sites on a merged site file change\n    def onUpdated(self, inner_path, file=None):\n        super(SiteStoragePlugin, self).onUpdated(inner_path, file)\n\n        merged_type = merged_db.get(self.site.address)\n\n        for merger_site in merged_to_merger.get(self.site.address, []):\n            if merger_site.address == self.site.address:  # Avoid infinite loop\n                continue\n            virtual_path = \"merged-%s/%s/%s\" % (merged_type, self.site.address, inner_path)\n            if inner_path.endswith(\".json\"):\n                if file is not None:\n                    merger_site.storage.onUpdated(virtual_path, file=file)\n                else:\n                    merger_site.storage.onUpdated(virtual_path, file=self.open(inner_path))\n            else:\n                merger_site.storage.onUpdated(virtual_path)\n\n\n@PluginManager.registerTo(\"Site\")\nclass SitePlugin(object):\n    def fileDone(self, inner_path):\n        super(SitePlugin, self).fileDone(inner_path)\n\n        for merger_site in merged_to_merger.get(self.address, []):\n            if merger_site.address == self.address:\n                continue\n            for ws in merger_site.websockets:\n                ws.event(\"siteChanged\", self, {\"event\": [\"file_done\", inner_path]})\n\n    def fileFailed(self, inner_path):\n        super(SitePlugin, self).fileFailed(inner_path)\n\n        for merger_site in merged_to_merger.get(self.address, []):\n            if merger_site.address == self.address:\n                continue\n            for ws in merger_site.websockets:\n                ws.event(\"siteChanged\", self, {\"event\": [\"file_failed\", inner_path]})\n\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n    # Update merger site for site types\n    def updateMergerSites(self):\n        global merger_db, merged_db, merged_to_merger, site_manager\n        s = time.time()\n        merger_db_new = {}\n        merged_db_new = {}\n        merged_to_merger_new = {}\n        site_manager = self\n        if not self.sites:\n            return\n        for site in self.sites.values():\n            # Update merged sites\n            try:\n                merged_type = site.content_manager.contents.get(\"content.json\", {}).get(\"merged_type\")\n            except Exception as err:\n                self.log.error(\"Error loading site %s: %s\" % (site.address, Debug.formatException(err)))\n                continue\n            if merged_type:\n                merged_db_new[site.address] = merged_type\n\n            # Update merger sites\n            for permission in site.settings[\"permissions\"]:\n                if not permission.startswith(\"Merger:\"):\n                    continue\n                if merged_type:\n                    self.log.error(\n                        \"Removing permission %s from %s: Merger and merged at the same time.\" %\n                        (permission, site.address)\n                    )\n                    site.settings[\"permissions\"].remove(permission)\n                    continue\n                merger_type = permission.replace(\"Merger:\", \"\")\n                if site.address not in merger_db_new:\n                    merger_db_new[site.address] = []\n                merger_db_new[site.address].append(merger_type)\n                site_manager.sites[site.address] = site\n\n            # Update merged to merger\n            if merged_type:\n                for merger_site in self.sites.values():\n                    if \"Merger:\" + merged_type in merger_site.settings[\"permissions\"]:\n                        if site.address not in merged_to_merger_new:\n                            merged_to_merger_new[site.address] = []\n                        merged_to_merger_new[site.address].append(merger_site)\n\n        # Update globals\n        merger_db = merger_db_new\n        merged_db = merged_db_new\n        merged_to_merger = merged_to_merger_new\n\n        self.log.debug(\"Updated merger sites in %.3fs\" % (time.time() - s))\n\n    def load(self, *args, **kwags):\n        super(SiteManagerPlugin, self).load(*args, **kwags)\n        self.updateMergerSites()\n\n    def saveDelayed(self, *args, **kwags):\n        super(SiteManagerPlugin, self).saveDelayed(*args, **kwags)\n        self.updateMergerSites()\n"
  },
  {
    "path": "plugins/MergerSite/__init__.py",
    "content": "from . import MergerSitePlugin"
  },
  {
    "path": "plugins/MergerSite/languages/es.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"¿Agregar <b>%s</b> nuevo sitio?\",\n\t\"Added <b>%s</b> new site\": \"Sitio <b>%s</b> agregado\",\n\t\"Site deleted: <b>%s</b>\": \"Sitio removido: <b>%s</b>\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/fr.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"Ajouter le site <b>%s</b> ?\",\n\t\"Added <b>%s</b> new site\": \"Site <b>%s</b> ajouté\",\n\t\"Site deleted: <b>%s</b>\": \"Site <b>%s</b> supprimé\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/hu.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"Új oldal hozzáadása: <b>%s</b>?\",\n\t\"Added <b>%s</b> new site\": \"Új oldal hozzáadva: <b>%s</b>\",\n\t\"Site deleted: <b>%s</b>\": \"Oldal törölve: <b>%s</b>\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/it.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"Aggiungere <b>%s</b> nuovo sito ?\",\n\t\"Added <b>%s</b> new site\": \"Sito <b>%s</b> aggiunto\",\n\t\"Site deleted: <b>%s</b>\": \"Sito <b>%s</b> eliminato\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/jp.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"サイト: <b>%s</b> を追加しますか？\",\n\t\"Added <b>%s</b> new site\": \"サイト: <b>%s</b> を追加しました\",\n\t\"Site deleted: <b>%s</b>\": \"サイト: <b>%s</b> を削除しました\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/pt-br.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"Adicionar <b>%s</b> novo site?\",\n\t\"Added <b>%s</b> new site\": \"Site <b>%s</b> adicionado\",\n\t\"Site deleted: <b>%s</b>\": \"Site removido: <b>%s</b>\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/tr.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"<b>%s</b> sitesi eklensin mi?\",\n\t\"Added <b>%s</b> new site\": \"<b>%s</b> sitesi eklendi\",\n\t\"Site deleted: <b>%s</b>\": \"<b>%s</b> sitesi silindi\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/zh-tw.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"添加新網站: <b>%s</b>？\",\n\t\"Added <b>%s</b> new site\": \"已添加到新網站：<b>%s</b>\",\n\t\"Site deleted: <b>%s</b>\": \"網站已刪除：<b>%s</b>\"\n}\n"
  },
  {
    "path": "plugins/MergerSite/languages/zh.json",
    "content": "{\n\t\"Add <b>%s</b> new site?\": \"添加新站点: <b>%s</b>？\",\n\t\"Added <b>%s</b> new site\": \"已添加到新站点：<b>%s</b>\",\n\t\"Site deleted: <b>%s</b>\": \"站点已删除：<b>%s</b>\"\n}\n"
  },
  {
    "path": "plugins/Newsfeed/NewsfeedPlugin.py",
    "content": "import time\nimport re\n\nfrom Plugin import PluginManager\nfrom Db.DbQuery import DbQuery\nfrom Debug import Debug\nfrom util import helper\nfrom util.Flag import flag\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def formatSiteInfo(self, site, create_user=True):\n        site_info = super(UiWebsocketPlugin, self).formatSiteInfo(site, create_user=create_user)\n        feed_following = self.user.sites.get(site.address, {}).get(\"follow\", None)\n        if feed_following == None:\n            site_info[\"feed_follow_num\"] = None\n        else:\n            site_info[\"feed_follow_num\"] = len(feed_following)\n        return site_info\n\n    def actionFeedFollow(self, to, feeds):\n        self.user.setFeedFollow(self.site.address, feeds)\n        self.user.save()\n        self.response(to, \"ok\")\n\n    def actionFeedListFollow(self, to):\n        feeds = self.user.sites.get(self.site.address, {}).get(\"follow\", {})\n        self.response(to, feeds)\n\n    @flag.admin\n    def actionFeedQuery(self, to, limit=10, day_limit=3):\n        from Site import SiteManager\n        rows = []\n        stats = []\n\n        total_s = time.time()\n        num_sites = 0\n\n        for address, site_data in list(self.user.sites.items()):\n            feeds = site_data.get(\"follow\")\n            if not feeds:\n                continue\n            if type(feeds) is not dict:\n                self.log.debug(\"Invalid feed for site %s\" % address)\n                continue\n            num_sites += 1\n            for name, query_set in feeds.items():\n                site = SiteManager.site_manager.get(address)\n                if not site or not site.storage.has_db:\n                    continue\n\n                s = time.time()\n                try:\n                    query_raw, params = query_set\n                    query_parts = re.split(r\"UNION(?:\\s+ALL|)\", query_raw)\n                    for i, query_part in enumerate(query_parts):\n                        db_query = DbQuery(query_part)\n                        if day_limit:\n                            where = \" WHERE %s > strftime('%%s', 'now', '-%s day')\" % (db_query.fields.get(\"date_added\", \"date_added\"), day_limit)\n                            if \"WHERE\" in query_part:\n                                query_part = re.sub(\"WHERE (.*?)(?=$| GROUP BY)\", where+\" AND (\\\\1)\", query_part)\n                            else:\n                                query_part += where\n                        query_parts[i] = query_part\n                    query = \" UNION \".join(query_parts)\n\n                    if \":params\" in query:\n                        query_params = map(helper.sqlquote, params)\n                        query = query.replace(\":params\", \",\".join(query_params))\n\n                    res = site.storage.query(query + \" ORDER BY date_added DESC LIMIT %s\" % limit)\n\n                except Exception as err:  # Log error\n                    self.log.error(\"%s feed query %s error: %s\" % (address, name, Debug.formatException(err)))\n                    stats.append({\"site\": site.address, \"feed_name\": name, \"error\": str(err)})\n                    continue\n\n                for row in res:\n                    row = dict(row)\n                    if not isinstance(row[\"date_added\"], (int, float, complex)):\n                        self.log.debug(\"Invalid date_added from site %s: %r\" % (address, row[\"date_added\"]))\n                        continue\n                    if row[\"date_added\"] > 1000000000000:  # Formatted as millseconds\n                        row[\"date_added\"] = row[\"date_added\"] / 1000\n                    if \"date_added\" not in row or row[\"date_added\"] > time.time() + 120:\n                        self.log.debug(\"Newsfeed item from the future from from site %s\" % address)\n                        continue  # Feed item is in the future, skip it\n                    row[\"site\"] = address\n                    row[\"feed_name\"] = name\n                    rows.append(row)\n                stats.append({\"site\": site.address, \"feed_name\": name, \"taken\": round(time.time() - s, 3)})\n                time.sleep(0.001)\n        return self.response(to, {\"rows\": rows, \"stats\": stats, \"num\": len(rows), \"sites\": num_sites, \"taken\": round(time.time() - total_s, 3)})\n\n    def parseSearch(self, search):\n        parts = re.split(\"(site|type):\", search)\n        if len(parts) > 1:  # Found filter\n            search_text = parts[0]\n            parts = [part.strip() for part in parts]\n            filters = dict(zip(parts[1::2], parts[2::2]))\n        else:\n            search_text = search\n            filters = {}\n        return [search_text, filters]\n\n    def actionFeedSearch(self, to, search, limit=30, day_limit=30):\n        if \"ADMIN\" not in self.site.settings[\"permissions\"]:\n            return self.response(to, \"FeedSearch not allowed\")\n\n        from Site import SiteManager\n        rows = []\n        stats = []\n        num_sites = 0\n        total_s = time.time()\n\n        search_text, filters = self.parseSearch(search)\n\n        for address, site in SiteManager.site_manager.list().items():\n            if not site.storage.has_db:\n                continue\n\n            if \"site\" in filters:\n                if filters[\"site\"].lower() not in [site.address, site.content_manager.contents[\"content.json\"].get(\"title\").lower()]:\n                    continue\n\n            if site.storage.db:  # Database loaded\n                feeds = site.storage.db.schema.get(\"feeds\")\n            else:\n                try:\n                    feeds = site.storage.loadJson(\"dbschema.json\").get(\"feeds\")\n                except:\n                    continue\n\n            if not feeds:\n                continue\n\n            num_sites += 1\n\n            for name, query in feeds.items():\n                s = time.time()\n                try:\n                    db_query = DbQuery(query)\n\n                    params = []\n                    # Filters\n                    if search_text:\n                        db_query.wheres.append(\"(%s LIKE ? OR %s LIKE ?)\" % (db_query.fields[\"body\"], db_query.fields[\"title\"]))\n                        search_like = \"%\" + search_text.replace(\" \", \"%\") + \"%\"\n                        params.append(search_like)\n                        params.append(search_like)\n                    if filters.get(\"type\") and filters[\"type\"] not in query:\n                        continue\n\n                    if day_limit:\n                        db_query.wheres.append(\n                            \"%s > strftime('%%s', 'now', '-%s day')\" % (db_query.fields.get(\"date_added\", \"date_added\"), day_limit)\n                        )\n\n                    # Order\n                    db_query.parts[\"ORDER BY\"] = \"date_added DESC\"\n                    db_query.parts[\"LIMIT\"] = str(limit)\n\n                    res = site.storage.query(str(db_query), params)\n                except Exception as err:\n                    self.log.error(\"%s feed query %s error: %s\" % (address, name, Debug.formatException(err)))\n                    stats.append({\"site\": site.address, \"feed_name\": name, \"error\": str(err), \"query\": query})\n                    continue\n                for row in res:\n                    row = dict(row)\n                    if not row[\"date_added\"] or row[\"date_added\"] > time.time() + 120:\n                        continue  # Feed item is in the future, skip it\n                    row[\"site\"] = address\n                    row[\"feed_name\"] = name\n                    rows.append(row)\n                stats.append({\"site\": site.address, \"feed_name\": name, \"taken\": round(time.time() - s, 3)})\n        return self.response(to, {\"rows\": rows, \"num\": len(rows), \"sites\": num_sites, \"taken\": round(time.time() - total_s, 3), \"stats\": stats})\n\n\n@PluginManager.registerTo(\"User\")\nclass UserPlugin(object):\n    # Set queries that user follows\n    def setFeedFollow(self, address, feeds):\n        site_data = self.getSiteData(address)\n        site_data[\"follow\"] = feeds\n        self.save()\n        return site_data\n"
  },
  {
    "path": "plugins/Newsfeed/__init__.py",
    "content": "from . import NewsfeedPlugin"
  },
  {
    "path": "plugins/OptionalManager/ContentDbPlugin.py",
    "content": "import time\nimport collections\nimport itertools\nimport re\n\nimport gevent\n\nfrom util import helper\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\n\nif \"content_db\" not in locals().keys():  # To keep between module reloads\n    content_db = None\n\n\n@PluginManager.registerTo(\"ContentDb\")\nclass ContentDbPlugin(object):\n    def __init__(self, *args, **kwargs):\n        global content_db\n        content_db = self\n        self.filled = {}  # Site addresses that already filled from content.json\n        self.need_filling = False  # file_optional table just created, fill data from content.json files\n        self.time_peer_numbers_updated = 0\n        self.my_optional_files = {}  # Last 50 site_address/inner_path called by fileWrite (auto-pinning these files)\n        self.optional_files = collections.defaultdict(dict)\n        self.optional_files_loaded = False\n        self.timer_check_optional = helper.timer(60 * 5, self.checkOptionalLimit)\n        super(ContentDbPlugin, self).__init__(*args, **kwargs)\n\n    def getSchema(self):\n        schema = super(ContentDbPlugin, self).getSchema()\n\n        # Need file_optional table\n        schema[\"tables\"][\"file_optional\"] = {\n            \"cols\": [\n                [\"file_id\", \"INTEGER PRIMARY KEY UNIQUE NOT NULL\"],\n                [\"site_id\", \"INTEGER REFERENCES site (site_id) ON DELETE CASCADE\"],\n                [\"inner_path\", \"TEXT\"],\n                [\"hash_id\", \"INTEGER\"],\n                [\"size\", \"INTEGER\"],\n                [\"peer\", \"INTEGER DEFAULT 0\"],\n                [\"uploaded\", \"INTEGER DEFAULT 0\"],\n                [\"is_downloaded\", \"INTEGER DEFAULT 0\"],\n                [\"is_pinned\", \"INTEGER DEFAULT 0\"],\n                [\"time_added\", \"INTEGER DEFAULT 0\"],\n                [\"time_downloaded\", \"INTEGER DEFAULT 0\"],\n                [\"time_accessed\", \"INTEGER DEFAULT 0\"]\n            ],\n            \"indexes\": [\n                \"CREATE UNIQUE INDEX file_optional_key ON file_optional (site_id, inner_path)\",\n                \"CREATE INDEX is_downloaded ON file_optional (is_downloaded)\"\n            ],\n            \"schema_changed\": 11\n        }\n\n        return schema\n\n    def initSite(self, site):\n        super(ContentDbPlugin, self).initSite(site)\n        if self.need_filling:\n            self.fillTableFileOptional(site)\n\n    def checkTables(self):\n        changed_tables = super(ContentDbPlugin, self).checkTables()\n        if \"file_optional\" in changed_tables:\n            self.need_filling = True\n        return changed_tables\n\n    # Load optional files ending\n    def loadFilesOptional(self):\n        s = time.time()\n        num = 0\n        total = 0\n        total_downloaded = 0\n        res = content_db.execute(\"SELECT site_id, inner_path, size, is_downloaded FROM file_optional\")\n        site_sizes = collections.defaultdict(lambda: collections.defaultdict(int))\n        for row in res:\n            self.optional_files[row[\"site_id\"]][row[\"inner_path\"][-8:]] = 1\n            num += 1\n\n            # Update site size stats\n            site_sizes[row[\"site_id\"]][\"size_optional\"] += row[\"size\"]\n            if row[\"is_downloaded\"]:\n                site_sizes[row[\"site_id\"]][\"optional_downloaded\"] += row[\"size\"]\n\n        # Site site size stats to sites.json settings\n        site_ids_reverse = {val: key for key, val in self.site_ids.items()}\n        for site_id, stats in site_sizes.items():\n            site_address = site_ids_reverse.get(site_id)\n            if not site_address or site_address not in self.sites:\n                self.log.error(\"Not found site_id: %s\" % site_id)\n                continue\n            site = self.sites[site_address]\n            site.settings[\"size_optional\"] = stats[\"size_optional\"]\n            site.settings[\"optional_downloaded\"] = stats[\"optional_downloaded\"]\n            total += stats[\"size_optional\"]\n            total_downloaded += stats[\"optional_downloaded\"]\n\n        self.log.info(\n            \"Loaded %s optional files: %.2fMB, downloaded: %.2fMB in %.3fs\" %\n            (num, float(total) / 1024 / 1024, float(total_downloaded) / 1024 / 1024, time.time() - s)\n        )\n\n        if self.need_filling and self.getOptionalLimitBytes() >= 0 and self.getOptionalLimitBytes() < total_downloaded:\n            limit_bytes = self.getOptionalLimitBytes()\n            limit_new = round((float(total_downloaded) / 1024 / 1024 / 1024) * 1.1, 2)  # Current limit + 10%\n            self.log.info(\n                \"First startup after update and limit is smaller than downloaded files size (%.2fGB), increasing it from %.2fGB to %.2fGB\" %\n                (float(total_downloaded) / 1024 / 1024 / 1024, float(limit_bytes) / 1024 / 1024 / 1024, limit_new)\n            )\n            config.saveValue(\"optional_limit\", limit_new)\n            config.optional_limit = str(limit_new)\n\n    # Predicts if the file is optional\n    def isOptionalFile(self, site_id, inner_path):\n        return self.optional_files[site_id].get(inner_path[-8:])\n\n    # Fill file_optional table with optional files found in sites\n    def fillTableFileOptional(self, site):\n        s = time.time()\n        site_id = self.site_ids.get(site.address)\n        if not site_id:\n            return False\n        cur = self.getCursor()\n        res = cur.execute(\"SELECT * FROM content WHERE size_files_optional > 0 AND site_id = %s\" % site_id)\n        num = 0\n        for row in res.fetchall():\n            content = site.content_manager.contents[row[\"inner_path\"]]\n            try:\n                num += self.setContentFilesOptional(site, row[\"inner_path\"], content, cur=cur)\n            except Exception as err:\n                self.log.error(\"Error loading %s into file_optional: %s\" % (row[\"inner_path\"], err))\n        cur.close()\n\n        # Set my files to pinned\n        from User import UserManager\n        user = UserManager.user_manager.get()\n        if not user:\n            user = UserManager.user_manager.create()\n        auth_address = user.getAuthAddress(site.address)\n        res = self.execute(\n            \"UPDATE file_optional SET is_pinned = 1 WHERE site_id = :site_id AND inner_path LIKE :inner_path\",\n            {\"site_id\": site_id, \"inner_path\": \"%%/%s/%%\" % auth_address}\n        )\n\n        self.log.debug(\n            \"Filled file_optional table for %s in %.3fs (loaded: %s, is_pinned: %s)\" %\n            (site.address, time.time() - s, num, res.rowcount)\n        )\n        self.filled[site.address] = True\n\n    def setContentFilesOptional(self, site, content_inner_path, content, cur=None):\n        if not cur:\n            cur = self\n\n        num = 0\n        site_id = self.site_ids[site.address]\n        content_inner_dir = helper.getDirname(content_inner_path)\n        for relative_inner_path, file in content.get(\"files_optional\", {}).items():\n            file_inner_path = content_inner_dir + relative_inner_path\n            hash_id = int(file[\"sha512\"][0:4], 16)\n            if hash_id in site.content_manager.hashfield:\n                is_downloaded = 1\n            else:\n                is_downloaded = 0\n            if site.address + \"/\" + content_inner_dir in self.my_optional_files:\n                is_pinned = 1\n            else:\n                is_pinned = 0\n            cur.insertOrUpdate(\"file_optional\", {\n                \"hash_id\": hash_id,\n                \"size\": int(file[\"size\"])\n            }, {\n                \"site_id\": site_id,\n                \"inner_path\": file_inner_path\n            }, oninsert={\n                \"time_added\": int(time.time()),\n                \"time_downloaded\": int(time.time()) if is_downloaded else 0,\n                \"is_downloaded\": is_downloaded,\n                \"peer\": is_downloaded,\n                \"is_pinned\": is_pinned\n            })\n            self.optional_files[site_id][file_inner_path[-8:]] = 1\n            num += 1\n\n        return num\n\n    def setContent(self, site, inner_path, content, size=0):\n        super(ContentDbPlugin, self).setContent(site, inner_path, content, size=size)\n        old_content = site.content_manager.contents.get(inner_path, {})\n        if (not self.need_filling or self.filled.get(site.address)) and (\"files_optional\" in content or \"files_optional\" in old_content):\n            self.setContentFilesOptional(site, inner_path, content)\n            # Check deleted files\n            if old_content:\n                old_files = old_content.get(\"files_optional\", {}).keys()\n                new_files = content.get(\"files_optional\", {}).keys()\n                content_inner_dir = helper.getDirname(inner_path)\n                deleted = [content_inner_dir + key for key in old_files if key not in new_files]\n                if deleted:\n                    site_id = self.site_ids[site.address]\n                    self.execute(\"DELETE FROM file_optional WHERE ?\", {\"site_id\": site_id, \"inner_path\": deleted})\n\n    def deleteContent(self, site, inner_path):\n        content = site.content_manager.contents.get(inner_path)\n        if content and \"files_optional\" in content:\n            site_id = self.site_ids[site.address]\n            content_inner_dir = helper.getDirname(inner_path)\n            optional_inner_paths = [\n                content_inner_dir + relative_inner_path\n                for relative_inner_path in content.get(\"files_optional\", {}).keys()\n            ]\n            self.execute(\"DELETE FROM file_optional WHERE ?\", {\"site_id\": site_id, \"inner_path\": optional_inner_paths})\n        super(ContentDbPlugin, self).deleteContent(site, inner_path)\n\n    def updatePeerNumbers(self):\n        s = time.time()\n        num_file = 0\n        num_updated = 0\n        num_site = 0\n        for site in list(self.sites.values()):\n            if not site.content_manager.has_optional_files:\n                continue\n            if not site.isServing():\n                continue\n            has_updated_hashfield = next((\n                peer\n                for peer in site.peers.values()\n                if peer.has_hashfield and peer.hashfield.time_changed > self.time_peer_numbers_updated\n            ), None)\n\n            if not has_updated_hashfield and site.content_manager.hashfield.time_changed < self.time_peer_numbers_updated:\n                continue\n\n            hashfield_peers = itertools.chain.from_iterable(\n                peer.hashfield.storage\n                for peer in site.peers.values()\n                if peer.has_hashfield\n            )\n            peer_nums = collections.Counter(\n                itertools.chain(\n                    hashfield_peers,\n                    site.content_manager.hashfield\n                )\n            )\n\n            site_id = self.site_ids[site.address]\n            if not site_id:\n                continue\n\n            res = self.execute(\"SELECT file_id, hash_id, peer FROM file_optional WHERE ?\", {\"site_id\": site_id})\n            updates = {}\n            for row in res:\n                peer_num = peer_nums.get(row[\"hash_id\"], 0)\n                if peer_num != row[\"peer\"]:\n                    updates[row[\"file_id\"]] = peer_num\n\n            for file_id, peer_num in updates.items():\n                self.execute(\"UPDATE file_optional SET peer = ? WHERE file_id = ?\", (peer_num, file_id))\n\n            num_updated += len(updates)\n            num_file += len(peer_nums)\n            num_site += 1\n\n        self.time_peer_numbers_updated = time.time()\n        self.log.debug(\"%s/%s peer number for %s site updated in %.3fs\" % (num_updated, num_file, num_site, time.time() - s))\n\n    def queryDeletableFiles(self):\n        # First return the files with atleast 10 seeder and not accessed in last week\n        query = \"\"\"\n            SELECT * FROM file_optional\n            WHERE peer > 10 AND %s\n            ORDER BY time_accessed < %s DESC, uploaded / size\n        \"\"\" % (self.getOptionalUsedWhere(), int(time.time() - 60 * 60 * 7))\n        limit_start = 0\n        while 1:\n            num = 0\n            res = self.execute(\"%s LIMIT %s, 50\" % (query, limit_start))\n            for row in res:\n                yield row\n                num += 1\n            if num < 50:\n                break\n            limit_start += 50\n\n        self.log.debug(\"queryDeletableFiles returning less-seeded files\")\n\n        # Then return files less seeder but still not accessed in last week\n        query = \"\"\"\n            SELECT * FROM file_optional\n            WHERE peer <= 10 AND %s\n            ORDER BY peer DESC, time_accessed < %s DESC, uploaded / size\n        \"\"\" % (self.getOptionalUsedWhere(), int(time.time() - 60 * 60 * 7))\n        limit_start = 0\n        while 1:\n            num = 0\n            res = self.execute(\"%s LIMIT %s, 50\" % (query, limit_start))\n            for row in res:\n                yield row\n                num += 1\n            if num < 50:\n                break\n            limit_start += 50\n\n        self.log.debug(\"queryDeletableFiles returning everyting\")\n\n        # At the end return all files\n        query = \"\"\"\n            SELECT * FROM file_optional\n            WHERE peer <= 10 AND %s\n            ORDER BY peer DESC, time_accessed, uploaded / size\n        \"\"\" % self.getOptionalUsedWhere()\n        limit_start = 0\n        while 1:\n            num = 0\n            res = self.execute(\"%s LIMIT %s, 50\" % (query, limit_start))\n            for row in res:\n                yield row\n                num += 1\n            if num < 50:\n                break\n            limit_start += 50\n\n    def getOptionalLimitBytes(self):\n        if config.optional_limit.endswith(\"%\"):\n            limit_percent = float(re.sub(\"[^0-9.]\", \"\", config.optional_limit))\n            limit_bytes = helper.getFreeSpace() * (limit_percent / 100)\n        else:\n            limit_bytes = float(re.sub(\"[^0-9.]\", \"\", config.optional_limit)) * 1024 * 1024 * 1024\n        return limit_bytes\n\n    def getOptionalUsedWhere(self):\n        maxsize = config.optional_limit_exclude_minsize * 1024 * 1024\n        query = \"is_downloaded = 1 AND is_pinned = 0 AND size < %s\" % maxsize\n\n        # Don't delete optional files from owned sites\n        my_site_ids = []\n        for address, site in self.sites.items():\n            if site.settings[\"own\"]:\n                my_site_ids.append(str(self.site_ids[address]))\n\n        if my_site_ids:\n            query += \" AND site_id NOT IN (%s)\" % \", \".join(my_site_ids)\n        return query\n\n    def getOptionalUsedBytes(self):\n        size = self.execute(\"SELECT SUM(size) FROM file_optional WHERE %s\" % self.getOptionalUsedWhere()).fetchone()[0]\n        if not size:\n            size = 0\n        return size\n\n    def getOptionalNeedDelete(self, size):\n        if config.optional_limit.endswith(\"%\"):\n            limit_percent = float(re.sub(\"[^0-9.]\", \"\", config.optional_limit))\n            need_delete = size - ((helper.getFreeSpace() + size) * (limit_percent / 100))\n        else:\n            need_delete = size - self.getOptionalLimitBytes()\n        return need_delete\n\n    def checkOptionalLimit(self, limit=None):\n        if not limit:\n            limit = self.getOptionalLimitBytes()\n\n        if limit < 0:\n            self.log.debug(\"Invalid limit for optional files: %s\" % limit)\n            return False\n\n        size = self.getOptionalUsedBytes()\n\n        need_delete = self.getOptionalNeedDelete(size)\n\n        self.log.debug(\n            \"Optional size: %.1fMB/%.1fMB, Need delete: %.1fMB\" %\n            (float(size) / 1024 / 1024, float(limit) / 1024 / 1024, float(need_delete) / 1024 / 1024)\n        )\n        if need_delete <= 0:\n            return False\n\n        self.updatePeerNumbers()\n\n        site_ids_reverse = {val: key for key, val in self.site_ids.items()}\n        deleted_file_ids = []\n        for row in self.queryDeletableFiles():\n            site_address = site_ids_reverse.get(row[\"site_id\"])\n            site = self.sites.get(site_address)\n            if not site:\n                self.log.error(\"No site found for id: %s\" % row[\"site_id\"])\n                continue\n            site.log.debug(\"Deleting %s %.3f MB left\" % (row[\"inner_path\"], float(need_delete) / 1024 / 1024))\n            deleted_file_ids.append(row[\"file_id\"])\n            try:\n                site.content_manager.optionalRemoved(row[\"inner_path\"], row[\"hash_id\"], row[\"size\"])\n                site.storage.delete(row[\"inner_path\"])\n                need_delete -= row[\"size\"]\n            except Exception as err:\n                site.log.error(\"Error deleting %s: %s\" % (row[\"inner_path\"], err))\n\n            if need_delete <= 0:\n                break\n\n        cur = self.getCursor()\n        for file_id in deleted_file_ids:\n            cur.execute(\"UPDATE file_optional SET is_downloaded = 0, is_pinned = 0, peer = peer - 1 WHERE ?\", {\"file_id\": file_id})\n        cur.close()\n\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n    def load(self, *args, **kwargs):\n        back = super(SiteManagerPlugin, self).load(*args, **kwargs)\n        if self.sites and not content_db.optional_files_loaded and content_db.conn:\n            content_db.optional_files_loaded = True\n            content_db.loadFilesOptional()\n        return back"
  },
  {
    "path": "plugins/OptionalManager/OptionalManagerPlugin.py",
    "content": "import time\nimport re\nimport collections\n\nimport gevent\n\nfrom util import helper\nfrom Plugin import PluginManager\nfrom . import ContentDbPlugin\n\n\n# We can only import plugin host clases after the plugins are loaded\n@PluginManager.afterLoad\ndef importPluginnedClasses():\n    global config\n    from Config import config\n\n\ndef processAccessLog():\n    global access_log\n    if access_log:\n        content_db = ContentDbPlugin.content_db\n        if not content_db.conn:\n            return False\n\n        s = time.time()\n        access_log_prev = access_log\n        access_log = collections.defaultdict(dict)\n        now = int(time.time())\n        num = 0\n        for site_id in access_log_prev:\n            content_db.execute(\n                \"UPDATE file_optional SET time_accessed = %s WHERE ?\" % now,\n                {\"site_id\": site_id, \"inner_path\": list(access_log_prev[site_id].keys())}\n            )\n            num += len(access_log_prev[site_id])\n\n        content_db.log.debug(\"Inserted %s web request stat in %.3fs\" % (num, time.time() - s))\n\n\ndef processRequestLog():\n    global request_log\n    if request_log:\n        content_db = ContentDbPlugin.content_db\n        if not content_db.conn:\n            return False\n\n        s = time.time()\n        request_log_prev = request_log\n        request_log = collections.defaultdict(lambda: collections.defaultdict(int))  # {site_id: {inner_path1: 1, inner_path2: 1...}}\n        num = 0\n        for site_id in request_log_prev:\n            for inner_path, uploaded in request_log_prev[site_id].items():\n                content_db.execute(\n                    \"UPDATE file_optional SET uploaded = uploaded + %s WHERE ?\" % uploaded,\n                    {\"site_id\": site_id, \"inner_path\": inner_path}\n                )\n                num += 1\n        content_db.log.debug(\"Inserted %s file request stat in %.3fs\" % (num, time.time() - s))\n\n\nif \"access_log\" not in locals().keys():  # To keep between module reloads\n    access_log = collections.defaultdict(dict)  # {site_id: {inner_path1: 1, inner_path2: 1...}}\n    request_log = collections.defaultdict(lambda: collections.defaultdict(int))  # {site_id: {inner_path1: 1, inner_path2: 1...}}\n    helper.timer(61, processAccessLog)\n    helper.timer(60, processRequestLog)\n\n\n@PluginManager.registerTo(\"ContentManager\")\nclass ContentManagerPlugin(object):\n    def __init__(self, *args, **kwargs):\n        self.cache_is_pinned = {}\n        super(ContentManagerPlugin, self).__init__(*args, **kwargs)\n\n    def optionalDownloaded(self, inner_path, hash_id, size=None, own=False):\n        if \"|\" in inner_path:  # Big file piece\n            file_inner_path, file_range = inner_path.split(\"|\")\n        else:\n            file_inner_path = inner_path\n\n        self.contents.db.executeDelayed(\n            \"UPDATE file_optional SET time_downloaded = :now, is_downloaded = 1, peer = peer + 1 WHERE site_id = :site_id AND inner_path = :inner_path AND is_downloaded = 0\",\n            {\"now\": int(time.time()), \"site_id\": self.contents.db.site_ids[self.site.address], \"inner_path\": file_inner_path}\n        )\n\n        return super(ContentManagerPlugin, self).optionalDownloaded(inner_path, hash_id, size, own)\n\n    def optionalRemoved(self, inner_path, hash_id, size=None):\n        res = self.contents.db.execute(\n            \"UPDATE file_optional SET is_downloaded = 0, is_pinned = 0, peer = peer - 1 WHERE site_id = :site_id AND inner_path = :inner_path AND is_downloaded = 1\",\n            {\"site_id\": self.contents.db.site_ids[self.site.address], \"inner_path\": inner_path}\n        )\n\n        if res.rowcount > 0:\n            back = super(ContentManagerPlugin, self).optionalRemoved(inner_path, hash_id, size)\n            # Re-add to hashfield if we have other file with the same hash_id\n            if self.isDownloaded(hash_id=hash_id, force_check_db=True):\n                self.hashfield.appendHashId(hash_id)\n        else:\n            back = False\n        self.cache_is_pinned = {}\n        return back\n\n    def optionalRenamed(self, inner_path_old, inner_path_new):\n        back = super(ContentManagerPlugin, self).optionalRenamed(inner_path_old, inner_path_new)\n        self.cache_is_pinned = {}\n        self.contents.db.execute(\n            \"UPDATE file_optional SET inner_path = :inner_path_new WHERE site_id = :site_id AND inner_path = :inner_path_old\",\n            {\"site_id\": self.contents.db.site_ids[self.site.address], \"inner_path_old\": inner_path_old, \"inner_path_new\": inner_path_new}\n        )\n        return back\n\n    def isDownloaded(self, inner_path=None, hash_id=None, force_check_db=False):\n        if hash_id and not force_check_db and hash_id not in self.hashfield:\n            return False\n\n        if inner_path:\n            res = self.contents.db.execute(\n                \"SELECT is_downloaded FROM file_optional WHERE site_id = :site_id AND inner_path = :inner_path LIMIT 1\",\n                {\"site_id\": self.contents.db.site_ids[self.site.address], \"inner_path\": inner_path}\n            )\n        else:\n            res = self.contents.db.execute(\n                \"SELECT is_downloaded FROM file_optional WHERE site_id = :site_id AND hash_id = :hash_id AND is_downloaded = 1 LIMIT 1\",\n                {\"site_id\": self.contents.db.site_ids[self.site.address], \"hash_id\": hash_id}\n            )\n        row = res.fetchone()\n        if row and row[\"is_downloaded\"]:\n            return True\n        else:\n            return False\n\n    def isPinned(self, inner_path):\n        if inner_path in self.cache_is_pinned:\n            self.site.log.debug(\"Cached is pinned: %s\" % inner_path)\n            return self.cache_is_pinned[inner_path]\n\n        res = self.contents.db.execute(\n            \"SELECT is_pinned FROM file_optional WHERE site_id = :site_id AND inner_path = :inner_path LIMIT 1\",\n            {\"site_id\": self.contents.db.site_ids[self.site.address], \"inner_path\": inner_path}\n        )\n        row = res.fetchone()\n\n        if row and row[0]:\n            is_pinned = True\n        else:\n            is_pinned = False\n\n        self.cache_is_pinned[inner_path] = is_pinned\n        self.site.log.debug(\"Cache set is pinned: %s %s\" % (inner_path, is_pinned))\n\n        return is_pinned\n\n    def setPin(self, inner_path, is_pinned):\n        content_db = self.contents.db\n        site_id = content_db.site_ids[self.site.address]\n        content_db.execute(\"UPDATE file_optional SET is_pinned = %d WHERE ?\" % is_pinned, {\"site_id\": site_id, \"inner_path\": inner_path})\n        self.cache_is_pinned = {}\n\n    def optionalDelete(self, inner_path):\n        if self.isPinned(inner_path):\n            self.site.log.debug(\"Skip deleting pinned optional file: %s\" % inner_path)\n            return False\n        else:\n            return super(ContentManagerPlugin, self).optionalDelete(inner_path)\n\n\n@PluginManager.registerTo(\"WorkerManager\")\nclass WorkerManagerPlugin(object):\n    def doneTask(self, task):\n        super(WorkerManagerPlugin, self).doneTask(task)\n\n        if task[\"optional_hash_id\"] and not self.tasks:  # Execute delayed queries immedietly after tasks finished\n            ContentDbPlugin.content_db.processDelayed()\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def parsePath(self, path):\n        global access_log\n        path_parts = super(UiRequestPlugin, self).parsePath(path)\n        if path_parts:\n            site_id = ContentDbPlugin.content_db.site_ids.get(path_parts[\"request_address\"])\n            if site_id:\n                if ContentDbPlugin.content_db.isOptionalFile(site_id, path_parts[\"inner_path\"]):\n                    access_log[site_id][path_parts[\"inner_path\"]] = 1\n        return path_parts\n\n\n@PluginManager.registerTo(\"FileRequest\")\nclass FileRequestPlugin(object):\n    def actionGetFile(self, params):\n        stats = super(FileRequestPlugin, self).actionGetFile(params)\n        self.recordFileRequest(params[\"site\"], params[\"inner_path\"], stats)\n        return stats\n\n    def actionStreamFile(self, params):\n        stats = super(FileRequestPlugin, self).actionStreamFile(params)\n        self.recordFileRequest(params[\"site\"], params[\"inner_path\"], stats)\n        return stats\n\n    def recordFileRequest(self, site_address, inner_path, stats):\n        if not stats:\n            # Only track the last request of files\n            return False\n        site_id = ContentDbPlugin.content_db.site_ids[site_address]\n        if site_id and ContentDbPlugin.content_db.isOptionalFile(site_id, inner_path):\n            request_log[site_id][inner_path] += stats[\"bytes_sent\"]\n\n\n@PluginManager.registerTo(\"Site\")\nclass SitePlugin(object):\n    def isDownloadable(self, inner_path):\n        is_downloadable = super(SitePlugin, self).isDownloadable(inner_path)\n        if is_downloadable:\n            return is_downloadable\n\n        for path in self.settings.get(\"optional_help\", {}).keys():\n            if inner_path.startswith(path):\n                return True\n\n        return False\n\n    def fileForgot(self, inner_path):\n        if \"|\" in inner_path and self.content_manager.isPinned(re.sub(r\"\\|.*\", \"\", inner_path)):\n            self.log.debug(\"File %s is pinned, no fileForgot\" % inner_path)\n            return False\n        else:\n            return super(SitePlugin, self).fileForgot(inner_path)\n\n    def fileDone(self, inner_path):\n        if \"|\" in inner_path and self.bad_files.get(inner_path, 0) > 5:  # Idle optional file done\n            inner_path_file = re.sub(r\"\\|.*\", \"\", inner_path)\n            num_changed = 0\n            for key, val in self.bad_files.items():\n                if key.startswith(inner_path_file) and val > 1:\n                    self.bad_files[key] = 1\n                    num_changed += 1\n            self.log.debug(\"Idle optional file piece done, changed retry number of %s pieces.\" % num_changed)\n            if num_changed:\n                gevent.spawn(self.retryBadFiles)\n\n        return super(SitePlugin, self).fileDone(inner_path)\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"OptionalManager plugin\")\n        group.add_argument('--optional_limit', help='Limit total size of optional files', default=\"10%\", metavar=\"GB or free space %\")\n        group.add_argument('--optional_limit_exclude_minsize', help='Exclude files larger than this limit from optional size limit calculation', default=20, metavar=\"MB\", type=int)\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/OptionalManager/Test/TestOptionalManager.py",
    "content": "import copy\n\nimport pytest\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestOptionalManager:\n    def testDbFill(self, site):\n        contents = site.content_manager.contents\n        assert len(site.content_manager.hashfield) > 0\n        assert contents.db.execute(\"SELECT COUNT(*) FROM file_optional WHERE is_downloaded = 1\").fetchone()[0] == len(site.content_manager.hashfield)\n\n    def testSetContent(self, site):\n        contents = site.content_manager.contents\n\n        # Add new file\n        new_content = copy.deepcopy(contents[\"content.json\"])\n        new_content[\"files_optional\"][\"testfile\"] = {\n            \"size\": 1234,\n            \"sha512\": \"aaaabbbbcccc\"\n        }\n        num_optional_files_before = contents.db.execute(\"SELECT COUNT(*) FROM file_optional\").fetchone()[0]\n        contents[\"content.json\"] = new_content\n        assert contents.db.execute(\"SELECT COUNT(*) FROM file_optional\").fetchone()[0] > num_optional_files_before\n\n        # Remove file\n        new_content = copy.deepcopy(contents[\"content.json\"])\n        del new_content[\"files_optional\"][\"testfile\"]\n        num_optional_files_before = contents.db.execute(\"SELECT COUNT(*) FROM file_optional\").fetchone()[0]\n        contents[\"content.json\"] = new_content\n        assert contents.db.execute(\"SELECT COUNT(*) FROM file_optional\").fetchone()[0] < num_optional_files_before\n\n    def testDeleteContent(self, site):\n        contents = site.content_manager.contents\n        num_optional_files_before = contents.db.execute(\"SELECT COUNT(*) FROM file_optional\").fetchone()[0]\n        del contents[\"content.json\"]\n        assert contents.db.execute(\"SELECT COUNT(*) FROM file_optional\").fetchone()[0] < num_optional_files_before\n\n    def testVerifyFiles(self, site):\n        contents = site.content_manager.contents\n\n        # Add new file\n        new_content = copy.deepcopy(contents[\"content.json\"])\n        new_content[\"files_optional\"][\"testfile\"] = {\n            \"size\": 1234,\n            \"sha512\": \"aaaabbbbcccc\"\n        }\n        contents[\"content.json\"] = new_content\n        file_row = contents.db.execute(\"SELECT * FROM file_optional WHERE inner_path = 'testfile'\").fetchone()\n        assert not file_row[\"is_downloaded\"]\n\n        # Write file from outside of ZeroNet\n        site.storage.open(\"testfile\", \"wb\").write(b\"A\" * 1234)  # For quick check hash does not matter only file size\n\n        hashfield_len_before = len(site.content_manager.hashfield)\n        site.storage.verifyFiles(quick_check=True)\n        assert len(site.content_manager.hashfield) == hashfield_len_before + 1\n\n        file_row = contents.db.execute(\"SELECT * FROM file_optional WHERE inner_path = 'testfile'\").fetchone()\n        assert file_row[\"is_downloaded\"]\n\n        # Delete file outside of ZeroNet\n        site.storage.delete(\"testfile\")\n        site.storage.verifyFiles(quick_check=True)\n        file_row = contents.db.execute(\"SELECT * FROM file_optional WHERE inner_path = 'testfile'\").fetchone()\n        assert not file_row[\"is_downloaded\"]\n\n    def testVerifyFilesSameHashId(self, site):\n        contents = site.content_manager.contents\n\n        new_content = copy.deepcopy(contents[\"content.json\"])\n\n        # Add two files with same hashid (first 4 character)\n        new_content[\"files_optional\"][\"testfile1\"] = {\n            \"size\": 1234,\n            \"sha512\": \"aaaabbbbcccc\"\n        }\n        new_content[\"files_optional\"][\"testfile2\"] = {\n            \"size\": 2345,\n            \"sha512\": \"aaaabbbbdddd\"\n        }\n        contents[\"content.json\"] = new_content\n\n        assert site.content_manager.hashfield.getHashId(\"aaaabbbbcccc\") == site.content_manager.hashfield.getHashId(\"aaaabbbbdddd\")\n\n        # Write files from outside of ZeroNet (For quick check hash does not matter only file size)\n        site.storage.open(\"testfile1\", \"wb\").write(b\"A\" * 1234)\n        site.storage.open(\"testfile2\", \"wb\").write(b\"B\" * 2345)\n\n        site.storage.verifyFiles(quick_check=True)\n\n        # Make sure that both is downloaded\n        assert site.content_manager.isDownloaded(\"testfile1\")\n        assert site.content_manager.isDownloaded(\"testfile2\")\n        assert site.content_manager.hashfield.getHashId(\"aaaabbbbcccc\") in site.content_manager.hashfield\n\n        # Delete one of the files\n        site.storage.delete(\"testfile1\")\n        site.storage.verifyFiles(quick_check=True)\n        assert not site.content_manager.isDownloaded(\"testfile1\")\n        assert site.content_manager.isDownloaded(\"testfile2\")\n        assert site.content_manager.hashfield.getHashId(\"aaaabbbbdddd\") in site.content_manager.hashfield\n\n    def testIsPinned(self, site):\n        assert not site.content_manager.isPinned(\"data/img/zerotalk-upvote.png\")\n        site.content_manager.setPin(\"data/img/zerotalk-upvote.png\", True)\n        assert site.content_manager.isPinned(\"data/img/zerotalk-upvote.png\")\n\n        assert len(site.content_manager.cache_is_pinned) == 1\n        site.content_manager.cache_is_pinned = {}\n        assert site.content_manager.isPinned(\"data/img/zerotalk-upvote.png\")\n\n    def testBigfilePieceReset(self, site):\n        site.bad_files = {\n            \"data/fake_bigfile.mp4|0-1024\": 10,\n            \"data/fake_bigfile.mp4|1024-2048\": 10,\n            \"data/fake_bigfile.mp4|2048-3064\": 10\n        }\n        site.onFileDone(\"data/fake_bigfile.mp4|0-1024\")\n        assert site.bad_files[\"data/fake_bigfile.mp4|1024-2048\"] == 1\n        assert site.bad_files[\"data/fake_bigfile.mp4|2048-3064\"] == 1\n\n    def testOptionalDelete(self, site):\n        contents = site.content_manager.contents\n\n        site.content_manager.setPin(\"data/img/zerotalk-upvote.png\", True)\n        site.content_manager.setPin(\"data/img/zeroid.png\", False)\n        new_content = copy.deepcopy(contents[\"content.json\"])\n        del new_content[\"files_optional\"][\"data/img/zerotalk-upvote.png\"]\n        del new_content[\"files_optional\"][\"data/img/zeroid.png\"]\n\n        assert site.storage.isFile(\"data/img/zerotalk-upvote.png\")\n        assert site.storage.isFile(\"data/img/zeroid.png\")\n\n        site.storage.writeJson(\"content.json\", new_content)\n        site.content_manager.loadContent(\"content.json\", force=True)\n\n        assert not site.storage.isFile(\"data/img/zeroid.png\")\n        assert site.storage.isFile(\"data/img/zerotalk-upvote.png\")\n\n    def testOptionalRename(self, site):\n        contents = site.content_manager.contents\n\n        site.content_manager.setPin(\"data/img/zerotalk-upvote.png\", True)\n        new_content = copy.deepcopy(contents[\"content.json\"])\n        new_content[\"files_optional\"][\"data/img/zerotalk-upvote-new.png\"] = new_content[\"files_optional\"][\"data/img/zerotalk-upvote.png\"]\n        del new_content[\"files_optional\"][\"data/img/zerotalk-upvote.png\"]\n\n        assert site.storage.isFile(\"data/img/zerotalk-upvote.png\")\n        assert site.content_manager.isPinned(\"data/img/zerotalk-upvote.png\")\n\n        site.storage.writeJson(\"content.json\", new_content)\n        site.content_manager.loadContent(\"content.json\", force=True)\n\n        assert not site.storage.isFile(\"data/img/zerotalk-upvote.png\")\n        assert not site.content_manager.isPinned(\"data/img/zerotalk-upvote.png\")\n        assert site.content_manager.isPinned(\"data/img/zerotalk-upvote-new.png\")\n        assert site.storage.isFile(\"data/img/zerotalk-upvote-new.png\")\n"
  },
  {
    "path": "plugins/OptionalManager/Test/conftest.py",
    "content": "from src.Test.conftest import *"
  },
  {
    "path": "plugins/OptionalManager/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/OptionalManager/UiWebsocketPlugin.py",
    "content": "import re\nimport time\nimport html\nimport os\n\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom util import helper\nfrom util.Flag import flag\nfrom Translate import Translate\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\nbigfile_sha512_cache = {}\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def __init__(self, *args, **kwargs):\n        self.time_peer_numbers_updated = 0\n        super(UiWebsocketPlugin, self).__init__(*args, **kwargs)\n\n    def actionSiteSign(self, to, privatekey=None, inner_path=\"content.json\", *args, **kwargs):\n        # Add file to content.db and set it as pinned\n        content_db = self.site.content_manager.contents.db\n        content_inner_dir = helper.getDirname(inner_path)\n        content_db.my_optional_files[self.site.address + \"/\" + content_inner_dir] = time.time()\n        if len(content_db.my_optional_files) > 50:  # Keep only last 50\n            oldest_key = min(\n                iter(content_db.my_optional_files.keys()),\n                key=(lambda key: content_db.my_optional_files[key])\n            )\n            del content_db.my_optional_files[oldest_key]\n\n        return super(UiWebsocketPlugin, self).actionSiteSign(to, privatekey, inner_path, *args, **kwargs)\n\n    def updatePeerNumbers(self):\n        self.site.updateHashfield()\n        content_db = self.site.content_manager.contents.db\n        content_db.updatePeerNumbers()\n        self.site.updateWebsocket(peernumber_updated=True)\n\n    def addBigfileInfo(self, row):\n        global bigfile_sha512_cache\n\n        content_db = self.site.content_manager.contents.db\n        site = content_db.sites[row[\"address\"]]\n        if not site.settings.get(\"has_bigfile\"):\n            return False\n\n        file_key = row[\"address\"] + \"/\" + row[\"inner_path\"]\n        sha512 = bigfile_sha512_cache.get(file_key)\n        file_info = None\n        if not sha512:\n            file_info = site.content_manager.getFileInfo(row[\"inner_path\"])\n            if not file_info or not file_info.get(\"piece_size\"):\n                return False\n            sha512 = file_info[\"sha512\"]\n            bigfile_sha512_cache[file_key] = sha512\n\n        if sha512 in site.storage.piecefields:\n            piecefield = site.storage.piecefields[sha512].tobytes()\n        else:\n            piecefield = None\n\n        if piecefield:\n            row[\"pieces\"] = len(piecefield)\n            row[\"pieces_downloaded\"] = piecefield.count(b\"\\x01\")\n            row[\"downloaded_percent\"] = 100 * row[\"pieces_downloaded\"] / row[\"pieces\"]\n            if row[\"pieces_downloaded\"]:\n                if row[\"pieces\"] == row[\"pieces_downloaded\"]:\n                    row[\"bytes_downloaded\"] = row[\"size\"]\n                else:\n                    if not file_info:\n                        file_info = site.content_manager.getFileInfo(row[\"inner_path\"])\n                    row[\"bytes_downloaded\"] = row[\"pieces_downloaded\"] * file_info.get(\"piece_size\", 0)\n            else:\n                row[\"bytes_downloaded\"] = 0\n\n            row[\"is_downloading\"] = bool(next((inner_path for inner_path in site.bad_files if inner_path.startswith(row[\"inner_path\"])), False))\n\n        # Add leech / seed stats\n        row[\"peer_seed\"] = 0\n        row[\"peer_leech\"] = 0\n        for peer in site.peers.values():\n            if not peer.time_piecefields_updated or sha512 not in peer.piecefields:\n                continue\n            peer_piecefield = peer.piecefields[sha512].tobytes()\n            if not peer_piecefield:\n                continue\n            if peer_piecefield == b\"\\x01\" * len(peer_piecefield):\n                row[\"peer_seed\"] += 1\n            else:\n                row[\"peer_leech\"] += 1\n\n        # Add myself\n        if piecefield:\n            if row[\"pieces_downloaded\"] == row[\"pieces\"]:\n                row[\"peer_seed\"] += 1\n            else:\n                row[\"peer_leech\"] += 1\n\n        return True\n\n    # Optional file functions\n\n    def actionOptionalFileList(self, to, address=None, orderby=\"time_downloaded DESC\", limit=10, filter=\"downloaded\", filter_inner_path=None):\n        if not address:\n            address = self.site.address\n\n        # Update peer numbers if necessary\n        content_db = self.site.content_manager.contents.db\n        if time.time() - content_db.time_peer_numbers_updated > 60 * 1 and time.time() - self.time_peer_numbers_updated > 60 * 5:\n            # Start in new thread to avoid blocking\n            self.time_peer_numbers_updated = time.time()\n            gevent.spawn(self.updatePeerNumbers)\n\n        if address == \"all\" and \"ADMIN\" not in self.permissions:\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        if not self.hasSitePermission(address):\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        if not all([re.match(\"^[a-z_*/+-]+( DESC| ASC|)$\", part.strip()) for part in orderby.split(\",\")]):\n            return self.response(to, \"Invalid order_by\")\n\n        if type(limit) != int:\n            return self.response(to, \"Invalid limit\")\n\n        back = []\n        content_db = self.site.content_manager.contents.db\n\n        wheres = {}\n        wheres_raw = []\n        if \"bigfile\" in filter:\n            wheres[\"size >\"] = 1024 * 1024 * 1\n        if \"downloaded\" in filter:\n            wheres_raw.append(\"(is_downloaded = 1 OR is_pinned = 1)\")\n        if \"pinned\" in filter:\n            wheres[\"is_pinned\"] = 1\n        if filter_inner_path:\n            wheres[\"inner_path__like\"] = filter_inner_path\n\n        if address == \"all\":\n            join = \"LEFT JOIN site USING (site_id)\"\n        else:\n            wheres[\"site_id\"] = content_db.site_ids[address]\n            join = \"\"\n\n        if wheres_raw:\n            query_wheres_raw = \"AND\" + \" AND \".join(wheres_raw)\n        else:\n            query_wheres_raw = \"\"\n\n        query = \"SELECT * FROM file_optional %s WHERE ? %s ORDER BY %s LIMIT %s\" % (join, query_wheres_raw, orderby, limit)\n\n        for row in content_db.execute(query, wheres):\n            row = dict(row)\n            if address != \"all\":\n                row[\"address\"] = address\n\n            if row[\"size\"] > 1024 * 1024:\n                has_bigfile_info = self.addBigfileInfo(row)\n            else:\n                has_bigfile_info = False\n\n            if not has_bigfile_info and \"bigfile\" in filter:\n                continue\n\n            if not has_bigfile_info:\n                if row[\"is_downloaded\"]:\n                    row[\"bytes_downloaded\"] = row[\"size\"]\n                    row[\"downloaded_percent\"] = 100\n                else:\n                    row[\"bytes_downloaded\"] = 0\n                    row[\"downloaded_percent\"] = 0\n\n            back.append(row)\n        self.response(to, back)\n\n    def actionOptionalFileInfo(self, to, inner_path):\n        content_db = self.site.content_manager.contents.db\n        site_id = content_db.site_ids[self.site.address]\n\n        # Update peer numbers if necessary\n        if time.time() - content_db.time_peer_numbers_updated > 60 * 1 and time.time() - self.time_peer_numbers_updated > 60 * 5:\n            # Start in new thread to avoid blocking\n            self.time_peer_numbers_updated = time.time()\n            gevent.spawn(self.updatePeerNumbers)\n\n        query = \"SELECT * FROM file_optional WHERE site_id = :site_id AND inner_path = :inner_path LIMIT 1\"\n        res = content_db.execute(query, {\"site_id\": site_id, \"inner_path\": inner_path})\n        row = next(res, None)\n        if row:\n            row = dict(row)\n            if row[\"size\"] > 1024 * 1024:\n                row[\"address\"] = self.site.address\n                self.addBigfileInfo(row)\n            self.response(to, row)\n        else:\n            self.response(to, None)\n\n    def setPin(self, inner_path, is_pinned, address=None):\n        if not address:\n            address = self.site.address\n\n        if not self.hasSitePermission(address):\n            return {\"error\": \"Forbidden\"}\n\n        site = self.server.sites[address]\n        site.content_manager.setPin(inner_path, is_pinned)\n\n        return \"ok\"\n\n    @flag.no_multiuser\n    def actionOptionalFilePin(self, to, inner_path, address=None):\n        if type(inner_path) is not list:\n            inner_path = [inner_path]\n        back = self.setPin(inner_path, 1, address)\n        num_file = len(inner_path)\n        if back == \"ok\":\n            if num_file == 1:\n                self.cmd(\"notification\", [\"done\", _[\"Pinned %s\"] % html.escape(helper.getFilename(inner_path[0])), 5000])\n            else:\n                self.cmd(\"notification\", [\"done\", _[\"Pinned %s files\"] % num_file, 5000])\n        self.response(to, back)\n\n    @flag.no_multiuser\n    def actionOptionalFileUnpin(self, to, inner_path, address=None):\n        if type(inner_path) is not list:\n            inner_path = [inner_path]\n        back = self.setPin(inner_path, 0, address)\n        num_file = len(inner_path)\n        if back == \"ok\":\n            if num_file == 1:\n                self.cmd(\"notification\", [\"done\", _[\"Removed pin from %s\"] % html.escape(helper.getFilename(inner_path[0])), 5000])\n            else:\n                self.cmd(\"notification\", [\"done\", _[\"Removed pin from %s files\"] % num_file, 5000])\n        self.response(to, back)\n\n    @flag.no_multiuser\n    def actionOptionalFileDelete(self, to, inner_path, address=None):\n        if not address:\n            address = self.site.address\n\n        if not self.hasSitePermission(address):\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        site = self.server.sites[address]\n\n        content_db = site.content_manager.contents.db\n        site_id = content_db.site_ids[site.address]\n\n        res = content_db.execute(\"SELECT * FROM file_optional WHERE ? LIMIT 1\", {\"site_id\": site_id, \"inner_path\": inner_path, \"is_downloaded\": 1})\n        row = next(res, None)\n\n        if not row:\n            return self.response(to, {\"error\": \"Not found in content.db\"})\n\n        removed = site.content_manager.optionalRemoved(inner_path, row[\"hash_id\"], row[\"size\"])\n        # if not removed:\n        #    return self.response(to, {\"error\": \"Not found in hash_id: %s\" % row[\"hash_id\"]})\n\n        content_db.execute(\"UPDATE file_optional SET is_downloaded = 0, is_pinned = 0, peer = peer - 1 WHERE ?\", {\"site_id\": site_id, \"inner_path\": inner_path})\n\n        try:\n            site.storage.delete(inner_path)\n        except Exception as err:\n            return self.response(to, {\"error\": \"File delete error: %s\" % err})\n        site.updateWebsocket(file_delete=inner_path)\n\n        if inner_path in site.content_manager.cache_is_pinned:\n            site.content_manager.cache_is_pinned = {}\n\n        self.response(to, \"ok\")\n\n    # Limit functions\n\n    @flag.admin\n    def actionOptionalLimitStats(self, to):\n        back = {}\n        back[\"limit\"] = config.optional_limit\n        back[\"used\"] = self.site.content_manager.contents.db.getOptionalUsedBytes()\n        back[\"free\"] = helper.getFreeSpace()\n\n        self.response(to, back)\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionOptionalLimitSet(self, to, limit):\n        config.optional_limit = re.sub(r\"\\.0+$\", \"\", limit)  # Remove unnecessary digits from end\n        config.saveValue(\"optional_limit\", limit)\n        self.response(to, \"ok\")\n\n    # Distribute help functions\n\n    def actionOptionalHelpList(self, to, address=None):\n        if not address:\n            address = self.site.address\n\n        if not self.hasSitePermission(address):\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        site = self.server.sites[address]\n\n        self.response(to, site.settings.get(\"optional_help\", {}))\n\n    @flag.no_multiuser\n    def actionOptionalHelp(self, to, directory, title, address=None):\n        if not address:\n            address = self.site.address\n\n        if not self.hasSitePermission(address):\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        site = self.server.sites[address]\n        content_db = site.content_manager.contents.db\n        site_id = content_db.site_ids[address]\n\n        if \"optional_help\" not in site.settings:\n            site.settings[\"optional_help\"] = {}\n\n        stats = content_db.execute(\n            \"SELECT COUNT(*) AS num, SUM(size) AS size FROM file_optional WHERE site_id = :site_id AND inner_path LIKE :inner_path\",\n            {\"site_id\": site_id, \"inner_path\": directory + \"%\"}\n        ).fetchone()\n        stats = dict(stats)\n\n        if not stats[\"size\"]:\n            stats[\"size\"] = 0\n        if not stats[\"num\"]:\n            stats[\"num\"] = 0\n\n        self.cmd(\"notification\", [\n            \"done\",\n            _[\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\"] %\n            (html.escape(title), html.escape(directory)),\n            10000\n        ])\n\n        site.settings[\"optional_help\"][directory] = title\n\n        self.response(to, dict(stats))\n\n    @flag.no_multiuser\n    def actionOptionalHelpRemove(self, to, directory, address=None):\n        if not address:\n            address = self.site.address\n\n        if not self.hasSitePermission(address):\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        site = self.server.sites[address]\n\n        try:\n            del site.settings[\"optional_help\"][directory]\n            self.response(to, \"ok\")\n        except Exception:\n            self.response(to, {\"error\": \"Not found\"})\n\n    def cbOptionalHelpAll(self, to, site, value):\n        site.settings[\"autodownloadoptional\"] = value\n        self.response(to, value)\n\n    @flag.no_multiuser\n    def actionOptionalHelpAll(self, to, value, address=None):\n        if not address:\n            address = self.site.address\n\n        if not self.hasSitePermission(address):\n            return self.response(to, {\"error\": \"Forbidden\"})\n\n        site = self.server.sites[address]\n\n        if value:\n            if \"ADMIN\" in self.site.settings[\"permissions\"]:\n                self.cbOptionalHelpAll(to, site, True)\n            else:\n                site_title = site.content_manager.contents[\"content.json\"].get(\"title\", address)\n                self.cmd(\n                    \"confirm\",\n                    [\n                        _[\"Help distribute all new optional files on site <b>%s</b>\"] % html.escape(site_title),\n                        _[\"Yes, I want to help!\"]\n                    ],\n                    lambda res: self.cbOptionalHelpAll(to, site, True)\n                )\n        else:\n            site.settings[\"autodownloadoptional\"] = False\n            self.response(to, False)\n"
  },
  {
    "path": "plugins/OptionalManager/__init__.py",
    "content": "from . import OptionalManagerPlugin\nfrom . import UiWebsocketPlugin\n"
  },
  {
    "path": "plugins/OptionalManager/languages/es.json",
    "content": "{\n\t\"Pinned %s files\": \"Archivos %s fijados\",\n\t\"Removed pin from %s files\": \"Archivos %s que no estan fijados\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"Tu empezaste a ayudar a distribuir <b>%s</b>.<br><small>Directorio: %s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"Ayude a distribuir todos los archivos opcionales en el sitio <b>%s</b>\",\n\t\"Yes, I want to help!\": \"¡Si, yo quiero ayudar!\"\n}\n"
  },
  {
    "path": "plugins/OptionalManager/languages/fr.json",
    "content": "{\n\t\"Pinned %s files\": \"Fichiers %s épinglés\",\n\t\"Removed pin from %s files\": \"Fichiers %s ne sont plus épinglés\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"Vous avez commencé à aider à distribuer <b>%s</b>.<br><small>Dossier : %s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"Aider à distribuer tous les fichiers optionnels du site <b>%s</b>\",\n\t\"Yes, I want to help!\": \"Oui, je veux aider !\"\n}\n"
  },
  {
    "path": "plugins/OptionalManager/languages/hu.json",
    "content": "{\n\t\"Pinned %s files\": \"%s fájl rögzítve\",\n\t\"Removed pin from %s files\": \"%s fájl rögzítés eltávolítva\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"Új segítség a terjesztésben: <b>%s</b>.<br><small>Könyvtár: %s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"Segítség az összes új opcionális fájl terjesztésében az <b>%s</b> oldalon\",\n\t\"Yes, I want to help!\": \"Igen, segíteni akarok!\"\n}\n"
  },
  {
    "path": "plugins/OptionalManager/languages/jp.json",
    "content": "{\n\t\"Pinned %s files\": \"%s 件のファイルを固定\",\n\t\"Removed pin from %s files\": \"%s 件のファイルの固定を解除\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"あなたはサイト: <b>%s</b> の配布の援助を開始しました。<br><small>ディレクトリ: %s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"サイト: <b>%s</b> のすべての新しいオプションファイルの配布を援助しますか？\",\n\t\"Yes, I want to help!\": \"はい、やります！\"\n}\n"
  },
  {
    "path": "plugins/OptionalManager/languages/pt-br.json",
    "content": "{\n\t\"Pinned %s files\": \"Arquivos %s fixados\",\n\t\"Removed pin from %s files\": \"Arquivos %s não estão fixados\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"Você começou a ajudar a distribuir <b>%s</b>.<br><small>Pasta: %s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"Ajude a distribuir todos os novos arquivos opcionais no site <b>%s</b>\",\n\t\"Yes, I want to help!\": \"Sim, eu quero ajudar!\"\n}\n"
  },
  {
    "path": "plugins/OptionalManager/languages/zh-tw.json",
    "content": "{\n\t\"Pinned %s files\": \"已固定 %s 個檔\",\n\t\"Removed pin from %s files\": \"已解除固定 %s 個檔\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"你已經開始幫助分發 <b>%s</b> 。<br><small>目錄：%s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"你想要幫助分發 <b>%s</b> 網站的所有檔嗎？\",\n\t\"Yes, I want to help!\": \"是，我想要幫助！\"\n}\n"
  },
  {
    "path": "plugins/OptionalManager/languages/zh.json",
    "content": "{\n\t\"Pinned %s files\": \"已固定 %s 个文件\",\n\t\"Removed pin from %s files\": \"已解除固定 %s 个文件\",\n\t\"You started to help distribute <b>%s</b>.<br><small>Directory: %s</small>\": \"您已经开始帮助分发 <b>%s</b> 。<br><small>目录：%s</small>\",\n\t\"Help distribute all new optional files on site <b>%s</b>\": \"您想要帮助分发 <b>%s</b> 站点的所有文件吗？\",\n\t\"Yes, I want to help!\": \"是，我想要帮助！\"\n}\n"
  },
  {
    "path": "plugins/PeerDb/PeerDbPlugin.py",
    "content": "import time\nimport sqlite3\nimport random\nimport atexit\n\nimport gevent\nfrom Plugin import PluginManager\n\n\n@PluginManager.registerTo(\"ContentDb\")\nclass ContentDbPlugin(object):\n    def __init__(self, *args, **kwargs):\n        atexit.register(self.saveAllPeers)\n        super(ContentDbPlugin, self).__init__(*args, **kwargs)\n\n    def getSchema(self):\n        schema = super(ContentDbPlugin, self).getSchema()\n\n        schema[\"tables\"][\"peer\"] = {\n            \"cols\": [\n                [\"site_id\", \"INTEGER REFERENCES site (site_id) ON DELETE CASCADE\"],\n                [\"address\", \"TEXT NOT NULL\"],\n                [\"port\", \"INTEGER NOT NULL\"],\n                [\"hashfield\", \"BLOB\"],\n                [\"reputation\", \"INTEGER NOT NULL\"],\n                [\"time_added\", \"INTEGER NOT NULL\"],\n                [\"time_found\", \"INTEGER NOT NULL\"]\n            ],\n            \"indexes\": [\n                \"CREATE UNIQUE INDEX peer_key ON peer (site_id, address, port)\"\n            ],\n            \"schema_changed\": 2\n        }\n\n        return schema\n\n    def loadPeers(self, site):\n        s = time.time()\n        site_id = self.site_ids.get(site.address)\n        res = self.execute(\"SELECT * FROM peer WHERE site_id = :site_id\", {\"site_id\": site_id})\n        num = 0\n        num_hashfield = 0\n        for row in res:\n            peer = site.addPeer(str(row[\"address\"]), row[\"port\"])\n            if not peer:  # Already exist\n                continue\n            if row[\"hashfield\"]:\n                peer.hashfield.replaceFromBytes(row[\"hashfield\"])\n                num_hashfield += 1\n            peer.time_added = row[\"time_added\"]\n            peer.time_found = row[\"time_found\"]\n            peer.reputation = row[\"reputation\"]\n            if row[\"address\"].endswith(\".onion\"):\n                peer.reputation = peer.reputation / 2 - 1 # Onion peers less likely working\n            num += 1\n        if num_hashfield:\n            site.content_manager.has_optional_files = True\n        site.log.debug(\"%s peers (%s with hashfield) loaded in %.3fs\" % (num, num_hashfield, time.time() - s))\n\n    def iteratePeers(self, site):\n        site_id = self.site_ids.get(site.address)\n        for key, peer in list(site.peers.items()):\n            address, port = key.rsplit(\":\", 1)\n            if peer.has_hashfield:\n                hashfield = sqlite3.Binary(peer.hashfield.tobytes())\n            else:\n                hashfield = \"\"\n            yield (site_id, address, port, hashfield, peer.reputation, int(peer.time_added), int(peer.time_found))\n\n    def savePeers(self, site, spawn=False):\n        if spawn:\n            # Save peers every hour (+random some secs to not update very site at same time)\n            site.greenlet_manager.spawnLater(60 * 60 + random.randint(0, 60), self.savePeers, site, spawn=True)\n        if not site.peers:\n            site.log.debug(\"Peers not saved: No peers found\")\n            return\n        s = time.time()\n        site_id = self.site_ids.get(site.address)\n        cur = self.getCursor()\n        try:\n            cur.execute(\"DELETE FROM peer WHERE site_id = :site_id\", {\"site_id\": site_id})\n            cur.executemany(\n                \"INSERT INTO peer (site_id, address, port, hashfield, reputation, time_added, time_found) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n                self.iteratePeers(site)\n            )\n        except Exception as err:\n            site.log.error(\"Save peer error: %s\" % err)\n        site.log.debug(\"Peers saved in %.3fs\" % (time.time() - s))\n\n    def initSite(self, site):\n        super(ContentDbPlugin, self).initSite(site)\n        site.greenlet_manager.spawnLater(0.5, self.loadPeers, site)\n        site.greenlet_manager.spawnLater(60*60, self.savePeers, site, spawn=True)\n\n    def saveAllPeers(self):\n        for site in list(self.sites.values()):\n            try:\n                self.savePeers(site)\n            except Exception as err:\n                site.log.error(\"Save peer error: %s\" % err)\n"
  },
  {
    "path": "plugins/PeerDb/__init__.py",
    "content": "from . import PeerDbPlugin\n\n"
  },
  {
    "path": "plugins/PeerDb/plugin_info.json",
    "content": "{\n\t\"name\": \"PeerDb\",\n\t\"description\": \"Save/restore peer list on client restart.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/Sidebar/ConsolePlugin.py",
    "content": "import re\nimport logging\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\nfrom util import SafeRe\nfrom util.Flag import flag\n\n\nclass WsLogStreamer(logging.StreamHandler):\n    def __init__(self, stream_id, ui_websocket, filter):\n        self.stream_id = stream_id\n        self.ui_websocket = ui_websocket\n\n        if filter:\n            if not SafeRe.isSafePattern(filter):\n                raise Exception(\"Not a safe prex pattern\")\n            self.filter_re = re.compile(\".*\" + filter)\n        else:\n            self.filter_re = None\n        return super(WsLogStreamer, self).__init__()\n\n    def emit(self, record):\n        if self.ui_websocket.ws.closed:\n            self.stop()\n            return\n        line = self.format(record)\n        if self.filter_re and not self.filter_re.match(line):\n            return False\n\n        self.ui_websocket.cmd(\"logLineAdd\", {\"stream_id\": self.stream_id, \"lines\": [line]})\n\n    def stop(self):\n        logging.getLogger('').removeHandler(self)\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def __init__(self, *args, **kwargs):\n        self.log_streamers = {}\n        return super(UiWebsocketPlugin, self).__init__(*args, **kwargs)\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionConsoleLogRead(self, to, filter=None, read_size=32 * 1024, limit=500):\n        log_file_path = \"%s/debug.log\" % config.log_dir\n        log_file = open(log_file_path, encoding=\"utf-8\")\n        log_file.seek(0, 2)\n        end_pos = log_file.tell()\n        log_file.seek(max(0, end_pos - read_size))\n        if log_file.tell() != 0:\n            log_file.readline()  # Partial line junk\n\n        pos_start = log_file.tell()\n        lines = []\n        if filter:\n            assert SafeRe.isSafePattern(filter)\n            filter_re = re.compile(\".*\" + filter)\n\n        last_match = False\n        for line in log_file:\n            if not line.startswith(\"[\") and last_match:  # Multi-line log entry\n                lines.append(line.replace(\" \", \"&nbsp;\"))\n                continue\n\n            if filter and not filter_re.match(line):\n                last_match = False\n                continue\n            last_match = True\n            lines.append(line)\n\n        num_found = len(lines)\n        lines = lines[-limit:]\n\n        return {\"lines\": lines, \"pos_end\": log_file.tell(), \"pos_start\": pos_start, \"num_found\": num_found}\n\n    def addLogStreamer(self, stream_id, filter=None):\n        logger = WsLogStreamer(stream_id, self, filter)\n        logger.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)-8s %(name)s %(message)s'))\n        logger.setLevel(logging.getLevelName(\"DEBUG\"))\n\n        logging.getLogger('').addHandler(logger)\n        return logger\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionConsoleLogStream(self, to, filter=None):\n        stream_id = to\n        self.log_streamers[stream_id] = self.addLogStreamer(stream_id, filter)\n        self.response(to, {\"stream_id\": stream_id})\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionConsoleLogStreamRemove(self, to, stream_id):\n        try:\n            self.log_streamers[stream_id].stop()\n            del self.log_streamers[stream_id]\n            return \"ok\"\n        except Exception as err:\n            return {\"error\": Debug.formatException(err)}\n"
  },
  {
    "path": "plugins/Sidebar/SidebarPlugin.py",
    "content": "import re\nimport os\nimport html\nimport sys\nimport math\nimport time\nimport json\nimport io\nimport urllib\nimport urllib.parse\n\nimport gevent\n\nimport util\nfrom Config import config\nfrom Plugin import PluginManager\nfrom Debug import Debug\nfrom Translate import Translate\nfrom util import helper\nfrom util.Flag import flag\nfrom .ZipStream import ZipStream\n\nplugin_dir = os.path.dirname(__file__)\nmedia_dir = plugin_dir + \"/media\"\n\nloc_cache = {}\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    # Inject our resources to end of original file streams\n    def actionUiMedia(self, path):\n        if path == \"/uimedia/all.js\" or path == \"/uimedia/all.css\":\n            # First yield the original file and header\n            body_generator = super(UiRequestPlugin, self).actionUiMedia(path)\n            for part in body_generator:\n                yield part\n\n            # Append our media file to the end\n            ext = re.match(\".*(js|css)$\", path).group(1)\n            plugin_media_file = \"%s/all.%s\" % (media_dir, ext)\n            if config.debug:\n                # If debugging merge *.css to all.css and *.js to all.js\n                from Debug import DebugMedia\n                DebugMedia.merge(plugin_media_file)\n            if ext == \"js\":\n                yield _.translateData(open(plugin_media_file).read()).encode(\"utf8\")\n            else:\n                for part in self.actionFile(plugin_media_file, send_header=False):\n                    yield part\n        elif path.startswith(\"/uimedia/globe/\"):  # Serve WebGL globe files\n            file_name = re.match(\".*/(.*)\", path).group(1)\n            plugin_media_file = \"%s_globe/%s\" % (media_dir, file_name)\n            if config.debug and path.endswith(\"all.js\"):\n                # If debugging merge *.css to all.css and *.js to all.js\n                from Debug import DebugMedia\n                DebugMedia.merge(plugin_media_file)\n            for part in self.actionFile(plugin_media_file):\n                yield part\n        else:\n            for part in super(UiRequestPlugin, self).actionUiMedia(path):\n                yield part\n\n    def actionZip(self):\n        address = self.get[\"address\"]\n        site = self.server.site_manager.get(address)\n        if not site:\n            return self.error404(\"Site not found\")\n\n        title = site.content_manager.contents.get(\"content.json\", {}).get(\"title\", \"\")\n        filename = \"%s-backup-%s.zip\" % (title, time.strftime(\"%Y-%m-%d_%H_%M\"))\n        filename_quoted = urllib.parse.quote(filename)\n        self.sendHeader(content_type=\"application/zip\", extra_headers={'Content-Disposition': 'attachment; filename=\"%s\"' % filename_quoted})\n\n        return self.streamZip(site.storage.getPath(\".\"))\n\n    def streamZip(self, dir_path):\n        zs = ZipStream(dir_path)\n        while 1:\n            data = zs.read()\n            if not data:\n                break\n            yield data\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def sidebarRenderPeerStats(self, body, site):\n        connected = len([peer for peer in list(site.peers.values()) if peer.connection and peer.connection.connected])\n        connectable = len([peer_id for peer_id in list(site.peers.keys()) if not peer_id.endswith(\":0\")])\n        onion = len([peer_id for peer_id in list(site.peers.keys()) if \".onion\" in peer_id])\n        local = len([peer for peer in list(site.peers.values()) if helper.isPrivateIp(peer.ip)])\n        peers_total = len(site.peers)\n\n        # Add myself\n        if site.isServing():\n            peers_total += 1\n            if any(site.connection_server.port_opened.values()):\n                connectable += 1\n            if site.connection_server.tor_manager.start_onions:\n                onion += 1\n\n        if peers_total:\n            percent_connected = float(connected) / peers_total\n            percent_connectable = float(connectable) / peers_total\n            percent_onion = float(onion) / peers_total\n        else:\n            percent_connectable = percent_connected = percent_onion = 0\n\n        if local:\n            local_html = _(\"<li class='color-yellow'><span>{_[Local]}:</span><b>{local}</b></li>\")\n        else:\n            local_html = \"\"\n\n        peer_ips = [peer.key for peer in site.getConnectablePeers(20, allow_private=False)]\n        peer_ips.sort(key=lambda peer_ip: \".onion:\" in peer_ip)\n        copy_link = \"http://127.0.0.1:43110/%s/?zeronet_peers=%s\" % (\n            site.content_manager.contents.get(\"content.json\", {}).get(\"domain\", site.address),\n            \",\".join(peer_ips)\n        )\n\n        body.append(_(\"\"\"\n            <li>\n             <label>\n              {_[Peers]}\n              <small class=\"label-right\"><a href='{copy_link}' id='link-copypeers' class='link-right'>{_[Copy to clipboard]}</a></small>\n             </label>\n             <ul class='graph'>\n              <li style='width: 100%' class='total back-black' title=\"{_[Total peers]}\"></li>\n              <li style='width: {percent_connectable:.0%}' class='connectable back-blue' title='{_[Connectable peers]}'></li>\n              <li style='width: {percent_onion:.0%}' class='connected back-purple' title='{_[Onion]}'></li>\n              <li style='width: {percent_connected:.0%}' class='connected back-green' title='{_[Connected peers]}'></li>\n             </ul>\n             <ul class='graph-legend'>\n              <li class='color-green'><span>{_[Connected]}:</span><b>{connected}</b></li>\n              <li class='color-blue'><span>{_[Connectable]}:</span><b>{connectable}</b></li>\n              <li class='color-purple'><span>{_[Onion]}:</span><b>{onion}</b></li>\n              {local_html}\n              <li class='color-black'><span>{_[Total]}:</span><b>{peers_total}</b></li>\n             </ul>\n            </li>\n        \"\"\".replace(\"{local_html}\", local_html)))\n\n    def sidebarRenderTransferStats(self, body, site):\n        recv = float(site.settings.get(\"bytes_recv\", 0)) / 1024 / 1024\n        sent = float(site.settings.get(\"bytes_sent\", 0)) / 1024 / 1024\n        transfer_total = recv + sent\n        if transfer_total:\n            percent_recv = recv / transfer_total\n            percent_sent = sent / transfer_total\n        else:\n            percent_recv = 0.5\n            percent_sent = 0.5\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Data transfer]}</label>\n             <ul class='graph graph-stacked'>\n              <li style='width: {percent_recv:.0%}' class='received back-yellow' title=\"{_[Received bytes]}\"></li>\n              <li style='width: {percent_sent:.0%}' class='sent back-green' title=\"{_[Sent bytes]}\"></li>\n             </ul>\n             <ul class='graph-legend'>\n              <li class='color-yellow'><span>{_[Received]}:</span><b>{recv:.2f}MB</b></li>\n              <li class='color-green'<span>{_[Sent]}:</span><b>{sent:.2f}MB</b></li>\n             </ul>\n            </li>\n        \"\"\"))\n\n    def sidebarRenderFileStats(self, body, site):\n        body.append(_(\"\"\"\n            <li>\n             <label>\n              {_[Files]}\n              <a href='/list/{site.address}' class='link-right link-outline' id=\"browse-files\">{_[Browse files]}</a>\n              <small class=\"label-right\">\n               <a href='/ZeroNet-Internal/Zip?address={site.address}' id='link-zip' class='link-right' download='site.zip'>{_[Save as .zip]}</a>\n              </small>\n             </label>\n             <ul class='graph graph-stacked'>\n        \"\"\"))\n\n        extensions = (\n            (\"html\", \"yellow\"),\n            (\"css\", \"orange\"),\n            (\"js\", \"purple\"),\n            (\"Image\", \"green\"),\n            (\"json\", \"darkblue\"),\n            (\"User data\", \"blue\"),\n            (\"Other\", \"white\"),\n            (\"Total\", \"black\")\n        )\n        # Collect stats\n        size_filetypes = {}\n        size_total = 0\n        contents = site.content_manager.listContents()  # Without user files\n        for inner_path in contents:\n            content = site.content_manager.contents[inner_path]\n            if \"files\" not in content or content[\"files\"] is None:\n                continue\n            for file_name, file_details in list(content[\"files\"].items()):\n                size_total += file_details[\"size\"]\n                ext = file_name.split(\".\")[-1]\n                size_filetypes[ext] = size_filetypes.get(ext, 0) + file_details[\"size\"]\n\n        # Get user file sizes\n        size_user_content = site.content_manager.contents.execute(\n            \"SELECT SUM(size) + SUM(size_files) AS size FROM content WHERE ?\",\n            {\"not__inner_path\": contents}\n        ).fetchone()[\"size\"]\n        if not size_user_content:\n            size_user_content = 0\n        size_filetypes[\"User data\"] = size_user_content\n        size_total += size_user_content\n\n        # The missing difference is content.json sizes\n        if \"json\" in size_filetypes:\n            size_filetypes[\"json\"] += max(0, site.settings[\"size\"] - size_total)\n        size_total = size_other = site.settings[\"size\"]\n\n        # Bar\n        for extension, color in extensions:\n            if extension == \"Total\":\n                continue\n            if extension == \"Other\":\n                size = max(0, size_other)\n            elif extension == \"Image\":\n                size = size_filetypes.get(\"jpg\", 0) + size_filetypes.get(\"png\", 0) + size_filetypes.get(\"gif\", 0)\n                size_other -= size\n            else:\n                size = size_filetypes.get(extension, 0)\n                size_other -= size\n            if size_total == 0:\n                percent = 0\n            else:\n                percent = 100 * (float(size) / size_total)\n            percent = math.floor(percent * 100) / 100  # Floor to 2 digits\n            body.append(\n                \"\"\"<li style='width: %.2f%%' class='%s back-%s' title=\"%s\"></li>\"\"\" %\n                (percent, _[extension], color, _[extension])\n            )\n\n        # Legend\n        body.append(\"</ul><ul class='graph-legend'>\")\n        for extension, color in extensions:\n            if extension == \"Other\":\n                size = max(0, size_other)\n            elif extension == \"Image\":\n                size = size_filetypes.get(\"jpg\", 0) + size_filetypes.get(\"png\", 0) + size_filetypes.get(\"gif\", 0)\n            elif extension == \"Total\":\n                size = size_total\n            else:\n                size = size_filetypes.get(extension, 0)\n\n            if extension == \"js\":\n                title = \"javascript\"\n            else:\n                title = extension\n\n            if size > 1024 * 1024 * 10:  # Format as mB is more than 10mB\n                size_formatted = \"%.0fMB\" % (size / 1024 / 1024)\n            else:\n                size_formatted = \"%.0fkB\" % (size / 1024)\n\n            body.append(\"<li class='color-%s'><span>%s:</span><b>%s</b></li>\" % (color, _[title], size_formatted))\n\n        body.append(\"</ul></li>\")\n\n    def sidebarRenderSizeLimit(self, body, site):\n        free_space = helper.getFreeSpace() / 1024 / 1024\n        size = float(site.settings[\"size\"]) / 1024 / 1024\n        size_limit = site.getSizeLimit()\n        percent_used = size / size_limit\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Size limit]} <small>({_[limit used]}: {percent_used:.0%}, {_[free space]}: {free_space:,.0f}MB)</small></label>\n             <input type='text' class='text text-num' value=\"{size_limit}\" id='input-sitelimit'/><span class='text-post'>MB</span>\n             <a href='#Set' class='button' id='button-sitelimit'>{_[Set]}</a>\n            </li>\n        \"\"\"))\n\n    def sidebarRenderOptionalFileStats(self, body, site):\n        size_total = float(site.settings[\"size_optional\"])\n        size_downloaded = float(site.settings[\"optional_downloaded\"])\n\n        if not size_total:\n            return False\n\n        percent_downloaded = size_downloaded / size_total\n\n        size_formatted_total = size_total / 1024 / 1024\n        size_formatted_downloaded = size_downloaded / 1024 / 1024\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Optional files]}</label>\n             <ul class='graph'>\n              <li style='width: 100%' class='total back-black' title=\"{_[Total size]}\"></li>\n              <li style='width: {percent_downloaded:.0%}' class='connected back-green' title='{_[Downloaded files]}'></li>\n             </ul>\n             <ul class='graph-legend'>\n              <li class='color-green'><span>{_[Downloaded]}:</span><b>{size_formatted_downloaded:.2f}MB</b></li>\n              <li class='color-black'><span>{_[Total]}:</span><b>{size_formatted_total:.2f}MB</b></li>\n             </ul>\n            </li>\n        \"\"\"))\n\n        return True\n\n    def sidebarRenderOptionalFileSettings(self, body, site):\n        if self.site.settings.get(\"autodownloadoptional\"):\n            checked = \"checked='checked'\"\n        else:\n            checked = \"\"\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Help distribute added optional files]}</label>\n             <input type=\"checkbox\" class=\"checkbox\" id=\"checkbox-autodownloadoptional\" {checked}/><div class=\"checkbox-skin\"></div>\n        \"\"\"))\n\n        if hasattr(config, \"autodownload_bigfile_size_limit\"):\n            autodownload_bigfile_size_limit = int(site.settings.get(\"autodownload_bigfile_size_limit\", config.autodownload_bigfile_size_limit))\n            body.append(_(\"\"\"\n                <div class='settings-autodownloadoptional'>\n                 <label>{_[Auto download big file size limit]}</label>\n                 <input type='text' class='text text-num' value=\"{autodownload_bigfile_size_limit}\" id='input-autodownload_bigfile_size_limit'/><span class='text-post'>MB</span>\n                 <a href='#Set' class='button' id='button-autodownload_bigfile_size_limit'>{_[Set]}</a>\n                 <a href='#Download+previous' class='button' id='button-autodownload_previous'>{_[Download previous files]}</a>\n                </div>\n            \"\"\"))\n        body.append(\"</li>\")\n\n    def sidebarRenderBadFiles(self, body, site):\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Needs to be updated]}:</label>\n             <ul class='filelist'>\n        \"\"\"))\n\n        i = 0\n        for bad_file, tries in site.bad_files.items():\n            i += 1\n            body.append(_(\"\"\"<li class='color-red' title=\"{bad_file_path} ({tries})\">{bad_filename}</li>\"\"\", {\n                \"bad_file_path\": bad_file,\n                \"bad_filename\": helper.getFilename(bad_file),\n                \"tries\": _.pluralize(tries, \"{} try\", \"{} tries\")\n            }))\n            if i > 30:\n                break\n\n        if len(site.bad_files) > 30:\n            num_bad_files = len(site.bad_files) - 30\n            body.append(_(\"\"\"<li class='color-red'>{_[+ {num_bad_files} more]}</li>\"\"\", nested=True))\n\n        body.append(\"\"\"\n             </ul>\n            </li>\n        \"\"\")\n\n    def sidebarRenderDbOptions(self, body, site):\n        if site.storage.db:\n            inner_path = site.storage.getInnerPath(site.storage.db.db_path)\n            size = float(site.storage.getSize(inner_path)) / 1024\n            feeds = len(site.storage.db.schema.get(\"feeds\", {}))\n        else:\n            inner_path = _[\"No database found\"]\n            size = 0.0\n            feeds = 0\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Database]} <small>({size:.2f}kB, {_[search feeds]}: {_[{feeds} query]})</small></label>\n             <div class='flex'>\n              <input type='text' class='text disabled' value=\"{inner_path}\" disabled='disabled'/>\n              <a href='#Reload' id=\"button-dbreload\" class='button'>{_[Reload]}</a>\n              <a href='#Rebuild' id=\"button-dbrebuild\" class='button'>{_[Rebuild]}</a>\n             </div>\n            </li>\n        \"\"\", nested=True))\n\n    def sidebarRenderIdentity(self, body, site):\n        auth_address = self.user.getAuthAddress(self.site.address, create=False)\n        rules = self.site.content_manager.getRules(\"data/users/%s/content.json\" % auth_address)\n        if rules and rules.get(\"max_size\"):\n            quota = rules[\"max_size\"] / 1024\n            try:\n                content = site.content_manager.contents[\"data/users/%s/content.json\" % auth_address]\n                used = len(json.dumps(content)) + sum([file[\"size\"] for file in list(content[\"files\"].values())])\n            except:\n                used = 0\n            used = used / 1024\n        else:\n            quota = used = 0\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Identity address]} <small>({_[limit used]}: {used:.2f}kB / {quota:.2f}kB)</small></label>\n             <div class='flex'>\n              <span class='input text disabled'>{auth_address}</span>\n              <a href='#Change' class='button' id='button-identity'>{_[Change]}</a>\n             </div>\n            </li>\n        \"\"\"))\n\n    def sidebarRenderControls(self, body, site):\n        auth_address = self.user.getAuthAddress(self.site.address, create=False)\n        if self.site.settings[\"serving\"]:\n            class_pause = \"\"\n            class_resume = \"hidden\"\n        else:\n            class_pause = \"hidden\"\n            class_resume = \"\"\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Site control]}</label>\n             <a href='#Update' class='button noupdate' id='button-update'>{_[Update]}</a>\n             <a href='#Pause' class='button {class_pause}' id='button-pause'>{_[Pause]}</a>\n             <a href='#Resume' class='button {class_resume}' id='button-resume'>{_[Resume]}</a>\n             <a href='#Delete' class='button noupdate' id='button-delete'>{_[Delete]}</a>\n            </li>\n        \"\"\"))\n\n        donate_key = site.content_manager.contents.get(\"content.json\", {}).get(\"donate\", True)\n        site_address = self.site.address\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Site address]}</label><br>\n             <div class='flex'>\n              <span class='input text disabled'>{site_address}</span>\n        \"\"\"))\n        if donate_key == False or donate_key == \"\":\n            pass\n        elif (type(donate_key) == str or type(donate_key) == str) and len(donate_key) > 0:\n            body.append(_(\"\"\"\n             </div>\n            </li>\n            <li>\n             <label>{_[Donate]}</label><br>\n             <div class='flex'>\n             {donate_key}\n            \"\"\"))\n        else:\n            body.append(_(\"\"\"\n              <a href='bitcoin:{site_address}' class='button' id='button-donate'>{_[Donate]}</a>\n            \"\"\"))\n        body.append(_(\"\"\"\n             </div>\n            </li>\n        \"\"\"))\n\n    def sidebarRenderOwnedCheckbox(self, body, site):\n        if self.site.settings[\"own\"]:\n            checked = \"checked='checked'\"\n        else:\n            checked = \"\"\n\n        body.append(_(\"\"\"\n            <h2 class='owned-title'>{_[This is my site]}</h2>\n            <input type=\"checkbox\" class=\"checkbox\" id=\"checkbox-owned\" {checked}/><div class=\"checkbox-skin\"></div>\n        \"\"\"))\n\n    def sidebarRenderOwnSettings(self, body, site):\n        title = site.content_manager.contents.get(\"content.json\", {}).get(\"title\", \"\")\n        description = site.content_manager.contents.get(\"content.json\", {}).get(\"description\", \"\")\n\n        body.append(_(\"\"\"\n            <li>\n             <label for='settings-title'>{_[Site title]}</label>\n             <input type='text' class='text' value=\"{title}\" id='settings-title'/>\n            </li>\n\n            <li>\n             <label for='settings-description'>{_[Site description]}</label>\n             <input type='text' class='text' value=\"{description}\" id='settings-description'/>\n            </li>\n\n            <li>\n             <a href='#Save' class='button' id='button-settings'>{_[Save site settings]}</a>\n            </li>\n        \"\"\"))\n\n    def sidebarRenderContents(self, body, site):\n        has_privatekey = bool(self.user.getSiteData(site.address, create=False).get(\"privatekey\"))\n        if has_privatekey:\n            tag_privatekey = _(\"{_[Private key saved.]} <a href='#Forget+private+key' id='privatekey-forget' class='link-right'>{_[Forget]}</a>\")\n        else:\n            tag_privatekey = _(\"<a href='#Add+private+key' id='privatekey-add' class='link-right'>{_[Add saved private key]}</a>\")\n\n        body.append(_(\"\"\"\n            <li>\n             <label>{_[Content publishing]} <small class='label-right'>{tag_privatekey}</small></label>\n        \"\"\".replace(\"{tag_privatekey}\", tag_privatekey)))\n\n        # Choose content you want to sign\n        body.append(_(\"\"\"\n             <div class='flex'>\n              <input type='text' class='text' value=\"content.json\" id='input-contents'/>\n              <a href='#Sign-and-Publish' id='button-sign-publish' class='button'>{_[Sign and publish]}</a>\n              <a href='#Sign-or-Publish' id='menu-sign-publish'>\\u22EE</a>\n             </div>\n        \"\"\"))\n\n        contents = [\"content.json\"]\n        contents += list(site.content_manager.contents.get(\"content.json\", {}).get(\"includes\", {}).keys())\n        body.append(_(\"<div class='contents'>{_[Choose]}: \"))\n        for content in contents:\n            body.append(_(\"<a href='{content}' class='contents-content'>{content}</a> \"))\n        body.append(\"</div>\")\n        body.append(\"</li>\")\n\n    @flag.admin\n    def actionSidebarGetHtmlTag(self, to):\n        site = self.site\n\n        body = []\n\n        body.append(\"<div>\")\n        body.append(\"<a href='#Close' class='close'>&times;</a>\")\n        body.append(\"<h1>%s</h1>\" % html.escape(site.content_manager.contents.get(\"content.json\", {}).get(\"title\", \"\"), True))\n\n        body.append(\"<div class='globe loading'></div>\")\n\n        body.append(\"<ul class='fields'>\")\n\n        self.sidebarRenderPeerStats(body, site)\n        self.sidebarRenderTransferStats(body, site)\n        self.sidebarRenderFileStats(body, site)\n        self.sidebarRenderSizeLimit(body, site)\n        has_optional = self.sidebarRenderOptionalFileStats(body, site)\n        if has_optional:\n            self.sidebarRenderOptionalFileSettings(body, site)\n        self.sidebarRenderDbOptions(body, site)\n        self.sidebarRenderIdentity(body, site)\n        self.sidebarRenderControls(body, site)\n        if site.bad_files:\n            self.sidebarRenderBadFiles(body, site)\n\n        self.sidebarRenderOwnedCheckbox(body, site)\n        body.append(\"<div class='settings-owned'>\")\n        self.sidebarRenderOwnSettings(body, site)\n        self.sidebarRenderContents(body, site)\n        body.append(\"</div>\")\n        body.append(\"</ul>\")\n        body.append(\"</div>\")\n\n        body.append(\"<div class='menu template'>\")\n        body.append(\"<a href='#'' class='menu-item template'>Template</a>\")\n        body.append(\"</div>\")\n\n        self.response(to, \"\".join(body))\n\n    def downloadGeoLiteDb(self, db_path):\n        import gzip\n        import shutil\n        from util import helper\n\n        if config.offline:\n            return False\n\n        self.log.info(\"Downloading GeoLite2 City database...\")\n        self.cmd(\"progress\", [\"geolite-info\", _[\"Downloading GeoLite2 City database (one time only, ~20MB)...\"], 0])\n        db_urls = [\n            \"https://raw.githubusercontent.com/aemr3/GeoLite2-Database/master/GeoLite2-City.mmdb.gz\",\n            \"https://raw.githubusercontent.com/texnikru/GeoLite2-Database/master/GeoLite2-City.mmdb.gz\"\n        ]\n        for db_url in db_urls:\n            downloadl_err = None\n            try:\n                # Download\n                response = helper.httpRequest(db_url)\n                data_size = response.getheader('content-length')\n                data_recv = 0\n                data = io.BytesIO()\n                while True:\n                    buff = response.read(1024 * 512)\n                    if not buff:\n                        break\n                    data.write(buff)\n                    data_recv += 1024 * 512\n                    if data_size:\n                        progress = int(float(data_recv) / int(data_size) * 100)\n                        self.cmd(\"progress\", [\"geolite-info\", _[\"Downloading GeoLite2 City database (one time only, ~20MB)...\"], progress])\n                self.log.info(\"GeoLite2 City database downloaded (%s bytes), unpacking...\" % data.tell())\n                data.seek(0)\n\n                # Unpack\n                with gzip.GzipFile(fileobj=data) as gzip_file:\n                    shutil.copyfileobj(gzip_file, open(db_path, \"wb\"))\n\n                self.cmd(\"progress\", [\"geolite-info\", _[\"GeoLite2 City database downloaded!\"], 100])\n                time.sleep(2)  # Wait for notify animation\n                self.log.info(\"GeoLite2 City database is ready at: %s\" % db_path)\n                return True\n            except Exception as err:\n                download_err = err\n                self.log.error(\"Error downloading %s: %s\" % (db_url, err))\n                pass\n        self.cmd(\"progress\", [\n            \"geolite-info\",\n            _[\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\"].format(download_err, db_urls[0]),\n            -100\n        ])\n\n    def getLoc(self, geodb, ip):\n        global loc_cache\n\n        if ip in loc_cache:\n            return loc_cache[ip]\n        else:\n            try:\n                loc_data = geodb.get(ip)\n            except:\n                loc_data = None\n\n            if not loc_data or \"location\" not in loc_data:\n                loc_cache[ip] = None\n                return None\n\n            loc = {\n                \"lat\": loc_data[\"location\"][\"latitude\"],\n                \"lon\": loc_data[\"location\"][\"longitude\"],\n            }\n            if \"city\" in loc_data:\n                loc[\"city\"] = loc_data[\"city\"][\"names\"][\"en\"]\n\n            if \"country\" in loc_data:\n                loc[\"country\"] = loc_data[\"country\"][\"names\"][\"en\"]\n\n            loc_cache[ip] = loc\n            return loc\n\n    @util.Noparallel()\n    def getGeoipDb(self):\n        db_name = 'GeoLite2-City.mmdb'\n\n        sys_db_paths = []\n        if sys.platform == \"linux\":\n            sys_db_paths += ['/usr/share/GeoIP/' + db_name]\n\n        data_dir_db_path = os.path.join(config.data_dir, db_name)\n\n        db_paths = sys_db_paths + [data_dir_db_path]\n\n        for path in db_paths:\n            if os.path.isfile(path) and os.path.getsize(path) > 0:\n                return path\n\n        self.log.info(\"GeoIP database not found at [%s]. Downloading to: %s\",\n                \" \".join(db_paths), data_dir_db_path)\n        if self.downloadGeoLiteDb(data_dir_db_path):\n            return data_dir_db_path\n        return None\n\n    def getPeerLocations(self, peers):\n        import maxminddb\n\n        db_path = self.getGeoipDb()\n        if not db_path:\n            self.log.debug(\"Not showing peer locations: no GeoIP database\")\n            return False\n\n        geodb = maxminddb.open_database(db_path)\n\n        peers = list(peers.values())\n        # Place bars\n        peer_locations = []\n        placed = {}  # Already placed bars here\n        for peer in peers:\n            # Height of bar\n            if peer.connection and peer.connection.last_ping_delay:\n                ping = round(peer.connection.last_ping_delay * 1000)\n            else:\n                ping = None\n            loc = self.getLoc(geodb, peer.ip)\n\n            if not loc:\n                continue\n            # Create position array\n            lat, lon = loc[\"lat\"], loc[\"lon\"]\n            latlon = \"%s,%s\" % (lat, lon)\n            if latlon in placed and helper.getIpType(peer.ip) == \"ipv4\":  # Dont place more than 1 bar to same place, fake repos using ip address last two part\n                lat += float(128 - int(peer.ip.split(\".\")[-2])) / 50\n                lon += float(128 - int(peer.ip.split(\".\")[-1])) / 50\n                latlon = \"%s,%s\" % (lat, lon)\n            placed[latlon] = True\n            peer_location = {}\n            peer_location.update(loc)\n            peer_location[\"lat\"] = lat\n            peer_location[\"lon\"] = lon\n            peer_location[\"ping\"] = ping\n\n            peer_locations.append(peer_location)\n\n        # Append myself\n        for ip in self.site.connection_server.ip_external_list:\n            my_loc = self.getLoc(geodb, ip)\n            if my_loc:\n                my_loc[\"ping\"] = 0\n                peer_locations.append(my_loc)\n\n        return peer_locations\n\n    @flag.admin\n    @flag.async_run\n    def actionSidebarGetPeers(self, to):\n        try:\n            peer_locations = self.getPeerLocations(self.site.peers)\n            globe_data = []\n            ping_times = [\n                peer_location[\"ping\"]\n                for peer_location in peer_locations\n                if peer_location[\"ping\"]\n            ]\n            if ping_times:\n                ping_avg = sum(ping_times) / float(len(ping_times))\n            else:\n                ping_avg = 0\n\n            for peer_location in peer_locations:\n                if peer_location[\"ping\"] == 0:  # Me\n                    height = -0.135\n                elif peer_location[\"ping\"]:\n                    height = min(0.20, math.log(1 + peer_location[\"ping\"] / ping_avg, 300))\n                else:\n                    height = -0.03\n\n                globe_data += [peer_location[\"lat\"], peer_location[\"lon\"], height]\n\n            self.response(to, globe_data)\n        except Exception as err:\n            self.log.debug(\"sidebarGetPeers error: %s\" % Debug.formatException(err))\n            self.response(to, {\"error\": str(err)})\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionSiteSetOwned(self, to, owned):\n        if self.site.address == config.updatesite:\n            return {\"error\": \"You can't change the ownership of the updater site\"}\n\n        self.site.settings[\"own\"] = bool(owned)\n        self.site.updateWebsocket(owned=owned)\n        return \"ok\"\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionSiteRecoverPrivatekey(self, to):\n        from Crypt import CryptBitcoin\n\n        site_data = self.user.sites[self.site.address]\n        if site_data.get(\"privatekey\"):\n            return {\"error\": \"This site already has saved privated key\"}\n\n        address_index = self.site.content_manager.contents.get(\"content.json\", {}).get(\"address_index\")\n        if not address_index:\n            return {\"error\": \"No address_index in content.json\"}\n\n        privatekey = CryptBitcoin.hdPrivatekey(self.user.master_seed, address_index)\n        privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey)\n\n        if privatekey_address == self.site.address:\n            site_data[\"privatekey\"] = privatekey\n            self.user.save()\n            self.site.updateWebsocket(recover_privatekey=True)\n            return \"ok\"\n        else:\n            return {\"error\": \"Unable to deliver private key for this site from current user's master_seed\"}\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionUserSetSitePrivatekey(self, to, privatekey):\n        site_data = self.user.sites[self.site.address]\n        site_data[\"privatekey\"] = privatekey\n        self.site.updateWebsocket(set_privatekey=bool(privatekey))\n        self.user.save()\n\n        return \"ok\"\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionSiteSetAutodownloadoptional(self, to, owned):\n        self.site.settings[\"autodownloadoptional\"] = bool(owned)\n        self.site.worker_manager.removeSolvedFileTasks()\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionDbReload(self, to):\n        self.site.storage.closeDb()\n        self.site.storage.getDb()\n\n        return self.response(to, \"ok\")\n\n    @flag.no_multiuser\n    @flag.admin\n    def actionDbRebuild(self, to):\n        try:\n            self.site.storage.rebuildDb()\n        except Exception as err:\n            return self.response(to, {\"error\": str(err)})\n\n\n        return self.response(to, \"ok\")\n"
  },
  {
    "path": "plugins/Sidebar/ZipStream.py",
    "content": "import io\nimport os\nimport zipfile\n\nclass ZipStream(object):\n    def __init__(self, dir_path):\n        self.dir_path = dir_path\n        self.pos = 0\n        self.buff_pos = 0\n        self.zf = zipfile.ZipFile(self, 'w', zipfile.ZIP_DEFLATED, allowZip64=True)\n        self.buff = io.BytesIO()\n        self.file_list = self.getFileList()\n\n    def getFileList(self):\n        for root, dirs, files in os.walk(self.dir_path):\n            for file in files:\n                file_path = root + \"/\" + file\n                relative_path = os.path.join(os.path.relpath(root, self.dir_path), file)\n                yield file_path, relative_path\n        self.zf.close()\n\n    def read(self, size=60 * 1024):\n        for file_path, relative_path in self.file_list:\n            self.zf.write(file_path, relative_path)\n            if self.buff.tell() >= size:\n                break\n        self.buff.seek(0)\n        back = self.buff.read()\n        self.buff.truncate(0)\n        self.buff.seek(0)\n        self.buff_pos += len(back)\n        return back\n\n    def write(self, data):\n        self.pos += len(data)\n        self.buff.write(data)\n\n    def tell(self):\n        return self.pos\n\n    def seek(self, pos, whence=0):\n        if pos >= self.buff_pos:\n            self.buff.seek(pos - self.buff_pos, whence)\n            self.pos = pos\n\n    def flush(self):\n        pass\n\n\nif __name__ == \"__main__\":\n    zs = ZipStream(\".\")\n    out = open(\"out.zip\", \"wb\")\n    while 1:\n        data = zs.read()\n        print(\"Write %s\" % len(data))\n        if not data:\n            break\n        out.write(data)\n    out.close()\n"
  },
  {
    "path": "plugins/Sidebar/__init__.py",
    "content": "from . import SidebarPlugin\nfrom . import ConsolePlugin"
  },
  {
    "path": "plugins/Sidebar/languages/da.json",
    "content": "{\n\t\"Peers\": \"Klienter\",\n\t\"Connected\": \"Forbundet\",\n\t\"Connectable\": \"Mulige\",\n\t\"Connectable peers\": \"Mulige klienter\",\n\n\t\"Data transfer\": \"Data overførsel\",\n\t\"Received\": \"Modtaget\",\n\t\"Received bytes\": \"Bytes modtaget\",\n\t\"Sent\": \"Sendt\",\n\t\"Sent bytes\": \"Bytes sendt\",\n\n\t\"Files\": \"Filer\",\n\t\"Total\": \"I alt\",\n\t\"Image\": \"Image\",\n\t\"Other\": \"Andet\",\n\t\"User data\": \"Bruger data\",\n\n\t\"Size limit\": \"Side max størrelse\",\n\t\"limit used\": \"brugt\",\n\t\"free space\": \"fri\",\n\t\"Set\": \"Opdater\",\n\n\t\"Optional files\": \"Valgfri filer\",\n\t\"Downloaded\": \"Downloadet\",\n\t\"Download and help distribute all files\": \"Download og hjælp med at dele filer\",\n\t\"Total size\": \"Størrelse i alt\",\n\t\"Downloaded files\": \"Filer downloadet\",\n\n\t\"Database\": \"Database\",\n\t\"search feeds\": \"søgninger\",\n\t\"{feeds} query\": \"{feeds} søgninger\",\n\t\"Reload\": \"Genindlæs\",\n\t\"Rebuild\": \"Genopbyg\",\n\t\"No database found\": \"Ingen database fundet\",\n\n\t\"Identity address\": \"Autorisations ID\",\n\t\"Change\": \"Skift\",\n\n\t\"Update\": \"Opdater\",\n\t\"Pause\": \"Pause\",\n\t\"Resume\": \"Aktiv\",\n\t\"Delete\": \"Slet\",\n\t\"Are you sure?\": \"Er du sikker?\",\n\n\t\"Site address\": \"Side addresse\",\n\t\"Donate\": \"Doner penge\",\n\n\t\"Missing files\": \"Manglende filer\",\n\t\"{} try\": \"{} forsøg\",\n\t\"{} tries\": \"{} forsøg\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} mere\",\n\n\t\"This is my site\": \"Dette er min side\",\n\t\"Site title\": \"Side navn\",\n\t\"Site description\": \"Side beskrivelse\",\n\t\"Save site settings\": \"Gem side opsætning\",\n\n\t\"Content publishing\": \"Indhold offentliggøres\",\n\t\"Choose\": \"Vælg\",\n\t\"Sign\": \"Signer\",\n\t\"Publish\": \"Offentliggør\",\n\n\t\"This function is disabled on this proxy\": \"Denne funktion er slået fra på denne ZeroNet proxyEz a funkció ki van kapcsolva ezen a proxy-n\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 City database kunne ikke downloades: {}!<br>Download venligst databasen manuelt og udpak i data folder:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"GeoLite2 város adatbázis letöltése (csak egyszer kell, kb 20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 City database downloadet!\",\n\n\t\"Are you sure?\": \"Er du sikker?\",\n\t\"Site storage limit modified!\": \"Side max størrelse ændret!\",\n\t\"Database schema reloaded!\": \"Database definition genindlæst!\",\n\t\"Database rebuilding....\": \"Genopbygger database...\",\n\t\"Database rebuilt!\": \"Database genopbygget!\",\n\t\"Site updated!\": \"Side opdateret!\",\n\t\"Delete this site\": \"Slet denne side\",\n\t\"File write error: \": \"Fejl ved skrivning af fil: \",\n\t\"Site settings saved!\": \"Side opsætning gemt!\",\n\t\"Enter your private key:\": \"Indtast din private nøgle:\",\n\t\" Signed!\": \" Signeret!\",\n\t\"WebGL not supported\": \"WebGL er ikke supporteret\"\n}"
  },
  {
    "path": "plugins/Sidebar/languages/de.json",
    "content": "{\n\t\"Peers\": \"Peers\",\n\t\"Connected\": \"Verbunden\",\n\t\"Connectable\": \"Verbindbar\",\n\t\"Connectable peers\": \"Verbindbare Peers\",\n\n\t\"Data transfer\": \"Datei Transfer\",\n\t\"Received\": \"Empfangen\",\n\t\"Received bytes\": \"Empfangene Bytes\",\n\t\"Sent\": \"Gesendet\",\n\t\"Sent bytes\": \"Gesendete Bytes\",\n\n\t\"Files\": \"Dateien\",\n\t\"Total\": \"Gesamt\",\n\t\"Image\": \"Bilder\",\n\t\"Other\": \"Sonstiges\",\n\t\"User data\": \"Nutzer Daten\",\n\n\t\"Size limit\": \"Speicher Limit\",\n\t\"limit used\": \"Limit benutzt\",\n\t\"free space\": \"freier Speicher\",\n\t\"Set\": \"Setzten\",\n\n\t\"Optional files\": \"Optionale Dateien\",\n\t\"Downloaded\": \"Heruntergeladen\",\n\t\"Download and help distribute all files\": \"Herunterladen und helfen alle Dateien zu verteilen\",\n\t\"Total size\": \"Gesamte Größe\",\n\t\"Downloaded files\": \"Heruntergeladene Dateien\",\n\n\t\"Database\": \"Datenbank\",\n\t\"search feeds\": \"Feeds durchsuchen\",\n\t\"{feeds} query\": \"{feeds} Abfrage\",\n\t\"Reload\": \"Neu laden\",\n\t\"Rebuild\": \"Neu bauen\",\n\t\"No database found\": \"Keine Datenbank gefunden\",\n\n\t\"Identity address\": \"Identitäts Adresse\",\n\t\"Change\": \"Ändern\",\n\n\t\"Update\": \"Aktualisieren\",\n\t\"Pause\": \"Pausieren\",\n\t\"Resume\": \"Fortsetzen\",\n\t\"Delete\": \"Löschen\",\n\t\"Are you sure?\": \"Bist du sicher?\",\n\n\t\"Site address\": \"Seiten Adresse\",\n\t\"Donate\": \"Spenden\",\n\n\t\"Missing files\": \"Fehlende Dateien\",\n\t\"{} try\": \"{} versuch\",\n\t\"{} tries\": \"{} versuche\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} mehr\",\n\n\t\"This is my site\": \"Das ist meine Seite\",\n\t\"Site title\": \"Seiten Titel\",\n\t\"Site description\": \"Seiten Beschreibung\",\n\t\"Save site settings\": \"Einstellungen der Seite speichern\",\n\n\t\"Content publishing\": \"Inhaltsveröffentlichung\",\n\t\"Choose\": \"Wähle\",\n\t\"Sign\": \"Signieren\",\n\t\"Publish\": \"Veröffentlichen\",\n\n\t\"This function is disabled on this proxy\": \"Diese Funktion ist auf dieser Proxy deaktiviert\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 City Datenbank Download Fehler: {}!<br>Bitte manuell herunterladen und die Datei in das Datei Verzeichnis extrahieren:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Herunterladen der GeoLite2 City Datenbank (einmalig, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 City Datenbank heruntergeladen!\",\n\n\t\"Are you sure?\": \"Bist du sicher?\",\n\t\"Site storage limit modified!\": \"Speicher Limit der Seite modifiziert!\",\n\t\"Database schema reloaded!\": \"Datebank Schema neu geladen!\",\n\t\"Database rebuilding....\": \"Datenbank neu bauen...\",\n\t\"Database rebuilt!\": \"Datenbank neu gebaut!\",\n\t\"Site updated!\": \"Seite aktualisiert!\",\n\t\"Delete this site\": \"Diese Seite löschen\",\n\t\"File write error: \": \"Datei schreib fehler:\",\n\t\"Site settings saved!\": \"Seiten Einstellungen gespeichert!\",\n\t\"Enter your private key:\": \"Gib deinen privaten Schlüssel ein:\",\n\t\" Signed!\": \" Signiert!\",\n\t\"WebGL not supported\": \"WebGL nicht unterstützt\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/es.json",
    "content": "{\n\t\"Peers\": \"Pares\",\n\t\"Connected\": \"Conectados\",\n\t\"Connectable\": \"Conectables\",\n\t\"Connectable peers\": \"Pares conectables\",\n\n\t\"Data transfer\": \"Transferencia de datos\",\n\t\"Received\": \"Recibidos\",\n\t\"Received bytes\": \"Bytes recibidos\",\n\t\"Sent\": \"Enviados\",\n\t\"Sent bytes\": \"Bytes envidados\",\n\n\t\"Files\": \"Ficheros\",\n\t\"Total\": \"Total\",\n\t\"Image\": \"Imagen\",\n\t\"Other\": \"Otro\",\n\t\"User data\": \"Datos del usuario\",\n\n\t\"Size limit\": \"Límite de tamaño\",\n\t\"limit used\": \"Límite utilizado\",\n\t\"free space\": \"Espacio libre\",\n\t\"Set\": \"Establecer\",\n\n\t\"Optional files\": \"Ficheros opcionales\",\n\t\"Downloaded\": \"Descargado\",\n\t\"Download and help distribute all files\": \"Descargar y ayudar a distribuir todos los ficheros\",\n\t\"Total size\": \"Tamaño total\",\n\t\"Downloaded files\": \"Ficheros descargados\",\n\n\t\"Database\": \"Base de datos\",\n\t\"search feeds\": \"Fuentes de búsqueda\",\n\t\"{feeds} query\": \"{feeds} consulta\",\n\t\"Reload\": \"Recargar\",\n\t\"Rebuild\": \"Reconstruir\",\n\t\"No database found\": \"No se ha encontrado la base de datos\",\n\n\t\"Identity address\": \"Dirección de la identidad\",\n\t\"Change\": \"Cambiar\",\n\n\t\"Update\": \"Actualizar\",\n\t\"Pause\": \"Pausar\",\n\t\"Resume\": \"Reanudar\",\n\t\"Delete\": \"Borrar\",\n\n\t\"Site address\": \"Dirección del sitio\",\n\t\"Donate\": \"Donar\",\n\n\t\"Missing files\": \"Ficheros perdidos\",\n\t\"{} try\": \"{} intento\",\n\t\"{} tries\": \"{} intentos\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} más\",\n\n\t\"This is my site\": \"Este es mi sitio\",\n\t\"Site title\": \"Título del sitio\",\n\t\"Site description\": \"Descripción del sitio\",\n\t\"Save site settings\": \"Guardar la configuración del sitio\",\n\n\t\"Content publishing\": \"Publicación del contenido\",\n\t\"Choose\": \"Elegir\",\n\t\"Sign\": \"Firmar\",\n\t\"Publish\": \"Publicar\",\n\t\"This function is disabled on this proxy\": \"Esta función está deshabilitada en este proxy\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"¡Error de la base de datos GeoLite2： {}!<br>Por favor, descárgalo manualmente y descomprime al directorio de datos：<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Descargando la base de datos de GeoLite2 (una única vez, ~20MB）...\",\n\t\"GeoLite2 City database downloaded!\": \"¡Base de datos de GeoLite2 descargada!\",\n\n\t\"Are you sure?\": \"¿Estás seguro?\",\n\t\"Site storage limit modified!\": \"¡Límite de almacenamiento del sitio modificado!\",\n\t\"Database schema reloaded!\": \"¡Esquema de la base de datos recargado!\",\n\t\"Database rebuilding....\": \"Reconstruyendo la base de datos...\",\n\t\"Database rebuilt!\": \"¡Base de datos reconstruida!\",\n\t\"Site updated!\": \"¡Sitio actualizado!\",\n\t\"Delete this site\": \"Borrar este sitio\",\n\t\"File write error: \": \"Error de escritura de fichero：\",\n\t\"Site settings saved!\": \"¡Configuración del sitio guardada!\",\n\t\"Enter your private key:\": \"Introduce tu clave privada：\",\n\t\" Signed!\": \" ¡firmado!\",\n\t\"WebGL not supported\": \"WebGL no está soportado\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/fr.json",
    "content": "{\n\t\"Peers\": \"Pairs\",\n\t\"Connected\": \"Connectés\",\n\t\"Connectable\": \"Accessibles\",\n\t\"Connectable peers\": \"Pairs accessibles\",\n\n\t\"Data transfer\": \"Données transférées\",\n\t\"Received\": \"Reçues\",\n\t\"Received bytes\": \"Bytes reçus\",\n\t\"Sent\": \"Envoyées\",\n\t\"Sent bytes\": \"Bytes envoyés\",\n\n\t\"Files\": \"Fichiers\",\n\t\"Total\": \"Total\",\n\t\"Image\": \"Image\",\n\t\"Other\": \"Autre\",\n\t\"User data\": \"Utilisateurs\",\n\n\t\"Size limit\": \"Taille maximale\",\n\t\"limit used\": \"utlisé\",\n\t\"free space\": \"libre\",\n\t\"Set\": \"Modifier\",\n\n\t\"Optional files\": \"Fichiers optionnels\",\n\t\"Downloaded\": \"Téléchargé\",\n\t\"Download and help distribute all files\": \"Télécharger et distribuer tous les fichiers\",\n\t\"Total size\": \"Taille totale\",\n\t\"Downloaded files\": \"Fichiers téléchargés\",\n\n\t\"Database\": \"Base de données\",\n\t\"search feeds\": \"recherche\",\n\t\"{feeds} query\": \"{feeds} requête\",\n\t\"Reload\": \"Recharger\",\n\t\"Rebuild\": \"Reconstruire\",\n\t\"No database found\": \"Aucune base de données trouvée\",\n\n\t\"Identity address\": \"Adresse d'identité\",\n\t\"Change\": \"Modifier\",\n\n\t\"Site control\": \"Opérations\",\n\t\"Update\": \"Mettre à jour\",\n\t\"Pause\": \"Suspendre\",\n\t\"Resume\": \"Reprendre\",\n\t\"Delete\": \"Supprimer\",\n\t\"Are you sure?\": \"Êtes-vous certain?\",\n\n\t\"Site address\": \"Adresse du site\",\n\t\"Donate\": \"Faire un don\",\n\n\t\"Missing files\": \"Fichiers manquants\",\n\t\"{} try\": \"{} essai\",\n\t\"{} tries\": \"{} essais\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} manquants\",\n\n\t\"This is my site\": \"Ce site m'appartient\",\n\t\"Site title\": \"Nom du site\",\n\t\"Site description\": \"Description du site\",\n\t\"Save site settings\": \"Enregistrer les paramètres\",\n\n\t\"Content publishing\": \"Publication du contenu\",\n\t\"Choose\": \"Sélectionner\",\n\t\"Sign\": \"Signer\",\n\t\"Publish\": \"Publier\",\n\n\t\"This function is disabled on this proxy\": \"Cette fonction est désactivé sur ce proxy\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"Erreur au téléchargement de la base de données GeoLite2: {}!<br>Téléchargez et décompressez dans le dossier data:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Téléchargement de la base de données GeoLite2 (une seule fois, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"Base de données GeoLite2 téléchargée!\",\n\n\t\"Are you sure?\": \"Êtes-vous certain?\",\n\t\"Site storage limit modified!\": \"Taille maximale modifiée!\",\n\t\"Database schema reloaded!\": \"Base de données rechargée!\",\n\t\"Database rebuilding....\": \"Reconstruction de la base de données...\",\n\t\"Database rebuilt!\": \"Base de données reconstruite!\",\n\t\"Site updated!\": \"Site mis à jour!\",\n\t\"Delete this site\": \"Supprimer ce site\",\n\t\"File write error: \": \"Erreur à l'écriture du fichier: \",\n\t\"Site settings saved!\": \"Paramètres du site enregistrés!\",\n\t\"Enter your private key:\": \"Entrez votre clé privée:\",\n\t\" Signed!\": \" Signé!\",\n\t\"WebGL not supported\": \"WebGL n'est pas supporté\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/hu.json",
    "content": "{\n\t\"Peers\": \"Csatlakozási pontok\",\n\t\"Connected\": \"Csaltakozva\",\n\t\"Connectable\": \"Csatlakozható\",\n\t\"Connectable peers\": \"Csatlakozható peer-ek\",\n\n\t\"Data transfer\": \"Adatátvitel\",\n\t\"Received\": \"Fogadott\",\n\t\"Received bytes\": \"Fogadott byte-ok\",\n\t\"Sent\": \"Küldött\",\n\t\"Sent bytes\": \"Küldött byte-ok\",\n\n\t\"Files\": \"Fájlok\",\n\t\"Total\": \"Összesen\",\n\t\"Image\": \"Kép\",\n\t\"Other\": \"Egyéb\",\n\t\"User data\": \"Felh. adat\",\n\n\t\"Size limit\": \"Méret korlát\",\n\t\"limit used\": \"felhasznált\",\n\t\"free space\": \"szabad hely\",\n\t\"Set\": \"Beállít\",\n\n\t\"Optional files\": \"Opcionális fájlok\",\n\t\"Downloaded\": \"Letöltött\",\n\t\"Download and help distribute all files\": \"Minden opcionális fájl letöltése\",\n\t\"Total size\": \"Teljes méret\",\n\t\"Downloaded files\": \"Letöltve\",\n\n\t\"Database\": \"Adatbázis\",\n\t\"search feeds\": \"Keresés források\",\n\t\"{feeds} query\": \"{feeds} lekérdezés\",\n\t\"Reload\": \"Újratöltés\",\n\t\"Rebuild\": \"Újraépítés\",\n\t\"No database found\": \"Adatbázis nem található\",\n\n\t\"Identity address\": \"Azonosító cím\",\n\t\"Change\": \"Módosít\",\n\n\t\"Site control\": \"Oldal műveletek\",\n\t\"Update\": \"Frissít\",\n\t\"Pause\": \"Szünteltet\",\n\t\"Resume\": \"Folytat\",\n\t\"Delete\": \"Töröl\",\n\t\"Are you sure?\": \"Biztos vagy benne?\",\n\n\t\"Site address\": \"Oldal címe\",\n\t\"Donate\": \"Támogatás\",\n\n\t\"Missing files\": \"Hiányzó fájlok\",\n\t\"{} try\": \"{} próbálkozás\",\n\t\"{} tries\": \"{} próbálkozás\",\n\t\"+ {num_bad_files} more\": \"+ még {num_bad_files} darab\",\n\n\t\"This is my site\": \"Ez az én oldalam\",\n\t\"Site title\": \"Oldal neve\",\n\t\"Site description\": \"Oldal leírása\",\n\t\"Save site settings\": \"Oldal beállítások mentése\",\n\n\t\"Content publishing\": \"Tartalom publikálás\",\n\t\"Choose\": \"Válassz\",\n\t\"Sign\": \"Aláírás\",\n\t\"Publish\": \"Publikálás\",\n\n\t\"This function is disabled on this proxy\": \"Ez a funkció ki van kapcsolva ezen a proxy-n\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 város adatbázis letöltési hiba: {}!<br>A térképhez töltsd le és csomagold ki a data könyvtárba:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"GeoLite2 város adatbázis letöltése (csak egyszer kell, kb 20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 város adatbázis letöltve!\",\n\n\t\"Are you sure?\": \"Biztos vagy benne?\",\n\t\"Site storage limit modified!\": \"Az oldalt méret korlát módosítva!\",\n\t\"Database schema reloaded!\": \"Adatbázis séma újratöltve!\",\n\t\"Database rebuilding....\": \"Adatbázis újraépítés...\",\n\t\"Database rebuilt!\": \"Adatbázis újraépítve!\",\n\t\"Site updated!\": \"Az oldal frissítve!\",\n\t\"Delete this site\": \"Az oldal törlése\",\n\t\"File write error: \": \"Fájl írási hiba: \",\n\t\"Site settings saved!\": \"Az oldal beállításai elmentve!\",\n\t\"Enter your private key:\": \"Add meg a privát kulcsod:\",\n\t\" Signed!\": \" Aláírva!\",\n\t\"WebGL not supported\": \"WebGL nem támogatott\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/it.json",
    "content": "{\n\t\"Peers\": \"Peer\",\n\t\"Connected\": \"Connessi\",\n\t\"Connectable\": \"Collegabili\",\n\t\"Connectable peers\": \"Peer collegabili\",\n\n\t\"Data transfer\": \"Trasferimento dati\",\n\t\"Received\": \"Ricevuti\",\n\t\"Received bytes\": \"Byte ricevuti\",\n\t\"Sent\": \"Inviati\",\n\t\"Sent bytes\": \"Byte inviati\",\n\n\t\"Files\": \"File\",\n\t\"Total\": \"Totale\",\n\t\"Image\": \"Imagine\",\n\t\"Other\": \"Altro\",\n\t\"User data\": \"Dati utente\",\n\n\t\"Size limit\": \"Limite dimensione\",\n\t\"limit used\": \"limite usato\",\n\t\"free space\": \"spazio libero\",\n\t\"Set\": \"Imposta\",\n\n\t\"Optional files\": \"File facoltativi\",\n\t\"Downloaded\": \"Scaricati\",\n\t\"Download and help distribute all files\": \"Scarica e aiuta a distribuire tutti i file\",\n\t\"Total size\": \"Dimensione totale\",\n\t\"Downloaded files\": \"File scaricati\",\n\n\t\"Database\": \"Database\",\n\t\"search feeds\": \"ricerca di feed\",\n\t\"{feeds} query\": \"{feeds} interrogazione\",\n\t\"Reload\": \"Ricaricare\",\n\t\"Rebuild\": \"Ricostruire\",\n\t\"No database found\": \"Nessun database trovato\",\n\n\t\"Identity address\": \"Indirizzo di identità\",\n\t\"Change\": \"Cambia\",\n\n\t\"Update\": \"Aggiorna\",\n\t\"Pause\": \"Sospendi\",\n\t\"Resume\": \"Riprendi\",\n\t\"Delete\": \"Cancella\",\n\t\"Are you sure?\": \"Sei sicuro?\",\n\n\t\"Site address\": \"Indirizzo sito\",\n\t\"Donate\": \"Dona\",\n\n\t\"Missing files\": \"File mancanti\",\n\t\"{} try\": \"{} tenta\",\n\t\"{} tries\": \"{} prova\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} altri\",\n\n\t\"This is my site\": \"Questo è il mio sito\",\n\t\"Site title\": \"Titolo sito\",\n\t\"Site description\": \"Descrizione sito\",\n\t\"Save site settings\": \"Salva impostazioni sito\",\n\n\t\"Content publishing\": \"Pubblicazione contenuto\",\n\t\"Choose\": \"Scegli\",\n\t\"Sign\": \"Firma\",\n\t\"Publish\": \"Pubblica\",\n\n\t\"This function is disabled on this proxy\": \"Questa funzione è disabilitata su questo proxy\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"Errore scaricamento database GeoLite2 City: {}!<br>Si prega di scaricarlo manualmente e spacchetarlo nella cartella dir:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Scaricamento database GeoLite2 City (solo una volta, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"Database GeoLite2 City scaricato!\",\n\n\t\"Are you sure?\": \"Sei sicuro?\",\n\t\"Site storage limit modified!\": \"Limite di archiviazione del sito modificato!\",\n\t\"Database schema reloaded!\": \"Schema database ricaricato!\",\n\t\"Database rebuilding....\": \"Ricostruzione database...\",\n\t\"Database rebuilt!\": \"Database ricostruito!\",\n\t\"Site updated!\": \"Sito aggiornato!\",\n\t\"Delete this site\": \"Cancella questo sito\",\n\t\"File write error: \": \"Errore scrittura file:\",\n\t\"Site settings saved!\": \"Impostazioni sito salvate!\",\n\t\"Enter your private key:\": \"Inserisci la tua chiave privata:\",\n\t\" Signed!\": \" Firmato!\",\n\t\"WebGL not supported\": \"WebGL non supportato\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/jp.json",
    "content": "{\n\t\"Copy to clipboard\": \"クリップボードにコピー\",\n\t\"Peers\": \"ピア\",\n\t\"Connected\": \"接続済み\",\n\t\"Connectable\": \"利用可能\",\n\t\"Connectable peers\": \"ピアに接続可能\",\n\t\"Onion\": \"Onion\",\n\t\"Local\": \"ローカル\",\n\n\t\"Data transfer\": \"データ転送\",\n\t\"Received\": \"受信\",\n\t\"Received bytes\": \"受信バイト数\",\n\t\"Sent\": \"送信\",\n\t\"Sent bytes\": \"送信バイト数\",\n\n\t\"Files\": \"ファイル\",\n\t\"Browse files\": \"ファイルを見る\",\n\t\"Save as .zip\": \"ZIP形式で保存\",\n\t\"Total\": \"合計\",\n\t\"Image\": \"画像\",\n\t\"Other\": \"その他\",\n\t\"User data\": \"ユーザーデータ\",\n\n\t\"Size limit\": \"サイズ制限\",\n\t\"limit used\": \"使用上限\",\n\t\"free space\": \"フリースペース\",\n\t\"Set\": \"セット\",\n\n\t\"Optional files\": \"オプション　ファイル\",\n\t\"Downloaded\": \"ダウンロード済み\",\n\t\"Help distribute added optional files\": \"オプションファイルの配布を支援する\",\n\t\"Auto download big file size limit\": \"大きなファイルの自動ダウンロードのサイズ制限\",\n\t\"Download previous files\": \"以前のファイルのダウンロード\",\n\t\"Optional files download started\": \"オプションファイルのダウンロードを開始\",\n\t\"Optional files downloaded\": \"オプションファイルのダウンロードが完了しました\",\n\t\"Download and help distribute all files\": \"ダウンロードしてすべてのファイルの配布を支援する\",\n\t\"Total size\": \"合計サイズ\",\n\t\"Downloaded files\": \"ダウンロードされたファイル\",\n\n\t\"Database\": \"データベース\",\n\t\"search feeds\": \"フィードを検索する\",\n\t\"{feeds} query\": \"{feeds} お問い合わせ\",\n\t\"Reload\": \"再読込\",\n\t\"Rebuild\": \"再ビルド\",\n\t\"No database found\": \"データベースが見つかりません\",\n\n\t\"Identity address\": \"あなたの識別アドレス\",\n\t\"Change\": \"編集\",\n\n\t\"Site control\": \"サイト管理\",\n\t\"Update\": \"更新\",\n\t\"Pause\": \"一時停止\",\n\t\"Resume\": \"再開\",\n\t\"Delete\": \"削除\",\n\t\"Are you sure?\": \"本当によろしいですか？\",\n\n\t\"Site address\": \"サイトアドレス\",\n\t\"Donate\": \"寄付する\",\n\n\t\"Missing files\": \"ファイルがありません\",\n\t\"{} try\": \"{} 試す\",\n\t\"{} tries\": \"{} 試行\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} more\",\n\n\t\"This is my site\": \"これは私のサイトです\",\n\t\"Site title\": \"サイトタイトル\",\n\t\"Site description\": \"サイトの説明\",\n\t\"Save site settings\": \"サイトの設定を保存する\",\n\t\"Open site directory\": \"サイトのディレクトリを開く\",\n\n\t\"Content publishing\": \"コンテンツを公開する\",\n\t\"Add saved private key\": \"秘密鍵の追加と保存\",\n\t\"Save\": \"保存\",\n\t\"Private key saved.\": \"秘密鍵が保存されています\",\n\t\"Private key saved for site signing\": \"サイトに署名するための秘密鍵を保存\",\n\t\"Forgot\": \"わすれる\",\n\t\"Saved private key removed\": \"保存された秘密鍵を削除しました\",\n\t\"Choose\": \"選択\",\n\t\"Sign\": \"署名\",\n\t\"Publish\": \"公開する\",\n\t\"Sign and publish\": \"署名して公開\",\n\n\t\"This function is disabled on this proxy\": \"この機能はこのプロキシで無効になっています\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 Cityデータベースのダウンロードエラー: {}!<br>手動でダウンロードして、フォルダに解凍してください。:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"GeoLite2 Cityデータベースの読み込み (これは一度だけ行われます, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 Cityデータベースがダウンロードされました！\",\n\n\t\"Are you sure?\": \"本当によろしいですか？\",\n\t\"Site storage limit modified!\": \"サイトの保存容量の制限が変更されました！\",\n\t\"Database schema reloaded!\": \"データベーススキーマがリロードされました！\",\n\t\"Database rebuilding....\": \"データベースの再構築中....\",\n\t\"Database rebuilt!\": \"データベースが再構築されました！\",\n\t\"Site updated!\": \"サイトが更新されました！\",\n\t\"Delete this site\": \"このサイトを削除する\",\n\t\"Blacklist\": \"NG\",\n\t\"Blacklist this site\": \"NGリストに入れる\",\n\t\"Reason\": \"理由\",\n\t\"Delete and Blacklist\": \"削除してNG\",\n\t\"File write error: \": \"ファイル書き込みエラー:\",\n\t\"Site settings saved!\": \"サイト設定が保存されました！\",\n\t\"Enter your private key:\": \"秘密鍵を入力してください:\",\n\t\" Signed!\": \" 署名しました!\",\n\t\"WebGL not supported\": \"WebGLはサポートされていません\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/pl.json",
    "content": "{\n\t\"Peers\": \"Użytkownicy równorzędni\",\n\t\"Connected\": \"Połączony\",\n\t\"Connectable\": \"Możliwy do podłączenia\",\n\t\"Connectable peers\": \"Połączeni użytkownicy równorzędni\",\n\n\t\"Data transfer\": \"Transfer danych\",\n\t\"Received\": \"Odebrane\",\n\t\"Received bytes\": \"Odebrany bajty\",\n\t\"Sent\": \"Wysłane\",\n\t\"Sent bytes\": \"Wysłane bajty\",\n\n\t\"Files\": \"Pliki\",\n\t\"Total\": \"Sumarycznie\",\n\t\"Image\": \"Obraz\",\n\t\"Other\": \"Inne\",\n\t\"User data\": \"Dane użytkownika\",\n\n\t\"Size limit\": \"Rozmiar limitu\",\n\t\"limit used\": \"zużyty limit\",\n\t\"free space\": \"wolna przestrzeń\",\n\t\"Set\": \"Ustaw\",\n\n\t\"Optional files\": \"Pliki opcjonalne\",\n\t\"Downloaded\": \"Ściągnięte\",\n\t\"Download and help distribute all files\": \"Ściągnij i pomóż rozpowszechniać wszystkie pliki\",\n\t\"Total size\": \"Rozmiar sumaryczny\",\n\t\"Downloaded files\": \"Ściągnięte pliki\",\n\n\t\"Database\": \"Baza danych\",\n\t\"search feeds\": \"przeszukaj zasoby\",\n\t\"{feeds} query\": \"{feeds} pytanie\",\n\t\"Reload\": \"Odśwież\",\n\t\"Rebuild\": \"Odbuduj\",\n\t\"No database found\": \"Nie odnaleziono bazy danych\",\n\n\t\"Identity address\": \"Adres identyfikacyjny\",\n\t\"Change\": \"Zmień\",\n\n\t\"Site control\": \"Kontrola strony\",\n\t\"Update\": \"Zaktualizuj\",\n\t\"Pause\": \"Wstrzymaj\",\n\t\"Resume\": \"Wznów\",\n\t\"Delete\": \"Skasuj\",\n\t\"Are you sure?\": \"Jesteś pewien?\",\n\n\t\"Site address\": \"Adres strony\",\n\t\"Donate\": \"Wspomóż\",\n\n\t\"Missing files\": \"Brakujące pliki\",\n\t\"{} try\": \"{} próba\",\n\t\"{} tries\": \"{} próby\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} więcej\",\n\n\t\"This is my site\": \"To moja strona\",\n\t\"Site title\": \"Tytuł strony\",\n\t\"Site description\": \"Opis strony\",\n\t\"Save site settings\": \"Zapisz ustawienia strony\",\n\n\t\"Content publishing\": \"Publikowanie treści\",\n\t\"Choose\": \"Wybierz\",\n\t\"Sign\": \"Podpisz\",\n\t\"Publish\": \"Opublikuj\",\n\n\t\"This function is disabled on this proxy\": \"Ta funkcja jest zablokowana w tym proxy\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"Błąd ściągania bazy danych GeoLite2 City: {}!<br>Proszę ściągnąć ją recznie i wypakować do katalogu danych:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Ściąganie bazy danych GeoLite2 City (tylko jednorazowo, ok. 20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"Baza danych GeoLite2 City ściagnięta!\",\n\n\t\"Are you sure?\": \"Jesteś pewien?\",\n\t\"Site storage limit modified!\": \"Limit pamięci strony zmodyfikowany!\",\n\t\"Database schema reloaded!\": \"Schemat bazy danych załadowany ponownie!\",\n\t\"Database rebuilding....\": \"Przebudowywanie bazy danych...\",\n\t\"Database rebuilt!\": \"Baza danych przebudowana!\",\n\t\"Site updated!\": \"Strona zaktualizowana!\",\n\t\"Delete this site\": \"Usuń tę stronę\",\n\t\"File write error: \": \"Błąd zapisu pliku: \",\n\t\"Site settings saved!\": \"Ustawienia strony zapisane!\",\n\t\"Enter your private key:\": \"Wpisz swój prywatny klucz:\",\n\t\" Signed!\": \" Podpisane!\",\n\t\"WebGL not supported\": \"WebGL nie jest obsługiwany\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/pt-br.json",
    "content": "{\n\t\"Copy to clipboard\": \"Copiar para área de transferência (clipboard)\",\n\t\"Peers\": \"Peers\",\n\t\"Connected\": \"Ligados\",\n\t\"Connectable\": \"Disponíveis\",\n\t\"Onion\": \"Onion\",\n\t\"Local\": \"Locais\",\n\t\"Connectable peers\": \"Peers disponíveis\",\n\n\t\"Data transfer\": \"Transferência de dados\",\n\t\"Received\": \"Recebidos\",\n\t\"Received bytes\": \"Bytes recebidos\",\n\t\"Sent\": \"Enviados\",\n\t\"Sent bytes\": \"Bytes enviados\",\n\n\t\"Files\": \"Arquivos\",\n\t\"Save as .zip\": \"Salvar como .zip\",\n\t\"Total\": \"Total\",\n\t\"Image\": \"Imagem\",\n\t\"Other\": \"Outros\",\n\t\"User data\": \"Dados do usuário\",\n\n\t\"Size limit\": \"Limite de tamanho\",\n\t\"limit used\": \"limite utilizado\",\n\t\"free space\": \"espaço livre\",\n\t\"Set\": \"Definir\",\n\n\t\"Optional files\": \"Arquivos opcionais\",\n\t\"Downloaded\": \"Baixados\",\n\t\"Download and help distribute all files\": \"Baixar e ajudar a distribuir todos os arquivos\",\n\t\"Total size\": \"Tamanho total\",\n\t\"Downloaded files\": \"Arquivos baixados\",\n\n\t\"Database\": \"Banco de dados\",\n\t\"search feeds\": \"pesquisar feeds\",\n\t\"{feeds} query\": \"consulta de {feeds}\",\n\t\"Reload\": \"Recarregar\",\n\t\"Rebuild\": \"Reconstruir\",\n\t\"No database found\": \"Base de dados não encontrada\",\n\n\t\"Identity address\": \"Endereço de identidade\",\n\t\"Change\": \"Alterar\",\n\n\t\"Site control\": \"Controle do site\",\n\t\"Update\": \"Atualizar\",\n\t\"Pause\": \"Suspender\",\n\t\"Resume\": \"Continuar\",\n\t\"Delete\": \"Remover\",\n\t\"Are you sure?\": \"Tem certeza?\",\n\n\t\"Site address\": \"Endereço do site\",\n\t\"Donate\": \"Doar\",\n\n\t\"Needs to be updated\": \"Necessitam ser atualizados\",\n\t\"{} try\": \"{} tentativa\",\n\t\"{} tries\": \"{} tentativas\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} adicionais\",\n\n\t\"This is my site\": \"Este é o meu site\",\n\t\"Site title\": \"Título do site\",\n\t\"Site description\": \"Descrição do site\",\n\t\"Save site settings\": \"Salvar definições do site\",\n\t\"Open site directory\": \"Abrir diretório do site\",\n\n\t\"Content publishing\": \"Publicação do conteúdo\",\n\t\"Choose\": \"Escolher\",\n\t\"Sign\": \"Assinar\",\n\t\"Publish\": \"Publicar\",\n\t\"Sign and publish\": \"Assinar e publicar\",\n\t\"add saved private key\": \"adicionar privatekey (chave privada) para salvar\",\n\t\"Private key saved for site signing\": \"Privatekey foi salva para assinar o site\",\n\t\"Private key saved.\": \"Privatekey salva.\",\n\t\"forgot\": \"esquecer\",\n\t\"Saved private key removed\": \"Privatekey salva foi removida\",\n\t\"This function is disabled on this proxy\": \"Esta função encontra-se desativada neste proxy\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"Erro ao baixar a base de dados GeoLite2 City: {}!<br>Por favor baixe manualmente e descompacte os dados para a seguinte pasta:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Baixando a base de dados GeoLite2 City (uma única vez, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"A base de dados GeoLite2 City foi baixada!\",\n\n\t\"Are you sure?\": \"Tem certeza?\",\n\t\"Site storage limit modified!\": \"O limite de armazenamento do site foi modificado!\",\n\t\"Database schema reloaded!\": \"O esquema da base de dados foi atualizado!\",\n\t\"Database rebuilding....\": \"Reconstruindo base de dados...\",\n\t\"Database rebuilt!\": \"Base de dados reconstruída!\",\n\t\"Site updated!\": \"Site atualizado!\",\n\t\"Delete this site\": \"Remover este site\",\n\t\"Blacklist\": \"Blacklist\",\n\t\"Blacklist this site\": \"Blacklistar este site\",\n\t\"Reason\": \"Motivo\",\n\t\"Delete and Blacklist\": \"Deletar e blacklistar\",\n\t\"File write error: \": \"Erro de escrita de arquivo: \",\n\t\"Site settings saved!\": \"Definições do site salvas!\",\n\t\"Enter your private key:\": \"Digite sua chave privada:\",\n\t\" Signed!\": \" Assinado!\",\n\t\"WebGL not supported\": \"WebGL não é suportado\",\n\t\"Save as .zip\": \"Salvar como .zip\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/ru.json",
    "content": "{\n\t\"Peers\": \"Пиры\",\n\t\"Connected\": \"Подключенные\",\n\t\"Connectable\": \"Доступные\",\n\t\"Connectable peers\": \"Пиры доступны для подключения\",\n\n\t\"Data transfer\": \"Передача данных\",\n\t\"Received\": \"Получено\",\n\t\"Received bytes\": \"Получено байн\",\n\t\"Sent\": \"Отправлено\",\n\t\"Sent bytes\": \"Отправлено байт\",\n\n\t\"Files\": \"Файлы\",\n\t\"Total\": \"Всего\",\n\t\"Image\": \"Изображений\",\n\t\"Other\": \"Другое\",\n\t\"User data\": \"Ваш контент\",\n\n\t\"Size limit\": \"Ограничение по размеру\",\n\t\"limit used\": \"Использовано\",\n\t\"free space\": \"Доступно\",\n\t\"Set\": \"Установить\",\n\n\t\"Optional files\": \"Опциональные файлы\",\n\t\"Downloaded\": \"Загружено\",\n\t\"Download and help distribute all files\": \"Загрузить опциональные файлы для помощи сайту\",\n\t\"Total size\": \"Объём\",\n\t\"Downloaded files\": \"Загруженные файлы\",\n\n\t\"Database\": \"База данных\",\n\t\"search feeds\": \"поиск подписок\",\n\t\"{feeds} query\": \"{feeds} запрос\",\n\t\"Reload\": \"Перезагрузить\",\n\t\"Rebuild\": \"Перестроить\",\n\t\"No database found\": \"База данных не найдена\",\n\n\t\"Identity address\": \"Уникальный адрес\",\n\t\"Change\": \"Изменить\",\n\n\t\"Site control\": \"Управление сайтом\",\n\t\"Update\": \"Обновить\",\n\t\"Pause\": \"Пауза\",\n\t\"Resume\": \"Продолжить\",\n\t\"Delete\": \"Удалить\",\n\t\"Are you sure?\": \"Вы уверены?\",\n\n\t\"Site address\": \"Адрес сайта\",\n\t\"Donate\": \"Пожертвовать\",\n\n\t\"Missing files\": \"Отсутствующие файлы\",\n\t\"{} try\": \"{} попробовать\",\n\t\"{} tries\": \"{} попыток\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} ещё\",\n\n\t\"This is my site\": \"Это мой сайт\",\n\t\"Site title\": \"Название сайта\",\n\t\"Site description\": \"Описание сайта\",\n\t\"Save site settings\": \"Сохранить настройки сайта\",\n\n\t\"Content publishing\": \"Публикация контента\",\n\t\"Choose\": \"Выбрать\",\n\t\"Sign\": \"Подписать\",\n\t\"Publish\": \"Опубликовать\",\n\n\t\"This function is disabled on this proxy\": \"Эта функция отключена на этом прокси\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"Ошибка загрузки базы городов GeoLite2: {}!<br>Пожалуйста, загрузите её вручную и распакуйте в папку:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"Загрузка базы городов GeoLite2 (это делается только 1 раз, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"База GeoLite2 успешно загружена!\",\n\n\t\"Are you sure?\": \"Вы уверены?\",\n\t\"Site storage limit modified!\": \"Лимит хранилища для сайта изменен!\",\n\t\"Database schema reloaded!\": \"Схема базы данных перезагружена!\",\n\t\"Database rebuilding....\": \"Перестройка базы данных...\",\n\t\"Database rebuilt!\": \"База данных перестроена!\",\n\t\"Site updated!\": \"Сайт обновлён!\",\n\t\"Delete this site\": \"Удалить этот сайт\",\n\t\"File write error: \": \"Ошибка записи файла:\",\n\t\"Site settings saved!\": \"Настройки сайта сохранены!\",\n\t\"Enter your private key:\": \"Введите свой приватный ключ:\",\n\t\" Signed!\": \" Подписано!\",\n\t\"WebGL not supported\": \"WebGL не поддерживается\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/tr.json",
    "content": "{\n\t\"Peers\": \"Eşler\",\n\t\"Connected\": \"Bağlı\",\n\t\"Connectable\": \"Erişilebilir\",\n\t\"Connectable peers\": \"Bağlanılabilir eşler\",\n\n\t\"Data transfer\": \"Veri aktarımı\",\n\t\"Received\": \"Alınan\",\n\t\"Received bytes\": \"Bayt alındı\",\n\t\"Sent\": \"Gönderilen\",\n\t\"Sent bytes\": \"Bayt gönderildi\",\n\n\t\"Files\": \"Dosyalar\",\n\t\"Total\": \"Toplam\",\n\t\"Image\": \"Resim\",\n\t\"Other\": \"Diğer\",\n\t\"User data\": \"Kullanıcı verisi\",\n\n\t\"Size limit\": \"Boyut sınırı\",\n\t\"limit used\": \"kullanılan\",\n\t\"free space\": \"boş\",\n\t\"Set\": \"Ayarla\",\n\n\t\"Optional files\": \"İsteğe bağlı dosyalar\",\n\t\"Downloaded\": \"İndirilen\",\n\t\"Download and help distribute all files\": \"Tüm dosyaları indir ve yayılmalarına yardım et\",\n\t\"Total size\": \"Toplam boyut\",\n\t\"Downloaded files\": \"İndirilen dosyalar\",\n\n\t\"Database\": \"Veritabanı\",\n\t\"search feeds\": \"kaynak ara\",\n\t\"{feeds} query\": \"{feeds} sorgu\",\n\t\"Reload\": \"Yenile\",\n\t\"Rebuild\": \"Yapılandır\",\n\t\"No database found\": \"Veritabanı yok\",\n\n\t\"Identity address\": \"Kimlik adresi\",\n\t\"Change\": \"Değiştir\",\n\n\t\"Site control\": \"Site kontrolü\",\n\t\"Update\": \"Güncelle\",\n\t\"Pause\": \"Duraklat\",\n\t\"Resume\": \"Sürdür\",\n\t\"Delete\": \"Sil\",\n\t\"Are you sure?\": \"Emin misin?\",\n\n\t\"Site address\": \"Site adresi\",\n\t\"Donate\": \"Bağış yap\",\n\n\t\"Missing files\": \"Eksik dosyalar\",\n\t\"{} try\": \"{} deneme\",\n\t\"{} tries\": \"{} deneme\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} tane daha\",\n\n\t\"This is my site\": \"Bu benim sitem\",\n\t\"Site title\": \"Site başlığı\",\n\t\"Site description\": \"Site açıklaması\",\n\t\"Save site settings\": \"Site ayarlarını kaydet\",\n\n\t\"Content publishing\": \"İçerik yayımlanıyor\",\n\t\"Choose\": \"Seç\",\n\t\"Sign\": \"İmzala\",\n\t\"Publish\": \"Yayımla\",\n\n\t\"This function is disabled on this proxy\": \"Bu özellik bu vekilde kullanılamaz\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 Şehir veritabanı indirme hatası: {}!<br>Lütfen kendiniz indirip aşağıdaki konuma açınınız:<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"GeoLite2 Şehir veritabanı indiriliyor (sadece bir kere, ~20MB)...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 Şehir veritabanı indirildi!\",\n\n\t\"Are you sure?\": \"Emin misiniz?\",\n\t\"Site storage limit modified!\": \"Site saklama sınırı değiştirildi!\",\n\t\"Database schema reloaded!\": \"Veritabanı şeması yeniden yüklendi!\",\n\t\"Database rebuilding....\": \"Veritabanı yeniden inşa ediliyor...\",\n\t\"Database rebuilt!\": \"Veritabanı yeniden inşa edildi!\",\n\t\"Site updated!\": \"Site güncellendi!\",\n\t\"Delete this site\": \"Bu siteyi sil\",\n\t\"File write error: \": \"Dosya yazma hatası: \",\n\t\"Site settings saved!\": \"Site ayarları kaydedildi!\",\n\t\"Enter your private key:\": \"Özel anahtarınızı giriniz:\",\n\t\" Signed!\": \" İmzala!\",\n\t\"WebGL not supported\": \"WebGL desteklenmiyor\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/zh-tw.json",
    "content": "{\n\t\"Peers\": \"節點數\",\n\t\"Connected\": \"已連線\",\n\t\"Connectable\": \"可連線\",\n\t\"Connectable peers\": \"可連線節點\",\n\n\t\"Data transfer\": \"數據傳輸\",\n\t\"Received\": \"已接收\",\n\t\"Received bytes\": \"已接收位元組\",\n\t\"Sent\": \"已傳送\",\n\t\"Sent bytes\": \"已傳送位元組\",\n\n\t\"Files\": \"檔案\",\n\t\"Total\": \"共計\",\n\t\"Image\": \"圖片\",\n\t\"Other\": \"其他\",\n\t\"User data\": \"使用者數據\",\n\n\t\"Size limit\": \"大小限制\",\n\t\"limit used\": \"已使用\",\n\t\"free space\": \"可用空間\",\n\t\"Set\": \"偏好設定\",\n\n\t\"Optional files\": \"可選檔案\",\n\t\"Downloaded\": \"已下載\",\n\t\"Download and help distribute all files\": \"下載並幫助分發所有檔案\",\n\t\"Total size\": \"總大小\",\n\t\"Downloaded files\": \"下載的檔案\",\n\n\t\"Database\": \"資料庫\",\n\t\"search feeds\": \"搜尋供稿\",\n\t\"{feeds} query\": \"{feeds} 查詢 \",\n\t\"Reload\": \"重新整理\",\n\t\"Rebuild\": \"重建\",\n\t\"No database found\": \"未找到資料庫\",\n\n\t\"Identity address\": \"身分位址\",\n\t\"Change\": \"變更\",\n\n\t\"Site control\": \"網站控制\",\n\t\"Update\": \"更新\",\n\t\"Pause\": \"暫停\",\n\t\"Resume\": \"恢復\",\n\t\"Delete\": \"刪除\",\n\t\"Are you sure?\": \"你確定？\",\n\n\t\"Site address\": \"網站位址\",\n\t\"Donate\": \"捐贈\",\n\n\t\"Missing files\": \"缺少的檔案\",\n\t\"{} try\": \"{} 嘗試\",\n\t\"{} tries\": \"{} 已嘗試\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} 更多\",\n\n\t\"This is my site\": \"這是我的網站\",\n\t\"Site title\": \"網站標題\",\n\t\"Site description\": \"網站描述\",\n\t\"Save site settings\": \"存儲網站設定\",\n\t\"Open site directory\": \"打開所在資料夾\",\n\n\t\"Content publishing\": \"內容發布\",\n\t\"Choose\": \"選擇\",\n\t\"Sign\": \"簽署\",\n\t\"Publish\": \"發布\",\n\t\"Sign and publish\": \"簽名並發布\",\n\t\"This function is disabled on this proxy\": \"此代理上禁用此功能\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 地理位置資料庫下載錯誤：{}!<br>請手動下載並解壓到數據目錄：<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"正在下載 GeoLite2 地理位置資料庫 （僅一次，約 20MB ）...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 地理位置資料庫已下載！\",\n\n\t\"Are you sure?\": \"你確定？\",\n\t\"Site storage limit modified!\": \"網站存儲限制已變更！\",\n\t\"Database schema reloaded!\": \"資料庫架構重新加載！\",\n\t\"Database rebuilding....\": \"資料庫重建中...\",\n\t\"Database rebuilt!\": \"資料庫已重建！\",\n\t\"Site updated!\": \"網站已更新！\",\n\t\"Delete this site\": \"刪除此網站\",\n\t\"File write error: \": \"檔案寫入錯誤：\",\n\t\"Site settings saved!\": \"網站設置已保存！\",\n\t\"Enter your private key:\": \"輸入您的私鑰：\",\n\t\" Signed!\": \" 已簽署！\",\n\t\"WebGL not supported\": \"不支援 WebGL\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/languages/zh.json",
    "content": "{\n\t\"Copy to clipboard\": \"复制到剪切板\",\n\t\"Peers\": \"节点数\",\n\t\"Connected\": \"已连接\",\n\t\"Connectable\": \"可连接\",\n\t\"Onion\": \"洋葱点\",\n\t\"Local\": \"局域网\",\n\t\"Connectable peers\": \"可连接节点\",\n\n\t\"Data transfer\": \"数据传输\",\n\t\"Received\": \"已接收\",\n\t\"Received bytes\": \"已接收字节\",\n\t\"Sent\": \"已发送\",\n\t\"Sent bytes\": \"已发送字节\",\n\n\t\"Files\": \"文件\",\n\t\"Save as .zip\": \"打包成zip文件\",\n\t\"Total\": \"总计\",\n\t\"Image\": \"图像\",\n\t\"Other\": \"其他\",\n\t\"User data\": \"用户数据\",\n\n\t\"Size limit\": \"大小限制\",\n\t\"limit used\": \"限额\",\n\t\"free space\": \"剩余空间\",\n\t\"Set\": \"设置\",\n\n\t\"Optional files\": \"可选文件\",\n\t\"Downloaded\": \"已下载\",\n\t\"Help distribute added optional files\": \"帮助分发新的可选文件\",\n\t\"Auto download big file size limit\": \"自动下载大文件大小限制\",\n\t\"Download previous files\": \"下载之前的文件\",\n\t\"Optional files download started\": \"可选文件下载启动\",\n\t\"Optional files downloaded\": \"可选文件下载完成\",\n\t\"Total size\": \"总大小\",\n\t\"Downloaded files\": \"已下载文件\",\n\n\t\"Database\": \"数据库\",\n\t\"search feeds\": \"搜索数据源\",\n\t\"{feeds} query\": \"{feeds} 请求\",\n\t\"Reload\": \"重载\",\n\t\"Rebuild\": \"重建\",\n\t\"No database found\": \"没有找到数据库\",\n\n\t\"Identity address\": \"身份地址\",\n\t\"Change\": \"更改\",\n\n\t\"Site control\": \"站点控制\",\n\t\"Update\": \"更新\",\n\t\"Pause\": \"暂停\",\n\t\"Resume\": \"恢复\",\n\t\"Delete\": \"删除\",\n\t\"Are you sure?\": \"您确定吗？\",\n\n\t\"Site address\": \"站点地址\",\n\t\"Donate\": \"捐赠\",\n\n\t\"Needs to be updated\": \"需要更新\",\n\t\"{} try\": \"{} 尝试\",\n\t\"{} tries\": \"{} 已尝试\",\n\t\"+ {num_bad_files} more\": \"+ {num_bad_files} 更多\",\n\n\t\"This is my site\": \"这是我的站点\",\n\t\"Site title\": \"站点标题\",\n\t\"Site description\": \"站点描述\",\n\t\"Save site settings\": \"保存站点设置\",\n\t\"Open site directory\": \"打开所在文件夹\",\n\n\t\"Content publishing\": \"内容发布\",\n\t\"Add saved private key\": \"添加并保存私钥\",\n\t\"Save\": \"保存\",\n\t\"Private key saved.\": \"私钥已保存\",\n\t\"Private key saved for site signing\": \"已保存用于站点签名的私钥\",\n\t\"Forgot\": \"删除私钥\",\n\t\"Saved private key removed\": \"保存的私钥已删除\",\n\t\"Choose\": \"选择\",\n\t\"Sign\": \"签名\",\n\t\"Publish\": \"发布\",\n\t\"Sign and publish\": \"签名并发布\",\n\t\"This function is disabled on this proxy\": \"此功能在代理上被禁用\",\n\t\"GeoLite2 City database download error: {}!<br>Please download manually and unpack to data dir:<br>{}\": \"GeoLite2 地理位置数据库下载错误：{}!<br>请手动下载并解压在数据目录：<br>{}\",\n\t\"Downloading GeoLite2 City database (one time only, ~20MB)...\": \"正在下载 GeoLite2 地理位置数据库 （仅需一次，约 20MB ）...\",\n\t\"GeoLite2 City database downloaded!\": \"GeoLite2 地理位置数据库已下载！\",\n\n\t\"Are you sure?\": \"您确定吗？\",\n\t\"Site storage limit modified!\": \"站点存储限制已更改！\",\n\t\"Database schema reloaded!\": \"数据库模式已重新加载！\",\n\t\"Database rebuilding....\": \"数据库重建中...\",\n\t\"Database rebuilt!\": \"数据库已重建！\",\n\t\"Site updated!\": \"站点已更新！\",\n\t\"Delete this site\": \"删除此站点\",\n\t\"Blacklist\": \"黑名单\",\n\t\"Blacklist this site\": \"拉黑此站点\",\n\t\"Reason\": \"原因\",\n\t\"Delete and Blacklist\": \"删除并拉黑\",\n\t\"File write error: \": \"文件写入错误：\",\n\t\"Site settings saved!\": \"站点设置已保存！\",\n\t\"Enter your private key:\": \"输入您的私钥：\",\n\t\" Signed!\": \" 已签名！\",\n\t\"WebGL not supported\": \"不支持 WebGL\"\n}\n"
  },
  {
    "path": "plugins/Sidebar/media/Class.coffee",
    "content": "class Class\n\ttrace: true\n\n\tlog: (args...) ->\n\t\treturn unless @trace\n\t\treturn if typeof console is 'undefined'\n\t\targs.unshift(\"[#{@.constructor.name}]\")\n\t\tconsole.log(args...)\n\t\t@\n\t\t\n\tlogStart: (name, args...) ->\n\t\treturn unless @trace\n\t\t@logtimers or= {}\n\t\t@logtimers[name] = +(new Date)\n\t\t@log \"#{name}\", args..., \"(started)\" if args.length > 0\n\t\t@\n\t\t\n\tlogEnd: (name, args...) ->\n\t\tms = +(new Date)-@logtimers[name]\n\t\t@log \"#{name}\", args..., \"(Done in #{ms}ms)\"\n\t\t@ \n\nwindow.Class = Class"
  },
  {
    "path": "plugins/Sidebar/media/Console.coffee",
    "content": "class Console extends Class\n\tconstructor: (@sidebar) ->\n\t\t@tag = null\n\t\t@opened = false\n\t\t@filter = null\n\t\t@tab_types = [\n\t\t\t{title: \"All\", filter: \"\"},\n\t\t\t{title: \"Info\", filter: \"INFO\"},\n\t\t\t{title: \"Warning\", filter: \"WARNING\"},\n\t\t\t{title: \"Error\", filter: \"ERROR\"}\n\t\t]\n\t\t@read_size = 32 * 1024\n\t\t@tab_active = \"\"\n\t\t#@filter = @sidebar.wrapper.site_info.address_short\n\t\thandleMessageWebsocket_original = @sidebar.wrapper.handleMessageWebsocket\n\t\t@sidebar.wrapper.handleMessageWebsocket = (message) =>\n\t\t\tif message.cmd == \"logLineAdd\" and message.params.stream_id == @stream_id\n\t\t\t\t@addLines(message.params.lines)\n\t\t\telse\n\t\t\t\thandleMessageWebsocket_original(message)\n\n\t\t$(window).on \"hashchange\", =>\n\t\t\tif window.top.location.hash.startsWith(\"#ZeroNet:Console\")\n\t\t\t\t@open()\n\n\t\tif window.top.location.hash.startsWith(\"#ZeroNet:Console\")\n\t\t\tsetTimeout (=> @open()), 10\n\n\tcreateHtmltag: ->\n\t\tif not @container\n\t\t\t@container = $(\"\"\"\n\t\t\t<div class=\"console-container\">\n\t\t\t\t<div class=\"console\">\n\t\t\t\t\t<div class=\"console-top\">\n\t\t\t\t\t\t<div class=\"console-tabs\"></div>\n\t\t\t\t\t\t<div class=\"console-text\">Loading...</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"console-middle\">\n\t\t\t\t\t\t<div class=\"mynode\"></div>\n\t\t\t\t\t\t<div class=\"peers\">\n\t\t\t\t\t\t\t<div class=\"peer\"><div class=\"line\"></div><a href=\"#\" class=\"icon\">\\u25BD</div></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t\"\"\")\n\t\t\t@text = @container.find(\".console-text\")\n\t\t\t@text_elem = @text[0]\n\t\t\t@tabs = @container.find(\".console-tabs\")\n\n\t\t\t@text.on \"mousewheel\", (e) =>  # Stop animation on manual scrolling\n\t\t\t\tif e.originalEvent.deltaY < 0\n\t\t\t\t\t@text.stop()\n\t\t\t\tRateLimit 300, @checkTextIsBottom\n\n\t\t\t@text.is_bottom = true\n\n\t\t\t@container.appendTo(document.body)\n\t\t\t@tag = @container.find(\".console\")\n\t\t\tfor tab_type in @tab_types\n\t\t\t\ttab = $(\"<a></a>\", {href: \"#\", \"data-filter\": tab_type.filter, \"data-title\": tab_type.title}).text(tab_type.title)\n\t\t\t\tif tab_type.filter == @tab_active\n\t\t\t\t\ttab.addClass(\"active\")\n\t\t\t\ttab.on(\"click\", @handleTabClick)\n\t\t\t\tif window.top.location.hash.endsWith(tab_type.title)\n\t\t\t\t\t@log \"Triggering click on\", tab\n\t\t\t\t\ttab.trigger(\"click\")\n\t\t\t\t@tabs.append(tab)\n\n\t\t\t@container.on \"mousedown touchend touchcancel\", (e) =>\n\t\t\t\tif e.target != e.currentTarget\n\t\t\t\t\treturn true\n\t\t\t\t@log \"closing\"\n\t\t\t\tif $(document.body).hasClass(\"body-console\")\n\t\t\t\t\t@close()\n\t\t\t\t\treturn true\n\n\t\t\t@loadConsoleText()\n\n\tcheckTextIsBottom: =>\n\t\t@text.is_bottom = Math.round(@text_elem.scrollTop + @text_elem.clientHeight) >= @text_elem.scrollHeight - 15\n\n\ttoColor: (text, saturation=60, lightness=70) ->\n\t\thash = 0\n\t\tfor i in [0..text.length-1]\n\t\t\thash += text.charCodeAt(i)*i\n\t\t\thash = hash % 1777\n\t\treturn \"hsl(\" + (hash % 360) + \",#{saturation}%,#{lightness}%)\";\n\n\tformatLine: (line) =>\n\t\tmatch = line.match(/(\\[.*?\\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/)\n\t\tif not match\n\t\t\treturn line.replace(/\\</g, \"&lt;\").replace(/\\>/g, \"&gt;\")\n\n\t\t[line, added, level, module, text] = line.match(/(\\[.*?\\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/)\n\t\tadded = \"<span style='color: #dfd0fa'>#{added}</span>\"\n\t\tlevel = \"<span style='color: #{@toColor(level, 100)};'>#{level}</span>\"\n\t\tmodule = \"<span style='color: #{@toColor(module, 60)}; font-weight: bold;'>#{module}</span>\"\n\n\t\ttext = text.replace(/(Site:[A-Za-z0-9\\.]+)/g, \"<span style='color: #AAAAFF'>$1</span>\")\n\t\ttext = text.replace(/\\</g, \"&lt;\").replace(/\\>/g, \"&gt;\")\n\t\t#text = text.replace(/( [0-9\\.]+(|s|ms))/g, \"<span style='color: #FFF;'>$1</span>\")\n\t\treturn \"#{added} #{level} #{module} #{text}\"\n\n\n\taddLines: (lines, animate=true) =>\n\t\thtml_lines = []\n\t\t@logStart \"formatting\"\n\t\tfor line in lines\n\t\t\thtml_lines.push @formatLine(line)\n\t\t@logEnd \"formatting\"\n\t\t@logStart \"adding\"\n\t\t@text.append(html_lines.join(\"<br>\") + \"<br>\")\n\t\t@logEnd \"adding\"\n\t\tif @text.is_bottom and animate\n\t\t\t@text.stop().animate({scrollTop: @text_elem.scrollHeight - @text_elem.clientHeight + 1}, 600, 'easeInOutCubic')\n\n\n\tloadConsoleText: =>\n\t\t@sidebar.wrapper.ws.cmd \"consoleLogRead\", {filter: @filter, read_size: @read_size}, (res) =>\n\t\t\t@text.html(\"\")\n\t\t\tpos_diff = res[\"pos_end\"] - res[\"pos_start\"]\n\t\t\tsize_read = Math.round(pos_diff/1024)\n\t\t\tsize_total = Math.round(res['pos_end']/1024)\n\t\t\t@text.append(\"<br><br>\")\n\t\t\t@text.append(\"Displaying #{res.lines.length} of #{res.num_found} lines found in the last #{size_read}kB of the log file. (#{size_total}kB total)<br>\")\n\t\t\t@addLines res.lines, false\n\t\t\t@text_elem.scrollTop = @text_elem.scrollHeight\n\t\tif @stream_id\n\t\t\t@sidebar.wrapper.ws.cmd \"consoleLogStreamRemove\", {stream_id: @stream_id}\n\t\t@sidebar.wrapper.ws.cmd \"consoleLogStream\", {filter: @filter}, (res) =>\n\t\t\t@stream_id = res.stream_id\n\n\tclose: =>\n\t\twindow.top.location.hash = \"\"\n\t\t@sidebar.move_lock = \"y\"\n\t\t@sidebar.startDrag()\n\t\t@sidebar.stopDrag()\n\n\topen: =>\n\t\t@sidebar.startDrag()\n\t\t@sidebar.moved(\"y\")\n\t\t@sidebar.fixbutton_targety = @sidebar.page_height - @sidebar.fixbutton_inity - 50\n\t\t@sidebar.stopDrag()\n\n\tonOpened: =>\n\t\t@sidebar.onClosed()\n\t\t@log \"onOpened\"\n\n\tonClosed: =>\n\t\t$(document.body).removeClass(\"body-console\")\n\t\tif @stream_id\n\t\t\t@sidebar.wrapper.ws.cmd \"consoleLogStreamRemove\", {stream_id: @stream_id}\n\n\tcleanup: =>\n\t\tif @container\n\t\t\t@container.remove()\n\t\t\t@container = null\n\n\tstopDragY: =>\n\t\t# Animate sidebar and iframe\n\t\tif @sidebar.fixbutton_targety == @sidebar.fixbutton_inity\n\t\t\t# Closed\n\t\t\ttargety = 0\n\t\t\t@opened = false\n\t\telse\n\t\t\t# Opened\n\t\t\ttargety = @sidebar.fixbutton_targety - @sidebar.fixbutton_inity\n\t\t\t@onOpened()\n\t\t\t@opened = true\n\n\t\t# Revent sidebar transitions\n\t\tif @tag\n\t\t\t@tag.css(\"transition\", \"0.5s ease-out\")\n\t\t\t@tag.css(\"transform\", \"translateY(#{targety}px)\").one transitionEnd, =>\n\t\t\t\t@tag.css(\"transition\", \"\")\n\t\t\t\tif not @opened\n\t\t\t\t\t@cleanup()\n\t\t# Revert body transformations\n\t\t@log \"stopDragY\", \"opened:\", @opened, targety\n\t\tif not @opened\n\t\t\t@onClosed()\n\n\tchangeFilter: (filter) =>\n\t\t@filter = filter\n\t\tif @filter == \"\"\n\t\t\t@read_size = 32 * 1024\n\t\telse\n\t\t\t@read_size = 5 * 1024 * 1024\n\t\t@loadConsoleText()\n\n\thandleTabClick: (e) =>\n\t\telem = $(e.currentTarget)\n\t\t@tab_active = elem.data(\"filter\")\n\t\t$(\"a\", @tabs).removeClass(\"active\")\n\t\telem.addClass(\"active\")\n\t\t@changeFilter(@tab_active)\n\t\twindow.top.location.hash = \"#ZeroNet:Console:\" + elem.data(\"title\")\n\t\treturn false\n\nwindow.Console = Console\n"
  },
  {
    "path": "plugins/Sidebar/media/Console.css",
    "content": ".console-container { width: 100%; z-index: 998; position: absolute; top: -100vh; padding-bottom: 100%; }\n.console-container .console { background-color: #212121; height: 100vh; transform: translateY(0px); padding-top: 80px; box-sizing: border-box; }\n\n.console-top { color: white; font-family: Consolas, monospace; font-size: 11px; line-height: 20px; height: 100%; box-sizing: border-box; letter-spacing: 0.5px;}\n.console-text { overflow-y: scroll; height: calc(100% - 10px); color: #DDD; padding: 5px; margin-top: -36px; overflow-wrap: break-word; }\n.console-tabs {\n    background-color: #41193fad; position: relative; margin-right: 17px; /*backdrop-filter: blur(2px);*/\n    box-shadow: -30px 0px 45px #7d2463; background: linear-gradient(-75deg, #591a48ed, #70305e66); border-bottom: 1px solid #792e6473;\n}\n.console-tabs a {\n    margin-right: 5px; padding: 5px 15px; text-decoration: none; color: #AAA;\n    font-size: 11px; font-family: \"Consolas\"; text-transform: uppercase; border: 1px solid #666;\n    border-bottom: 0px; display: inline-block; margin: 5px; margin-bottom: 0px; background-color: rgba(0,0,0,0.5);\n}\n.console-tabs a:hover { color: #FFF }\n.console-tabs a.active { background-color: #46223c; color: #FFF }\n.console-middle {height: 0px; top: 50%; position: absolute; width: 100%; left: 50%; display: none; }\n\n.console .mynode {\n\tborder: 0.5px solid #aaa; width: 50px; height: 50px; transform: rotateZ(45deg); margin-top: -25px; margin-left: -25px;\n\topacity: 1; display: inline-block; background-color: #EEE; z-index: 9; position: absolute; outline: 5px solid #EEE;\n}\n.console .peers { width: 0px; height: 0px; position: absolute; left: -20px; top: -20px; text-align: center; }\n.console .peer { left: 0px; top: 0px; position: absolute; }\n.console .peer .icon { width: 20px; height: 20px; padding: 10px; display: inline-block; text-decoration: none; left: 200px; position: absolute; color: #666; }\n.console .peer .icon:before { content: \"\\25BC\"; position: absolute; margin-top: 3px; margin-left: -1px; opacity: 0; transition: all 0.3s }\n.console .peer .icon:hover:before { opacity: 1; transition: none }\n.console .peer .line {\n\twidth: 187px; border-top: 1px solid #CCC; position: absolute; top: 20px; left: 20px;\n\ttransform: rotateZ(334deg); transform-origin: bottom left;\n}\n"
  },
  {
    "path": "plugins/Sidebar/media/Menu.coffee",
    "content": "class Menu\n\tconstructor: (@button) ->\n\t\t@elem = $(\".menu.template\").clone().removeClass(\"template\")\n\t\t@elem.appendTo(\"body\")\n\t\t@items = []\n\n\tshow: ->\n\t\tif window.visible_menu and window.visible_menu.button[0] == @button[0] # Same menu visible then hide it\n\t\t\twindow.visible_menu.hide()\n\t\t\t@hide()\n\t\telse\n\t\t\tbutton_pos = @button.offset()\n\t\t\tleft = button_pos.left\n\t\t\t@elem.css({\"top\": button_pos.top+@button.outerHeight(), \"left\": left})\n\t\t\t@button.addClass(\"menu-active\")\n\t\t\t@elem.addClass(\"visible\")\n\t\t\tif @elem.position().left + @elem.width() + 20 > window.innerWidth\n\t\t\t\t@elem.css(\"left\", window.innerWidth - @elem.width() - 20)\n\t\t\tif window.visible_menu then window.visible_menu.hide()\n\t\t\twindow.visible_menu = @\n\n\n\thide: ->\n\t\t@elem.removeClass(\"visible\")\n\t\t@button.removeClass(\"menu-active\")\n\t\twindow.visible_menu = null\n\n\n\taddItem: (title, cb) ->\n\t\titem = $(\".menu-item.template\", @elem).clone().removeClass(\"template\")\n\t\titem.html(title)\n\t\titem.on \"click\", =>\n\t\t\tif not cb(item)\n\t\t\t\t@hide()\n\t\t\treturn false\n\t\titem.appendTo(@elem)\n\t\t@items.push item\n\t\treturn item\n\n\n\tlog: (args...) ->\n\t\tconsole.log \"[Menu]\", args...\n\nwindow.Menu = Menu\n\n# Hide menu on outside click\n$(\"body\").on \"click\", (e) ->\n\tif window.visible_menu and e.target != window.visible_menu.button[0] and $(e.target).parent()[0] != window.visible_menu.elem[0]\n\t\twindow.visible_menu.hide()\n"
  },
  {
    "path": "plugins/Sidebar/media/Menu.css",
    "content": ".menu {\n\tbackground-color: white; padding: 10px 0px; position: absolute; top: 0px; left: 0px; max-height: 0px; overflow: hidden; transform: translate(0px, -30px); pointer-events: none;\n\tbox-shadow: 0px 2px 8px rgba(0,0,0,0.3); border-radius: 2px; opacity: 0; transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out;\n}\n.menu.visible { opacity: 1; max-height: 350px; transform: translate(0px, 0px); transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; pointer-events: all }\n\n.menu-item { display: block; text-decoration: none; color: black; padding: 6px 24px; transition: all 0.2s; border-bottom: none; font-weight: normal; padding-left: 30px; }\n.menu-item-separator { margin-top: 5px; border-top: 1px solid #eee }\n\n.menu-item:hover { background-color: #F6F6F6; transition: none; color: inherit; border: none }\n.menu-item:active, .menu-item:focus { background-color: #AF3BFF; color: white; transition: none }\n.menu-item.selected:before {\n\tcontent: \"L\"; display: inline-block; transform: rotateZ(45deg) scaleX(-1);\n\tfont-weight: bold; position: absolute; margin-left: -17px; font-size: 12px; margin-top: 2px;\n}\n\n@media only screen and (max-width: 800px) {\n.menu, .menu.visible { position: absolute; left: unset !important; right: 20px; }\n}"
  },
  {
    "path": "plugins/Sidebar/media/Prototypes.coffee",
    "content": "String::startsWith = (s) -> @[...s.length] is s\nString::endsWith = (s) -> s is '' or @[-s.length..] is s\nString::capitalize = ->  if @.length then @[0].toUpperCase() + @.slice(1) else \"\"\nString::repeat = (count) -> new Array( count + 1 ).join(@)\n\nwindow.isEmpty = (obj) ->\n\tfor key of obj\n\t\treturn false\n\treturn true\n"
  },
  {
    "path": "plugins/Sidebar/media/RateLimit.coffee",
    "content": "limits = {}\ncall_after_interval = {}\nwindow.RateLimit = (interval, fn) ->\n\tif not limits[fn]\n\t\tcall_after_interval[fn] = false\n\t\tfn() # First call is not delayed\n\t\tlimits[fn] = setTimeout (->\n\t\t\tif call_after_interval[fn]\n\t\t\t\tfn()\n\t\t\tdelete limits[fn]\n\t\t\tdelete call_after_interval[fn]\n\t\t), interval\n\telse # Called within iterval, delay the call\n\t\tcall_after_interval[fn] = true\n"
  },
  {
    "path": "plugins/Sidebar/media/Scrollable.js",
    "content": "/* via http://jsfiddle.net/elGrecode/00dgurnn/ */\n\nwindow.initScrollable = function () {\n\n    var scrollContainer = document.querySelector('.scrollable'),\n        scrollContentWrapper = document.querySelector('.scrollable .content-wrapper'),\n        scrollContent = document.querySelector('.scrollable .content'),\n        contentPosition = 0,\n        scrollerBeingDragged = false,\n        scroller,\n        topPosition,\n        scrollerHeight;\n\n    function calculateScrollerHeight() {\n        // *Calculation of how tall scroller should be\n        var visibleRatio = scrollContainer.offsetHeight / scrollContentWrapper.scrollHeight;\n        if (visibleRatio == 1)\n            scroller.style.display = \"none\";\n        else\n            scroller.style.display = \"block\";\n        return visibleRatio * scrollContainer.offsetHeight;\n    }\n\n    function moveScroller(evt) {\n        // Move Scroll bar to top offset\n        var scrollPercentage = evt.target.scrollTop / scrollContentWrapper.scrollHeight;\n        topPosition = scrollPercentage * (scrollContainer.offsetHeight - 5); // 5px arbitrary offset so scroll bar doesn't move too far beyond content wrapper bounding box\n        scroller.style.top = topPosition + 'px';\n    }\n\n    function startDrag(evt) {\n        normalizedPosition = evt.pageY;\n        contentPosition = scrollContentWrapper.scrollTop;\n        scrollerBeingDragged = true;\n        window.addEventListener('mousemove', scrollBarScroll);\n        return false;\n    }\n\n    function stopDrag(evt) {\n        scrollerBeingDragged = false;\n        window.removeEventListener('mousemove', scrollBarScroll);\n    }\n\n    function scrollBarScroll(evt) {\n        if (scrollerBeingDragged === true) {\n            evt.preventDefault();\n            var mouseDifferential = evt.pageY - normalizedPosition;\n            var scrollEquivalent = mouseDifferential * (scrollContentWrapper.scrollHeight / scrollContainer.offsetHeight);\n            scrollContentWrapper.scrollTop = contentPosition + scrollEquivalent;\n        }\n    }\n\n    function updateHeight() {\n        scrollerHeight = calculateScrollerHeight() - 10;\n        scroller.style.height = scrollerHeight + 'px';\n    }\n\n    function createScroller() {\n        // *Creates scroller element and appends to '.scrollable' div\n        // create scroller element\n        scroller = document.createElement(\"div\");\n        scroller.className = 'scroller';\n\n        // determine how big scroller should be based on content\n        scrollerHeight = calculateScrollerHeight() - 10;\n\n        if (scrollerHeight / scrollContainer.offsetHeight < 1) {\n            // *If there is a need to have scroll bar based on content size\n            scroller.style.height = scrollerHeight + 'px';\n\n            // append scroller to scrollContainer div\n            scrollContainer.appendChild(scroller);\n\n            // show scroll path divot\n            scrollContainer.className += ' showScroll';\n\n            // attach related draggable listeners\n            scroller.addEventListener('mousedown', startDrag);\n            window.addEventListener('mouseup', stopDrag);\n        }\n\n    }\n\n    createScroller();\n\n\n    // *** Listeners ***\n    scrollContentWrapper.addEventListener('scroll', moveScroller);\n\n    return updateHeight;\n};"
  },
  {
    "path": "plugins/Sidebar/media/Scrollbable.css",
    "content": ".scrollable {\n    overflow: hidden;\n}\n\n.scrollable.showScroll::after {\n    position: absolute;\n    content: '';\n    top: 5%;\n    right: 7px;\n    height: 90%;\n    width: 3px;\n    background: rgba(224, 224, 255, .3);\n}\n\n.scrollable .content-wrapper {\n    width: 100%;\n    height: 100%;\n    padding-right: 50%;\n    overflow-y: scroll;\n}\n.scroller {\n    margin-top: 5px;\n    z-index: 5;\n    cursor: pointer;\n    position: absolute;\n    width: 7px;\n    border-radius: 5px;\n    background: #3A3A3A;\n    top: 0px;\n    left: 395px;\n    -webkit-transition: top .08s;\n    -moz-transition: top .08s;\n    -ms-transition: top .08s;\n    -o-transition: top .08s;\n    transition: top .08s;\n}\n.scroller {\n    -webkit-touch-callout: none;\n    -webkit-user-select: none;\n    -khtml-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n    user-select: none;\n}\n"
  },
  {
    "path": "plugins/Sidebar/media/Sidebar.coffee",
    "content": "class Sidebar extends Class\n\tconstructor: (@wrapper) ->\n\t\t@tag = null\n\t\t@container = null\n\t\t@opened = false\n\t\t@width = 410\n\t\t@console = new Console(@)\n\t\t@fixbutton = $(\".fixbutton\")\n\t\t@fixbutton_addx = 0\n\t\t@fixbutton_addy = 0\n\t\t@fixbutton_initx = 0\n\t\t@fixbutton_inity = 15\n\t\t@fixbutton_targetx = 0\n\t\t@move_lock = null\n\t\t@page_width = $(window).width()\n\t\t@page_height = $(window).height()\n\t\t@frame = $(\"#inner-iframe\")\n\t\t@initFixbutton()\n\t\t@dragStarted = 0\n\t\t@globe = null\n\t\t@preload_html = null\n\n\t\t@original_set_site_info = @wrapper.setSiteInfo  # We going to override this, save the original\n\n\t\t# Start in opened state for debugging\n\t\tif window.top.location.hash == \"#ZeroNet:OpenSidebar\"\n\t\t\t@startDrag()\n\t\t\t@moved(\"x\")\n\t\t\t@fixbutton_targetx = @fixbutton_initx - @width\n\t\t\t@stopDrag()\n\n\n\tinitFixbutton: ->\n\n\t\t# Detect dragging\n\t\t@fixbutton.on \"mousedown touchstart\", (e) =>\n\t\t\tif e.button > 0  # Right or middle click\n\t\t\t\treturn\n\t\t\te.preventDefault()\n\n\t\t\t# Disable previous listeners\n\t\t\t@fixbutton.off \"click touchend touchcancel\"\n\n\t\t\t# Make sure its not a click\n\t\t\t@dragStarted = (+ new Date)\n\n\t\t\t# Fullscreen drag bg to capture mouse events over iframe\n\t\t\t$(\".drag-bg\").remove()\n\t\t\t$(\"<div class='drag-bg'></div>\").appendTo(document.body)\n\n\t\t\t$(\"body\").one \"mousemove touchmove\", (e) =>\n\t\t\t\tmousex = e.pageX\n\t\t\t\tmousey = e.pageY\n\t\t\t\tif not mousex\n\t\t\t\t\tmousex = e.originalEvent.touches[0].pageX\n\t\t\t\t\tmousey = e.originalEvent.touches[0].pageY\n\n\t\t\t\t@fixbutton_addx = @fixbutton.offset().left - mousex\n\t\t\t\t@fixbutton_addy = @fixbutton.offset().top - mousey\n\t\t\t\t@startDrag()\n\t\t@fixbutton.parent().on \"click touchend touchcancel\", (e) =>\n\t\t\tif (+ new Date) - @dragStarted < 100\n\t\t\t\twindow.top.location = @fixbutton.find(\".fixbutton-bg\").attr(\"href\")\n\t\t\t@stopDrag()\n\t\t@resized()\n\t\t$(window).on \"resize\", @resized\n\n\tresized: =>\n\t\t@page_width = $(window).width()\n\t\t@page_height = $(window).height()\n\t\t@fixbutton_initx = @page_width - 75  # Initial x position\n\t\tif @opened\n\t\t\t@fixbutton.css\n\t\t\t\tleft: @fixbutton_initx - @width\n\t\telse\n\t\t\t@fixbutton.css\n\t\t\t\tleft: @fixbutton_initx\n\n\t# Start dragging the fixbutton\n\tstartDrag: ->\n\t\t#@move_lock = \"x\"  # Temporary until internals not finished\n\t\t@log \"startDrag\", @fixbutton_initx, @fixbutton_inity\n\t\t@fixbutton_targetx = @fixbutton_initx  # Fallback x position\n\t\t@fixbutton_targety = @fixbutton_inity  # Fallback y position\n\n\t\t@fixbutton.addClass(\"dragging\")\n\n\t\t# IE position wrap fix\n\t\tif navigator.userAgent.indexOf('MSIE') != -1 or navigator.appVersion.indexOf('Trident/') > 0\n\t\t\t@fixbutton.css(\"pointer-events\", \"none\")\n\n\t\t# Don't go to homepage\n\t\t@fixbutton.one \"click\", (e) =>\n\t\t\t@stopDrag()\n\t\t\t@fixbutton.removeClass(\"dragging\")\n\t\t\tmoved_x = Math.abs(@fixbutton.offset().left - @fixbutton_initx)\n\t\t\tmoved_y = Math.abs(@fixbutton.offset().top - @fixbutton_inity)\n\t\t\tif moved_x > 5 or moved_y > 10\n\t\t\t\t# If moved more than some pixel the button then don't go to homepage\n\t\t\t\te.preventDefault()\n\n\t\t# Animate drag\n\t\t@fixbutton.parents().on \"mousemove touchmove\", @animDrag\n\t\t@fixbutton.parents().on \"mousemove touchmove\" ,@waitMove\n\n\t\t# Stop dragging listener\n\t\t@fixbutton.parents().one \"mouseup touchend touchcancel\", (e) =>\n\t\t\te.preventDefault()\n\t\t\t@stopDrag()\n\n\n\t# Wait for moving the fixbutton\n\twaitMove: (e) =>\n\t\tdocument.body.style.perspective = \"1000px\"\n\t\tdocument.body.style.height = \"100%\"\n\t\tdocument.body.style.willChange = \"perspective\"\n\t\tdocument.documentElement.style.height = \"100%\"\n\t\t#$(document.body).css(\"backface-visibility\", \"hidden\").css(\"perspective\", \"1000px\").css(\"height\", \"900px\")\n\t\t# $(\"iframe\").css(\"backface-visibility\", \"hidden\")\n\n\t\tmoved_x = Math.abs(parseInt(@fixbutton[0].style.left) - @fixbutton_targetx)\n\t\tmoved_y = Math.abs(parseInt(@fixbutton[0].style.top) - @fixbutton_targety)\n\t\tif moved_x > 5 and (+ new Date) - @dragStarted + moved_x > 50\n\t\t\t@moved(\"x\")\n\t\t\t@fixbutton.stop().animate {\"top\": @fixbutton_inity}, 1000\n\t\t\t@fixbutton.parents().off \"mousemove touchmove\" ,@waitMove\n\n\t\telse if moved_y > 5 and (+ new Date) - @dragStarted + moved_y > 50\n\t\t\t@moved(\"y\")\n\t\t\t@fixbutton.parents().off \"mousemove touchmove\" ,@waitMove\n\n\tmoved: (direction) ->\n\t\t@log \"Moved\", direction\n\t\t@move_lock = direction\n\t\tif direction == \"y\"\n\t\t\t$(document.body).addClass(\"body-console\")\n\t\t\treturn @console.createHtmltag()\n\t\t@createHtmltag()\n\t\t$(document.body).addClass(\"body-sidebar\")\n\t\t@container.on \"mousedown touchend touchcancel\", (e) =>\n\t\t\tif e.target != e.currentTarget\n\t\t\t\treturn true\n\t\t\t@log \"closing\"\n\t\t\tif $(document.body).hasClass(\"body-sidebar\")\n\t\t\t\t@close()\n\t\t\t\treturn true\n\n\t\t$(window).off \"resize\"\n\t\t$(window).on \"resize\", =>\n\t\t\t$(document.body).css \"height\", $(window).height()\n\t\t\t@scrollable()\n\t\t\t@resized()\n\n\t\t# Override setsiteinfo to catch changes\n\t\t@wrapper.setSiteInfo = (site_info) =>\n\t\t\t@setSiteInfo(site_info)\n\t\t\t@original_set_site_info.apply(@wrapper, arguments)\n\n\t\t# Preload world.jpg\n\t\timg = new Image();\n\t\timg.src = \"/uimedia/globe/world.jpg\";\n\n\tsetSiteInfo: (site_info) ->\n\t\tRateLimit 1500, =>\n\t\t\t@updateHtmlTag()\n\t\tRateLimit 30000, =>\n\t\t\t@displayGlobe()\n\n\t# Create the sidebar html tag\n\tcreateHtmltag: ->\n\t\t@when_loaded = $.Deferred()\n\t\tif not @container\n\t\t\t@container = $(\"\"\"\n\t\t\t<div class=\"sidebar-container\"><div class=\"sidebar scrollable\"><div class=\"content-wrapper\"><div class=\"content\">\n\t\t\t</div></div></div></div>\n\t\t\t\"\"\")\n\t\t\t@container.appendTo(document.body)\n\t\t\t@tag = @container.find(\".sidebar\")\n\t\t\t@updateHtmlTag()\n\t\t\t@scrollable = window.initScrollable()\n\n\n\tupdateHtmlTag: ->\n\t\tif @preload_html\n\t\t\t@setHtmlTag(@preload_html)\n\t\t\t@preload_html = null\n\t\telse\n\t\t\t@wrapper.ws.cmd \"sidebarGetHtmlTag\", {}, @setHtmlTag\n\n\tsetHtmlTag: (res) =>\n\t\tif @tag.find(\".content\").children().length == 0 # First update\n\t\t\t@log \"Creating content\"\n\t\t\t@container.addClass(\"loaded\")\n\t\t\tmorphdom(@tag.find(\".content\")[0], '<div class=\"content\">'+res+'</div>')\n\t\t\t# @scrollable()\n\t\t\t@when_loaded.resolve()\n\n\t\telse  # Not first update, patch the html to keep unchanged dom elements\n\t\t\tmorphdom @tag.find(\".content\")[0], '<div class=\"content\">'+res+'</div>', {\n\t\t\t\tonBeforeMorphEl: (from_el, to_el) ->  # Ignore globe loaded state\n\t\t\t\t\tif from_el.className == \"globe\" or from_el.className.indexOf(\"noupdate\") >= 0\n\t\t\t\t\t\treturn false\n\t\t\t\t\telse\n\t\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t# Save and forget privatekey for site signing\n\t\t@tag.find(\"#privatekey-add\").off(\"click, touchend\").on \"click touchend\", (e) =>\n\t\t\t@wrapper.displayPrompt \"Enter your private key:\", \"password\", \"Save\", \"\", (privatekey) =>\n\t\t\t\t@wrapper.ws.cmd \"userSetSitePrivatekey\", [privatekey], (res) =>\n\t\t\t\t\t@wrapper.notifications.add \"privatekey\", \"done\", \"Private key saved for site signing\", 5000\n\t\t\treturn false\n\n\t\t@tag.find(\"#privatekey-forget\").off(\"click, touchend\").on \"click touchend\", (e) =>\n\t\t\t@wrapper.displayConfirm \"Remove saved private key for this site?\", \"Forget\", (res) =>\n\t\t\t\tif not res\n\t\t\t\t\treturn false\n\t\t\t\t@wrapper.ws.cmd \"userSetSitePrivatekey\", [\"\"], (res) =>\n\t\t\t\t\t@wrapper.notifications.add \"privatekey\", \"done\", \"Saved private key removed\", 5000\n\t\t\treturn false\n\n\t\t# Use requested address for browse files urls\n\t\t@tag.find(\"#browse-files\").attr(\"href\", document.location.pathname.replace(/(\\/.*?(\\/|$)).*$/, \"/list$1\"))\n\n\n\n\tanimDrag: (e) =>\n\t\tmousex = e.pageX\n\t\tmousey = e.pageY\n\t\tif not mousex and e.originalEvent.touches\n\t\t\tmousex = e.originalEvent.touches[0].pageX\n\t\t\tmousey = e.originalEvent.touches[0].pageY\n\n\t\toverdrag = @fixbutton_initx - @width - mousex\n\t\tif overdrag > 0  # Overdragged\n\t\t\toverdrag_percent = 1 + overdrag/300\n\t\t\tmousex = (mousex + (@fixbutton_initx-@width)*overdrag_percent)/(1+overdrag_percent)\n\t\ttargetx = @fixbutton_initx - mousex - @fixbutton_addx\n\t\ttargety = @fixbutton_inity - mousey - @fixbutton_addy\n\n\t\tif @move_lock == \"x\"\n\t\t\ttargety = @fixbutton_inity\n\t\telse if @move_lock == \"y\"\n\t\t\ttargetx = @fixbutton_initx\n\n\t\tif not @move_lock or @move_lock == \"x\"\n\t\t\t@fixbutton[0].style.left = (mousex + @fixbutton_addx) + \"px\"\n\t\t\tif @tag\n\t\t\t\t@tag[0].style.transform = \"translateX(#{0 - targetx}px)\"\n\n\t\tif not @move_lock or @move_lock == \"y\"\n\t\t\t@fixbutton[0].style.top = (mousey + @fixbutton_addy) + \"px\"\n\t\t\tif @console.tag\n\t\t\t\t@console.tag[0].style.transform = \"translateY(#{0 - targety}px)\"\n\n\t\t#if @move_lock == \"x\"\n\t\t\t# @fixbutton[0].style.left = \"#{@fixbutton_targetx} px\"\n\t\t\t#@fixbutton[0].style.top = \"#{@fixbutton_inity}px\"\n\t\t#if @move_lock == \"y\"\n\t\t#\t@fixbutton[0].style.top = \"#{@fixbutton_targety} px\"\n\n\t\t# Check if opened\n\t\tif (not @opened and targetx > @width/3) or (@opened and targetx > @width*0.9)\n\t\t\t@fixbutton_targetx = @fixbutton_initx - @width  # Make it opened\n\t\telse\n\t\t\t@fixbutton_targetx = @fixbutton_initx\n\n\t\tif (not @console.opened and 0 - targety > @page_height/10) or (@console.opened and 0 - targety > @page_height*0.8)\n\t\t\t@fixbutton_targety = @page_height - @fixbutton_inity - 50\n\t\telse\n\t\t\t@fixbutton_targety = @fixbutton_inity\n\n\n\t# Stop dragging the fixbutton\n\tstopDrag: ->\n\t\t@fixbutton.parents().off \"mousemove touchmove\"\n\t\t@fixbutton.off \"mousemove touchmove\"\n\t\t@fixbutton.css(\"pointer-events\", \"\")\n\t\t$(\".drag-bg\").remove()\n\t\tif not @fixbutton.hasClass(\"dragging\")\n\t\t\treturn\n\t\t@fixbutton.removeClass(\"dragging\")\n\n\t\t# Move back to initial position\n\t\tif @fixbutton_targetx != @fixbutton.offset().left or @fixbutton_targety != @fixbutton.offset().top\n\t\t\t# Animate fixbutton\n\t\t\tif @move_lock == \"y\"\n\t\t\t\ttop = @fixbutton_targety\n\t\t\t\tleft = @fixbutton_initx\n\t\t\tif @move_lock == \"x\"\n\t\t\t\ttop = @fixbutton_inity\n\t\t\t\tleft = @fixbutton_targetx\n\t\t\t@fixbutton.stop().animate {\"left\": left, \"top\": top}, 500, \"easeOutBack\", =>\n\t\t\t\t# Switch back to auto align\n\t\t\t\tif @fixbutton_targetx == @fixbutton_initx  # Closed\n\t\t\t\t\t@fixbutton.css(\"left\", \"auto\")\n\t\t\t\telse  # Opened\n\t\t\t\t\t@fixbutton.css(\"left\", left)\n\n\t\t\t\t$(\".fixbutton-bg\").trigger \"mouseout\"  # Switch fixbutton back to normal status\n\n\t\t\t@stopDragX()\n\t\t\t@console.stopDragY()\n\t\t@move_lock = null\n\n\tstopDragX: ->\n\t\t# Animate sidebar and iframe\n\t\tif @fixbutton_targetx == @fixbutton_initx or @move_lock == \"y\"\n\t\t\t# Closed\n\t\t\ttargetx = 0\n\t\t\t@opened = false\n\t\telse\n\t\t\t# Opened\n\t\t\ttargetx = @width\n\t\t\tif @opened\n\t\t\t\t@onOpened()\n\t\t\telse\n\t\t\t\t@when_loaded.done =>\n\t\t\t\t\t@onOpened()\n\t\t\t@opened = true\n\n\t\t# Revent sidebar transitions\n\t\tif @tag\n\t\t\t@tag.css(\"transition\", \"0.4s ease-out\")\n\t\t\t@tag.css(\"transform\", \"translateX(-#{targetx}px)\").one transitionEnd, =>\n\t\t\t\t@tag.css(\"transition\", \"\")\n\t\t\t\tif not @opened\n\t\t\t\t\t@container.remove()\n\t\t\t\t\t@container = null\n\t\t\t\t\tif @tag\n\t\t\t\t\t\t@tag.remove()\n\t\t\t\t\t\t@tag = null\n\n\t\t# Revert body transformations\n\t\t@log \"stopdrag\", \"opened:\", @opened\n\t\tif not @opened\n\t\t\t@onClosed()\n\n\tsign: (inner_path, privatekey) ->\n\t\t@wrapper.displayProgress(\"sign\", \"Signing: #{inner_path}...\", 0)\n\t\t@wrapper.ws.cmd \"siteSign\", {privatekey: privatekey, inner_path: inner_path, update_changed_files: true}, (res) =>\n\t\t\tif res == \"ok\"\n\t\t\t\t@wrapper.displayProgress(\"sign\", \"#{inner_path} signed!\", 100)\n\t\t\telse\n\t\t\t\t@wrapper.displayProgress(\"sign\", \"Error signing #{inner_path}\", -1)\n\n\tpublish: (inner_path, privatekey) ->\n\t\t@wrapper.ws.cmd \"sitePublish\", {privatekey: privatekey, inner_path: inner_path, sign: true, update_changed_files: true}, (res) =>\n\t\t\tif res == \"ok\"\n\t\t\t\t@wrapper.notifications.add \"sign\", \"done\", \"#{inner_path} Signed and published!\", 5000\n\n\thandleSiteDeleteClick: ->\n\t\tif @wrapper.site_info.privatekey\n\t\t\tquestion = \"Are you sure?<br>This site has a saved private key\"\n\t\t\toptions = [\"Forget private key and delete site\"]\n\t\telse\n\t\t\tquestion = \"Are you sure?\"\n\t\t\toptions = [\"Delete this site\", \"Blacklist\"]\n\t\t@wrapper.displayConfirm question, options, (confirmed) =>\n\t\t\tif confirmed == 1\n\t\t\t\t@tag.find(\"#button-delete\").addClass(\"loading\")\n\t\t\t\t@wrapper.ws.cmd \"siteDelete\", @wrapper.site_info.address, ->\n\t\t\t\t\tdocument.location = $(\".fixbutton-bg\").attr(\"href\")\n\t\t\telse if confirmed == 2\n\t\t\t\t@wrapper.displayPrompt \"Blacklist this site\", \"text\", \"Delete and Blacklist\", \"Reason\", (reason) =>\n\t\t\t\t\t@tag.find(\"#button-delete\").addClass(\"loading\")\n\t\t\t\t\t@wrapper.ws.cmd \"siteblockAdd\", [@wrapper.site_info.address, reason]\n\t\t\t\t\t@wrapper.ws.cmd \"siteDelete\", @wrapper.site_info.address, ->\n\t\t\t\t\t\tdocument.location = $(\".fixbutton-bg\").attr(\"href\")\n\n\tonOpened: ->\n\t\t@log \"Opened\"\n\t\t@scrollable()\n\n\t\t# Re-calculate height when site admin opened or closed\n\t\t@tag.find(\"#checkbox-owned, #checkbox-autodownloadoptional\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\tsetTimeout (=>\n\t\t\t\t@scrollable()\n\t\t\t), 300\n\n\t\t# Site limit button\n\t\t@tag.find(\"#button-sitelimit\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"siteSetLimit\", $(\"#input-sitelimit\").val(), (res) =>\n\t\t\t\tif res == \"ok\"\n\t\t\t\t\t@wrapper.notifications.add \"done-sitelimit\", \"done\", \"Site storage limit modified!\", 5000\n\t\t\t\t@updateHtmlTag()\n\t\t\treturn false\n\n\t\t# Site autodownload limit button\n\t\t@tag.find(\"#button-autodownload_bigfile_size_limit\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"siteSetAutodownloadBigfileLimit\", $(\"#input-autodownload_bigfile_size_limit\").val(), (res) =>\n\t\t\t\tif res == \"ok\"\n\t\t\t\t\t@wrapper.notifications.add \"done-bigfilelimit\", \"done\", \"Site bigfile auto download limit modified!\", 5000\n\t\t\t\t@updateHtmlTag()\n\t\t\treturn false\n\n\t\t# Site start download optional files\n\t\t@tag.find(\"#button-autodownload_previous\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"siteUpdate\", {\"address\": @wrapper.site_info.address, \"check_files\": true}, =>\n\t\t\t\t@wrapper.notifications.add \"done-download_optional\", \"done\", \"Optional files downloaded\", 5000\n\n\t\t\t@wrapper.notifications.add \"start-download_optional\", \"info\", \"Optional files download started\", 5000\n\t\t\treturn false\n\n\t\t# Database reload\n\t\t@tag.find(\"#button-dbreload\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"dbReload\", [], =>\n\t\t\t\t@wrapper.notifications.add \"done-dbreload\", \"done\", \"Database schema reloaded!\", 5000\n\t\t\t\t@updateHtmlTag()\n\t\t\treturn false\n\n\t\t# Database rebuild\n\t\t@tag.find(\"#button-dbrebuild\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.notifications.add \"done-dbrebuild\", \"info\", \"Database rebuilding....\"\n\t\t\t@wrapper.ws.cmd \"dbRebuild\", [], =>\n\t\t\t\t@wrapper.notifications.add \"done-dbrebuild\", \"done\", \"Database rebuilt!\", 5000\n\t\t\t\t@updateHtmlTag()\n\t\t\treturn false\n\n\t\t# Update site\n\t\t@tag.find(\"#button-update\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@tag.find(\"#button-update\").addClass(\"loading\")\n\t\t\t@wrapper.ws.cmd \"siteUpdate\", @wrapper.site_info.address, =>\n\t\t\t\t@wrapper.notifications.add \"done-updated\", \"done\", \"Site updated!\", 5000\n\t\t\t\t@tag.find(\"#button-update\").removeClass(\"loading\")\n\t\t\treturn false\n\n\t\t# Pause site\n\t\t@tag.find(\"#button-pause\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@tag.find(\"#button-pause\").addClass(\"hidden\")\n\t\t\t@wrapper.ws.cmd \"sitePause\", @wrapper.site_info.address\n\t\t\treturn false\n\n\t\t# Resume site\n\t\t@tag.find(\"#button-resume\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@tag.find(\"#button-resume\").addClass(\"hidden\")\n\t\t\t@wrapper.ws.cmd \"siteResume\", @wrapper.site_info.address\n\t\t\treturn false\n\n\t\t# Delete site\n\t\t@tag.find(\"#button-delete\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@handleSiteDeleteClick()\n\t\t\treturn false\n\n\t\t# Owned checkbox\n\t\t@tag.find(\"#checkbox-owned\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\towned = @tag.find(\"#checkbox-owned\").is(\":checked\")\n\t\t\t@wrapper.ws.cmd \"siteSetOwned\", [owned], (res_set_owned) =>\n\t\t\t\t@log \"Owned\", owned\n\t\t\t\tif owned\n\t\t\t\t\t@wrapper.ws.cmd \"siteRecoverPrivatekey\", [], (res_recover) =>\n\t\t\t\t\t\tif res_recover == \"ok\"\n\t\t\t\t\t\t\t@wrapper.notifications.add(\"recover\", \"done\", \"Private key recovered from master seed\", 5000)\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\t@log \"Unable to recover private key: #{res_recover.error}\"\n\n\n\t\t# Owned auto download checkbox\n\t\t@tag.find(\"#checkbox-autodownloadoptional\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"siteSetAutodownloadoptional\", [@tag.find(\"#checkbox-autodownloadoptional\").is(\":checked\")]\n\n\t\t# Change identity button\n\t\t@tag.find(\"#button-identity\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"certSelect\"\n\t\t\treturn false\n\n\t\t# Save settings\n\t\t@tag.find(\"#button-settings\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"fileGet\", \"content.json\", (res) =>\n\t\t\t\tdata = JSON.parse(res)\n\t\t\t\tdata[\"title\"] = $(\"#settings-title\").val()\n\t\t\t\tdata[\"description\"] = $(\"#settings-description\").val()\n\t\t\t\tjson_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\\t')))\n\t\t\t\t@wrapper.ws.cmd \"fileWrite\", [\"content.json\", btoa(json_raw), true], (res) =>\n\t\t\t\t\tif res != \"ok\" # fileWrite failed\n\t\t\t\t\t\t@wrapper.notifications.add \"file-write\", \"error\", \"File write error: #{res}\"\n\t\t\t\t\telse\n\t\t\t\t\t\t@wrapper.notifications.add \"file-write\", \"done\", \"Site settings saved!\", 5000\n\t\t\t\t\t\tif @wrapper.site_info.privatekey\n\t\t\t\t\t\t\t@wrapper.ws.cmd \"siteSign\", {privatekey: \"stored\", inner_path: \"content.json\", update_changed_files: true}\n\t\t\t\t\t\t@updateHtmlTag()\n\t\t\treturn false\n\n\n\t\t# Open site directory\n\t\t@tag.find(\"#link-directory\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\t@wrapper.ws.cmd \"serverShowdirectory\", [\"site\", @wrapper.site_info.address]\n\t\t\treturn false\n\n\t\t# Copy site with peers\n\t\t@tag.find(\"#link-copypeers\").off(\"click touchend\").on \"click touchend\", (e) =>\n\t\t\tcopy_text = e.currentTarget.href\n\t\t\thandler = (e) =>\n\t\t\t\te.clipboardData.setData('text/plain', copy_text)\n\t\t\t\te.preventDefault()\n\t\t\t\t@wrapper.notifications.add \"copy\", \"done\", \"Site address with peers copied to your clipboard\", 5000\n\t\t\t\tdocument.removeEventListener('copy', handler, true)\n\n\t\t\tdocument.addEventListener('copy', handler, true)\n\t\t\tdocument.execCommand('copy')\n\t\t\treturn false\n\n\t\t# Sign and publish content.json\n\t\t$(document).on \"click touchend\", =>\n\t\t\t@tag?.find(\"#button-sign-publish-menu\").removeClass(\"visible\")\n\t\t\t@tag?.find(\".contents + .flex\").removeClass(\"sign-publish-flex\")\n\n\t\t@tag.find(\".contents-content\").off(\"click touchend\").on \"click touchend\", (e) =>\n\t\t\t$(\"#input-contents\").val(e.currentTarget.innerText);\n\t\t\treturn false;\n\n\t\tmenu = new Menu(@tag.find(\"#menu-sign-publish\"))\n\t\tmenu.elem.css(\"margin-top\", \"-130px\")  # Open upwards\n\t\tmenu.addItem \"Sign\", =>\n\t\t\tinner_path = @tag.find(\"#input-contents\").val()\n\n\t\t\t@wrapper.ws.cmd \"fileRules\", {inner_path: inner_path}, (rules) =>\n\t\t\t\tif @wrapper.site_info.auth_address in rules.signers\n\t\t\t\t\t# ZeroID or other ID provider\n\t\t\t\t\t@sign(inner_path)\n\t\t\t\telse if @wrapper.site_info.privatekey\n\t\t\t\t\t# Privatekey stored in users.json\n\t\t\t\t\t@sign(inner_path, \"stored\")\n\t\t\t\telse\n\t\t\t\t\t# Ask the user for privatekey\n\t\t\t\t\t@wrapper.displayPrompt \"Enter your private key:\", \"password\", \"Sign\", \"\", (privatekey) => # Prompt the private key\n\t\t\t\t\t\t@sign(inner_path, privatekey)\n\n\t\t\t@tag.find(\".contents + .flex\").removeClass \"active\"\n\t\t\tmenu.hide()\n\n\t\tmenu.addItem \"Publish\", =>\n\t\t\tinner_path = @tag.find(\"#input-contents\").val()\n\t\t\t@wrapper.ws.cmd \"sitePublish\", {\"inner_path\": inner_path, \"sign\": false}\n\n\t\t\t@tag.find(\".contents + .flex\").removeClass \"active\"\n\t\t\tmenu.hide()\n\n\t\t@tag.find(\"#menu-sign-publish\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\tif window.visible_menu == menu\n\t\t\t\t@tag.find(\".contents + .flex\").removeClass \"active\"\n\t\t\t\tmenu.hide()\n\t\t\telse\n\t\t\t\t@tag.find(\".contents + .flex\").addClass \"active\"\n\t\t\t\t@tag.find(\".content-wrapper\").prop \"scrollTop\", 10000\n\t\t\t\tmenu.show()\n\t\t\treturn false\n\n\t\t$(\"body\").on \"click\", =>\n\t\t\tif @tag\n\t\t\t\t@tag.find(\".contents + .flex\").removeClass \"active\"\n\n\t\t@tag.find(\"#button-sign-publish\").off(\"click touchend\").on \"click touchend\", =>\n\t\t\tinner_path = @tag.find(\"#input-contents\").val()\n\n\t\t\t@wrapper.ws.cmd \"fileRules\", {inner_path: inner_path}, (rules) =>\n\t\t\t\tif @wrapper.site_info.auth_address in rules.signers\n\t\t\t\t\t# ZeroID or other ID provider\n\t\t\t\t\t@publish(inner_path, null)\n\t\t\t\telse if @wrapper.site_info.privatekey\n\t\t\t\t\t# Privatekey stored in users.json\n\t\t\t\t\t@publish(inner_path, \"stored\")\n\t\t\t\telse\n\t\t\t\t\t# Ask the user for privatekey\n\t\t\t\t\t@wrapper.displayPrompt \"Enter your private key:\", \"password\", \"Sign\", \"\", (privatekey) => # Prompt the private key\n\t\t\t\t\t\t@publish(inner_path, privatekey)\n\t\t\treturn false\n\n\t\t# Close\n\t\t@tag.find(\".close\").off(\"click touchend\").on \"click touchend\", (e) =>\n\t\t\t@close()\n\t\t\treturn false\n\n\t\t@loadGlobe()\n\n\tclose: ->\n\t\t@move_lock = \"x\"\n\t\t@startDrag()\n\t\t@stopDrag()\n\n\n\tonClosed: ->\n\t\t$(window).off \"resize\"\n\t\t$(window).on \"resize\", @resized\n\t\t$(document.body).css(\"transition\", \"0.6s ease-in-out\").removeClass(\"body-sidebar\").on transitionEnd, (e) =>\n\t\t\tif e.target == document.body and not $(document.body).hasClass(\"body-sidebar\") and not $(document.body).hasClass(\"body-console\")\n\t\t\t\t$(document.body).css(\"height\", \"auto\").css(\"perspective\", \"\").css(\"will-change\", \"\").css(\"transition\", \"\").off transitionEnd\n\t\t\t\t@unloadGlobe()\n\n\t\t# We dont need site info anymore\n\t\t@wrapper.setSiteInfo = @original_set_site_info\n\n\n\tloadGlobe: =>\n\t\tif @tag.find(\".globe\").hasClass(\"loading\")\n\t\t\tsetTimeout (=>\n\t\t\t\tif typeof(DAT) == \"undefined\"  # Globe script not loaded, do it first\n\t\t\t\t\tscript_tag = $(\"<script>\")\n\t\t\t\t\tscript_tag.attr(\"nonce\", @wrapper.script_nonce)\n\t\t\t\t\tscript_tag.attr(\"src\", \"/uimedia/globe/all.js\")\n\t\t\t\t\tscript_tag.on(\"load\", @displayGlobe)\n\t\t\t\t\tdocument.head.appendChild(script_tag[0])\n\t\t\t\telse\n\t\t\t\t\t@displayGlobe()\n\t\t\t), 600\n\n\n\tdisplayGlobe: =>\n\t\timg = new Image();\n\t\timg.src = \"/uimedia/globe/world.jpg\";\n\t\timg.onload = =>\n\t\t\t@wrapper.ws.cmd \"sidebarGetPeers\", [], (globe_data) =>\n\t\t\t\tif @globe\n\t\t\t\t\t@globe.scene.remove(@globe.points)\n\t\t\t\t\t@globe.addData( globe_data, {format: 'magnitude', name: \"hello\", animated: false} )\n\t\t\t\t\t@globe.createPoints()\n\t\t\t\t\t@tag?.find(\".globe\").removeClass(\"loading\")\n\t\t\t\telse if typeof(DAT) != \"undefined\"\n\t\t\t\t\ttry\n\t\t\t\t\t\t@globe = new DAT.Globe( @tag.find(\".globe\")[0], {\"imgDir\": \"/uimedia/globe/\"} )\n\t\t\t\t\t\t@globe.addData( globe_data, {format: 'magnitude', name: \"hello\"} )\n\t\t\t\t\t\t@globe.createPoints()\n\t\t\t\t\t\t@globe.animate()\n\t\t\t\t\tcatch e\n\t\t\t\t\t\tconsole.log \"WebGL error\", e\n\t\t\t\t\t\t@tag?.find(\".globe\").addClass(\"error\").text(\"WebGL not supported\")\n\t\t\t\t\t@tag?.find(\".globe\").removeClass(\"loading\")\n\n\n\n\tunloadGlobe: =>\n\t\tif not @globe\n\t\t\treturn false\n\t\t@globe.unload()\n\t\t@globe = null\n\n\nwrapper = window.wrapper\nsetTimeout ( ->\n\twindow.sidebar = new Sidebar(wrapper)\n), 500\n\n\nwindow.transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend'\n"
  },
  {
    "path": "plugins/Sidebar/media/Sidebar.css",
    "content": ".menu {\n\tfont-family: Roboto, 'Segoe UI', 'Helvetica Neue'; z-index: 999;\n}\n\n.drag-bg { width: 100%; height: 100%; position: fixed; }\n.fixbutton.dragging { cursor: -webkit-grabbing; }\n.fixbutton-bg:active { cursor: -webkit-grabbing; }\n\n\n.body-sidebar, .body-console { background-color: #666 !important;  }\n#inner-iframe { transition: 0.3s ease-in-out; transform-origin: left bottom; }\n.body-sidebar iframe { transform: rotateY(5deg); opacity: 0.8; pointer-events: none; outline: 1px solid transparent }\n.body-console iframe { transform: rotateX(5deg); opacity: 0.8; pointer-events: none; outline: 1px solid transparent }\n\n.sidebar .label-right { float: right; margin-right: 7px; margin-top: 1px; float: right; }\n.sidebar .link-right { color: white; text-decoration: none; border-bottom: 1px solid #666; text-transform: uppercase; }\n.sidebar .link-right:hover { border-color: #CCC; }\n.sidebar .link-right:active { background-color: #444 }\n.sidebar .link-outline { outline: 1px solid #eee6; padding: 2px 13px; border-bottom: none; font-size: 80%; }\n/* SIDEBAR */\n\n.sidebar-container { width: 100%; height: 100%; overflow: hidden; position: fixed; top: 0px; z-index: 2;}\n.sidebar { background-color: #212121; position: fixed; backface-visibility: hidden; right: -1200px; height: 100%; width: 1200px; } /*box-shadow: inset 0px 0px 10px #000*/\n.sidebar .content { margin: 30px; font-family: \"Segoe UI Light\", \"Segoe UI\", \"Helvetica Neue\"; color: white; width: 375px; height: 300px; font-weight: 200; transition: all 1s; opacity: 0 }\n.sidebar-container.loaded .content { opacity: 1; transform: none }\n.sidebar h1, .sidebar h2 { font-weight: lighter; }\n.sidebar .close { color: #999; float: right; text-decoration: none; margin-top: -5px; padding: 0px 5px; font-size: 33px; margin-right: 20px; display: none }\n.sidebar .button { margin: 0px; display: inline-block; transition: all 0.3s; box-sizing: border-box; max-width: 260px }\n.sidebar .button.hidden { padding: 0px; max-width: 0px; opacity: 0; pointer-events: none }\n.sidebar #button-delete { background-color: transparent; border: 1px solid #333; color: #AAA; margin-left: 10px }\n.sidebar #button-delete:hover { border: 1px solid #666; color: white }\n\n.sidebar .flex { display: flex }\n.sidebar .flex .input.text, .sidebar .flex input.text { width: 100%; }\n.sidebar .flex .button { margin-left: 4px; white-space: nowrap; }\n\n/* FIELDS */\n\n.sidebar .fields { padding: 0px; list-style-type: none; width: 355px; }\n.sidebar .fields > li, .sidebar .fields .settings-owned > li { margin-bottom: 30px }\n.sidebar .fields > li:after, .sidebar .fields .settings-owned > li:after { clear: both; content: ''; display: block }\n.sidebar .fields label {\n\tfont-family: Consolas, monospace; text-transform: uppercase; font-size: 13px; color: #ACACAC; display: inline-block; margin-bottom: 10px;\n\tvertical-align: text-bottom; margin-right: 10px; width: 100%\n}\n.sidebar .fields label small { font-weight: normal; color: white; text-transform: none; }\n.sidebar .fields .text { background-color: black; border: 0px; padding: 10px; color: white; border-radius: 3px; width: 260px; font-family: Consolas, monospace; }\n.sidebar .fields .text.long { width: 330px; font-size: 72%; }\n.sidebar .fields .disabled { color: #AAA; background-color: #3B3B3B; }\n.sidebar .fields .text-num { width: 30px; text-align: right; padding-right: 30px; }\n.sidebar .fields .text-post { color: white; font-family: Consolas, monospace; display: inline-block; font-size: 13px; margin-left: -25px; width: 25px; }\n\n/* Select */\n.sidebar .fields select {\n\twidth: 225px; background-color: #3B3B3B; color: white; font-family: Consolas, monospace; appearance: none;\n\tpadding: 5px; padding-right: 25px; border: 0px; border-radius: 3px; height: 35px; vertical-align: 1px; box-shadow: 0px 1px 2px rgba(0,0,0,0.5);\n}\n.sidebar .fields .select-down { margin-left: -39px; width: 34px; display: inline-block; transform: rotateZ(90deg); height: 35px; vertical-align: -8px; pointer-events: none; font-weight: bold }\n\n/* Checkbox */\n.sidebar .fields .checkbox { width: 50px; height: 24px; position: relative; z-index: 999; opacity: 0; }\n.sidebar .fields .checkbox-skin { background-color: #CCC; width: 50px; height: 24px; border-radius: 15px; transition: all 0.3s ease-in-out; display: inline-block; margin-left: -59px; }\n.sidebar .fields .checkbox-skin:before {\n\tcontent: \"\"; position: relative; width: 20px; background-color: white; height: 20px; display: block; border-radius: 100%; margin-top: 2px; margin-left: 2px;\n\ttransition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.sidebar .fields .checkbox:checked ~ .checkbox-skin:before { margin-left: 27px; }\n.sidebar .fields .checkbox:checked ~ .checkbox-skin { background-color: #2ECC71; }\n\n/* Fake input */\n.sidebar .input { font-size: 13px; width: 250px; display: inline-block; overflow: hidden; text-overflow: ellipsis; vertical-align: top }\n\n/* GRAPH */\n\n.graph { padding: 0px; list-style-type: none; width: 351px; background-color: black; height: 10px; border-radius: 8px; overflow: hidden; position: relative; font-size: 0 }\n.graph li { height: 100%; position: absolute; transition: all 0.3s; }\n.graph-stacked { white-space: nowrap; }\n.graph-stacked li { position: static; display: inline-block; height: 20px }\n\n.graph-legend { padding: 0px; list-style-type: none; margin-top: 13px; font-family: Consolas, \"Andale Mono\", monospace; font-size: 13px; text-transform: capitalize; }\n.sidebar .graph-legend li { margin: 0px; margin-top: 5px; margin-left: 0px; width: 160px; float: left; position: relative; }\n.sidebar .graph-legend li:nth-child(odd) { margin-right: 29px }\n.graph-legend span { position: absolute; }\n.graph-legend b { text-align: right; display: inline-block; width: 50px; float: right; font-weight: normal; }\n.graph-legend li:before { content: '\\2022'; font-size: 23px; line-height: 0px; vertical-align: -3px; margin-right: 5px; }\n\n.filelist { font-size: 12px; font-family: monospace; margin: 0px; padding: 0px; list-style-type: none; line-height: 1.5em; }\n.filelist li:before { content: '\\2022'; font-size: 11px; line-height: 0px; vertical-align: 0px; margin-right: 5px; color: #FFBE00; }\n.filelist li { overflow: hidden; text-overflow: ellipsis; }\n\n/* COLORS */\n\n.back-green { background-color: #2ECC71 }\n.color-green:before { color: #2ECC71 }\n.back-blue { background-color: #3BAFDA }\n.color-blue:before { color: #3BAFDA }\n.back-darkblue { background-color: #156fb7 }\n.color-darkblue:before { color: #156fb7 }\n.back-purple { background-color: #B10DC9 }\n.color-purple:before { color: #B10DC9 }\n.back-yellow { background-color: #FFDC00 }\n.color-yellow:before { color: #FFDC00 }\n.back-orange { background-color: #FF9800 }\n.color-orange:before { color: #FF9800 }\n.back-gray { background-color: #ECF0F1 }\n.color-gray:before { color: #ECF0F1 }\n.back-black { background-color: #34495E }\n.color-black:before { color: #34495E }\n.back-red { background-color: #5E4934 }\n.color-red:before { color: #5E4934 }\n.back-gray { background-color: #9e9e9e }\n.color-gray:before { color: #9e9e9e }\n.back-white { background-color: #EEE }\n.color-white:before { color: #EEE }\n.back-red { background-color: #E91E63 }\n.color-red:before { color: #E91E63 }\n\n\n/* Settings owned */\n\n.owned-title { float: left }\n#checkbox-owned { margin-bottom: 25px; margin-top: 26px; margin-left: 11px; }\n.settings-owned { clear: both }\n#checkbox-owned ~ .settings-owned { opacity: 0; max-height: 0px; transition: all 0.3s linear; overflow: hidden }\n#checkbox-owned:checked ~ .settings-owned { opacity: 1; max-height: 420px }\n\n/* Settings autodownload */\n\n.settings-autodownloadoptional { clear: both; box-sizing: border-box; padding-top: 0px; }\n#checkbox-autodownloadoptional ~ .settings-autodownloadoptional { opacity: 0; max-height: 0px; transition: all 0.3s ease-in-out; overflow: hidden; }\n#checkbox-autodownloadoptional:checked ~ .settings-autodownloadoptional { opacity: 1; max-height: 120px; padding-top: 30px; }\n\n/* Globe */\n.globe { width: 360px; height: 360px }\n.globe.loading { background: url(/uimedia/img/loading-circle.gif) center center no-repeat }\n.globe.error { text-align: center; padding-top: 156px; box-sizing: border-box; opacity: 0.2; }\n\n/* Sign publish */\n.contents { background-color: #3B3B3B; color: white; padding: 7px 10px; font-family: Consolas; font-size: 11px; display: inline-block; margin-bottom: 6px; margin-top: 10px }\n.contents a { color: white }\n.contents a:active { background-color: #6B6B6B }\n\n.contents + .flex.active {\n\tpadding-bottom: 100px;\n}\n#wrapper-sign-publish {\n\tpadding: 0;\n}\n#button-sign-publish, #menu-sign-publish {\n\tdisplay: inline-block;\n\tmargin: 5px 10px;\n\n\ttext-decoration: none;\n}\n#button-sign-publish {\n\tmargin-right: 5px;\n}\n#menu-sign-publish {\n\tmargin-left: 5px;\n\tcolor: #AAA;\n    padding: 7px;\n    margin: 0px;\n}\n#menu-sign-publish:hover { color: white }\n\n/* Small screen */\n@media screen and (max-width: 600px) {\n\t.sidebar .close { display: block }\n}\n"
  },
  {
    "path": "plugins/Sidebar/media/all.css",
    "content": "\n/* ---- Console.css ---- */\n\n\n.console-container { width: 100%; z-index: 998; position: absolute; top: -100vh; padding-bottom: 100%; }\n.console-container .console { background-color: #212121; height: 100vh; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -o-transform: translateY(0px); -ms-transform: translateY(0px); transform: translateY(0px) ; padding-top: 80px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; }\n\n.console-top { color: white; font-family: Consolas, monospace; font-size: 11px; line-height: 20px; height: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; letter-spacing: 0.5px;}\n.console-text { overflow-y: scroll; height: calc(100% - 10px); color: #DDD; padding: 5px; margin-top: -36px; overflow-wrap: break-word; }\n.console-tabs {\n    background-color: #41193fad; position: relative; margin-right: 17px; /*backdrop-filter: blur(2px);*/\n    -webkit-box-shadow: -30px 0px 45px #7d2463; -moz-box-shadow: -30px 0px 45px #7d2463; -o-box-shadow: -30px 0px 45px #7d2463; -ms-box-shadow: -30px 0px 45px #7d2463; box-shadow: -30px 0px 45px #7d2463 ; background: -webkit-linear-gradient(-75deg, #591a48ed, #70305e66);background: -moz-linear-gradient(-75deg, #591a48ed, #70305e66);background: -o-linear-gradient(-75deg, #591a48ed, #70305e66);background: -ms-linear-gradient(-75deg, #591a48ed, #70305e66);background: linear-gradient(-75deg, #591a48ed, #70305e66); border-bottom: 1px solid #792e6473;\n}\n.console-tabs a {\n    margin-right: 5px; padding: 5px 15px; text-decoration: none; color: #AAA;\n    font-size: 11px; font-family: \"Consolas\"; text-transform: uppercase; border: 1px solid #666;\n    border-bottom: 0px; display: inline-block; margin: 5px; margin-bottom: 0px; background-color: rgba(0,0,0,0.5);\n}\n.console-tabs a:hover { color: #FFF }\n.console-tabs a.active { background-color: #46223c; color: #FFF }\n.console-middle {height: 0px; top: 50%; position: absolute; width: 100%; left: 50%; display: none; }\n\n.console .mynode {\n\tborder: 0.5px solid #aaa; width: 50px; height: 50px; -webkit-transform: rotateZ(45deg); -moz-transform: rotateZ(45deg); -o-transform: rotateZ(45deg); -ms-transform: rotateZ(45deg); transform: rotateZ(45deg) ; margin-top: -25px; margin-left: -25px;\n\topacity: 1; display: inline-block; background-color: #EEE; z-index: 9; position: absolute; outline: 5px solid #EEE;\n}\n.console .peers { width: 0px; height: 0px; position: absolute; left: -20px; top: -20px; text-align: center; }\n.console .peer { left: 0px; top: 0px; position: absolute; }\n.console .peer .icon { width: 20px; height: 20px; padding: 10px; display: inline-block; text-decoration: none; left: 200px; position: absolute; color: #666; }\n.console .peer .icon:before { content: \"\\25BC\"; position: absolute; margin-top: 3px; margin-left: -1px; opacity: 0; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.console .peer .icon:hover:before { opacity: 1; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n.console .peer .line {\n\twidth: 187px; border-top: 1px solid #CCC; position: absolute; top: 20px; left: 20px;\n\t-webkit-transform: rotateZ(334deg); -moz-transform: rotateZ(334deg); -o-transform: rotateZ(334deg); -ms-transform: rotateZ(334deg); transform: rotateZ(334deg) ; transform-origin: bottom left;\n}\n\n\n/* ---- Menu.css ---- */\n\n\n.menu {\n\tbackground-color: white; padding: 10px 0px; position: absolute; top: 0px; left: 0px; max-height: 0px; overflow: hidden; -webkit-transform: translate(0px, -30px); -moz-transform: translate(0px, -30px); -o-transform: translate(0px, -30px); -ms-transform: translate(0px, -30px); transform: translate(0px, -30px) ; pointer-events: none;\n\t-webkit-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); -moz-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); -o-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); -ms-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); box-shadow: 0px 2px 8px rgba(0,0,0,0.3) ; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; opacity: 0; -webkit-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; -moz-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; -o-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; -ms-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out ;\n}\n.menu.visible { opacity: 1; max-height: 350px; -webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); -o-transform: translate(0px, 0px); -ms-transform: translate(0px, 0px); transform: translate(0px, 0px) ; -webkit-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; -moz-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; -o-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; -ms-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out; transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s ease-in-out ; pointer-events: all }\n\n.menu-item { display: block; text-decoration: none; color: black; padding: 6px 24px; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -o-transition: all 0.2s; -ms-transition: all 0.2s; transition: all 0.2s ; border-bottom: none; font-weight: normal; padding-left: 30px; }\n.menu-item-separator { margin-top: 5px; border-top: 1px solid #eee }\n\n.menu-item:hover { background-color: #F6F6F6; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; color: inherit; border: none }\n.menu-item:active, .menu-item:focus { background-color: #AF3BFF; color: white; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n.menu-item.selected:before {\n\tcontent: \"L\"; display: inline-block; -webkit-transform: rotateZ(45deg) scaleX(-1); -moz-transform: rotateZ(45deg) scaleX(-1); -o-transform: rotateZ(45deg) scaleX(-1); -ms-transform: rotateZ(45deg) scaleX(-1); transform: rotateZ(45deg) scaleX(-1) ;\n\tfont-weight: bold; position: absolute; margin-left: -17px; font-size: 12px; margin-top: 2px;\n}\n\n@media only screen and (max-width: 800px) {\n.menu, .menu.visible { position: absolute; left: unset !important; right: 20px; }\n}\n\n/* ---- Scrollbable.css ---- */\n\n\n.scrollable {\n    overflow: hidden;\n}\n\n.scrollable.showScroll::after {\n    position: absolute;\n    content: '';\n    top: 5%;\n    right: 7px;\n    height: 90%;\n    width: 3px;\n    background: rgba(224, 224, 255, .3);\n}\n\n.scrollable .content-wrapper {\n    width: 100%;\n    height: 100%;\n    padding-right: 50%;\n    overflow-y: scroll;\n}\n.scroller {\n    margin-top: 5px;\n    z-index: 5;\n    cursor: pointer;\n    position: absolute;\n    width: 7px;\n    -webkit-border-radius: 5px; -moz-border-radius: 5px; -o-border-radius: 5px; -ms-border-radius: 5px; border-radius: 5px ;\n    background: #3A3A3A;\n    top: 0px;\n    left: 395px;\n    -webkit-transition: top .08s;\n    -moz-transition: top .08s;\n    -ms-transition: top .08s;\n    -o-transition: top .08s;\n    -webkit-transition: top .08s; -moz-transition: top .08s; -o-transition: top .08s; -ms-transition: top .08s; transition: top .08s ;\n}\n.scroller {\n    -webkit-touch-callout: none;\n    -webkit-user-select: none;\n    -khtml-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n    user-select: none;\n}\n\n\n/* ---- Sidebar.css ---- */\n\n\n.menu {\n\tfont-family: Roboto, 'Segoe UI', 'Helvetica Neue'; z-index: 999;\n}\n\n.drag-bg { width: 100%; height: 100%; position: fixed; }\n.fixbutton.dragging { cursor: -webkit-grabbing; }\n.fixbutton-bg:active { cursor: -webkit-grabbing; }\n\n\n.body-sidebar, .body-console { background-color: #666 !important;  }\n#inner-iframe { -webkit-transition: 0.3s ease-in-out; -moz-transition: 0.3s ease-in-out; -o-transition: 0.3s ease-in-out; -ms-transition: 0.3s ease-in-out; transition: 0.3s ease-in-out ; transform-origin: left bottom; }\n.body-sidebar iframe { -webkit-transform: rotateY(5deg); -moz-transform: rotateY(5deg); -o-transform: rotateY(5deg); -ms-transform: rotateY(5deg); transform: rotateY(5deg) ; opacity: 0.8; pointer-events: none; outline: 1px solid transparent }\n.body-console iframe { -webkit-transform: rotateX(5deg); -moz-transform: rotateX(5deg); -o-transform: rotateX(5deg); -ms-transform: rotateX(5deg); transform: rotateX(5deg) ; opacity: 0.8; pointer-events: none; outline: 1px solid transparent }\n\n.sidebar .label-right { float: right; margin-right: 7px; margin-top: 1px; float: right; }\n.sidebar .link-right { color: white; text-decoration: none; border-bottom: 1px solid #666; text-transform: uppercase; }\n.sidebar .link-right:hover { border-color: #CCC; }\n.sidebar .link-right:active { background-color: #444 }\n.sidebar .link-outline { outline: 1px solid #eee6; padding: 2px 13px; border-bottom: none; font-size: 80%; }\n/* SIDEBAR */\n\n.sidebar-container { width: 100%; height: 100%; overflow: hidden; position: fixed; top: 0px; z-index: 2;}\n.sidebar { background-color: #212121; position: fixed; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; right: -1200px; height: 100%; width: 1200px; } /*box-shadow: inset 0px 0px 10px #000*/\n.sidebar .content { margin: 30px; font-family: \"Segoe UI Light\", \"Segoe UI\", \"Helvetica Neue\"; color: white; width: 375px; height: 300px; font-weight: 200; -webkit-transition: all 1s; -moz-transition: all 1s; -o-transition: all 1s; -ms-transition: all 1s; transition: all 1s ; opacity: 0 }\n.sidebar-container.loaded .content { opacity: 1; -webkit-transform: none ; -moz-transform: none ; -o-transform: none ; -ms-transform: none ; transform: none  }\n.sidebar h1, .sidebar h2 { font-weight: lighter; }\n.sidebar .close { color: #999; float: right; text-decoration: none; margin-top: -5px; padding: 0px 5px; font-size: 33px; margin-right: 20px; display: none }\n.sidebar .button { margin: 0px; display: inline-block; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; max-width: 260px }\n.sidebar .button.hidden { padding: 0px; max-width: 0px; opacity: 0; pointer-events: none }\n.sidebar #button-delete { background-color: transparent; border: 1px solid #333; color: #AAA; margin-left: 10px }\n.sidebar #button-delete:hover { border: 1px solid #666; color: white }\n\n.sidebar .flex { display: flex }\n.sidebar .flex .input.text, .sidebar .flex input.text { width: 100%; }\n.sidebar .flex .button { margin-left: 4px; white-space: nowrap; }\n\n/* FIELDS */\n\n.sidebar .fields { padding: 0px; list-style-type: none; width: 355px; }\n.sidebar .fields > li, .sidebar .fields .settings-owned > li { margin-bottom: 30px }\n.sidebar .fields > li:after, .sidebar .fields .settings-owned > li:after { clear: both; content: ''; display: block }\n.sidebar .fields label {\n\tfont-family: Consolas, monospace; text-transform: uppercase; font-size: 13px; color: #ACACAC; display: inline-block; margin-bottom: 10px;\n\tvertical-align: text-bottom; margin-right: 10px; width: 100%\n}\n.sidebar .fields label small { font-weight: normal; color: white; text-transform: none; }\n.sidebar .fields .text { background-color: black; border: 0px; padding: 10px; color: white; -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ; width: 260px; font-family: Consolas, monospace; }\n.sidebar .fields .text.long { width: 330px; font-size: 72%; }\n.sidebar .fields .disabled { color: #AAA; background-color: #3B3B3B; }\n.sidebar .fields .text-num { width: 30px; text-align: right; padding-right: 30px; }\n.sidebar .fields .text-post { color: white; font-family: Consolas, monospace; display: inline-block; font-size: 13px; margin-left: -25px; width: 25px; }\n\n/* Select */\n.sidebar .fields select {\n\twidth: 225px; background-color: #3B3B3B; color: white; font-family: Consolas, monospace; -webkit-appearance: none; -moz-appearance: none; -o-appearance: none; -ms-appearance: none; appearance: none ;\n\tpadding: 5px; padding-right: 25px; border: 0px; -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ; height: 35px; vertical-align: 1px; -webkit-box-shadow: 0px 1px 2px rgba(0,0,0,0.5); -moz-box-shadow: 0px 1px 2px rgba(0,0,0,0.5); -o-box-shadow: 0px 1px 2px rgba(0,0,0,0.5); -ms-box-shadow: 0px 1px 2px rgba(0,0,0,0.5); box-shadow: 0px 1px 2px rgba(0,0,0,0.5) ;\n}\n.sidebar .fields .select-down { margin-left: -39px; width: 34px; display: inline-block; -webkit-transform: rotateZ(90deg); -moz-transform: rotateZ(90deg); -o-transform: rotateZ(90deg); -ms-transform: rotateZ(90deg); transform: rotateZ(90deg) ; height: 35px; vertical-align: -8px; pointer-events: none; font-weight: bold }\n\n/* Checkbox */\n.sidebar .fields .checkbox { width: 50px; height: 24px; position: relative; z-index: 999; opacity: 0; }\n.sidebar .fields .checkbox-skin { background-color: #CCC; width: 50px; height: 24px; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; -ms-border-radius: 15px; border-radius: 15px ; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; -ms-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out ; display: inline-block; margin-left: -59px; }\n.sidebar .fields .checkbox-skin:before {\n\tcontent: \"\"; position: relative; width: 20px; background-color: white; height: 20px; display: block; -webkit-border-radius: 100%; -moz-border-radius: 100%; -o-border-radius: 100%; -ms-border-radius: 100%; border-radius: 100% ; margin-top: 2px; margin-left: 2px;\n\t-webkit-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -moz-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -o-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -ms-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86) ;\n}\n.sidebar .fields .checkbox:checked ~ .checkbox-skin:before { margin-left: 27px; }\n.sidebar .fields .checkbox:checked ~ .checkbox-skin { background-color: #2ECC71; }\n\n/* Fake input */\n.sidebar .input { font-size: 13px; width: 250px; display: inline-block; overflow: hidden; text-overflow: ellipsis; vertical-align: top }\n\n/* GRAPH */\n\n.graph { padding: 0px; list-style-type: none; width: 351px; background-color: black; height: 10px; -webkit-border-radius: 8px; -moz-border-radius: 8px; -o-border-radius: 8px; -ms-border-radius: 8px; border-radius: 8px ; overflow: hidden; position: relative; font-size: 0 }\n.graph li { height: 100%; position: absolute; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; }\n.graph-stacked { white-space: nowrap; }\n.graph-stacked li { position: static; display: inline-block; height: 20px }\n\n.graph-legend { padding: 0px; list-style-type: none; margin-top: 13px; font-family: Consolas, \"Andale Mono\", monospace; font-size: 13px; text-transform: capitalize; }\n.sidebar .graph-legend li { margin: 0px; margin-top: 5px; margin-left: 0px; width: 160px; float: left; position: relative; }\n.sidebar .graph-legend li:nth-child(odd) { margin-right: 29px }\n.graph-legend span { position: absolute; }\n.graph-legend b { text-align: right; display: inline-block; width: 50px; float: right; font-weight: normal; }\n.graph-legend li:before { content: '\\2022'; font-size: 23px; line-height: 0px; vertical-align: -3px; margin-right: 5px; }\n\n.filelist { font-size: 12px; font-family: monospace; margin: 0px; padding: 0px; list-style-type: none; line-height: 1.5em; }\n.filelist li:before { content: '\\2022'; font-size: 11px; line-height: 0px; vertical-align: 0px; margin-right: 5px; color: #FFBE00; }\n.filelist li { overflow: hidden; text-overflow: ellipsis; }\n\n/* COLORS */\n\n.back-green { background-color: #2ECC71 }\n.color-green:before { color: #2ECC71 }\n.back-blue { background-color: #3BAFDA }\n.color-blue:before { color: #3BAFDA }\n.back-darkblue { background-color: #156fb7 }\n.color-darkblue:before { color: #156fb7 }\n.back-purple { background-color: #B10DC9 }\n.color-purple:before { color: #B10DC9 }\n.back-yellow { background-color: #FFDC00 }\n.color-yellow:before { color: #FFDC00 }\n.back-orange { background-color: #FF9800 }\n.color-orange:before { color: #FF9800 }\n.back-gray { background-color: #ECF0F1 }\n.color-gray:before { color: #ECF0F1 }\n.back-black { background-color: #34495E }\n.color-black:before { color: #34495E }\n.back-red { background-color: #5E4934 }\n.color-red:before { color: #5E4934 }\n.back-gray { background-color: #9e9e9e }\n.color-gray:before { color: #9e9e9e }\n.back-white { background-color: #EEE }\n.color-white:before { color: #EEE }\n.back-red { background-color: #E91E63 }\n.color-red:before { color: #E91E63 }\n\n\n/* Settings owned */\n\n.owned-title { float: left }\n#checkbox-owned { margin-bottom: 25px; margin-top: 26px; margin-left: 11px; }\n.settings-owned { clear: both }\n#checkbox-owned ~ .settings-owned { opacity: 0; max-height: 0px; -webkit-transition: all 0.3s linear; -moz-transition: all 0.3s linear; -o-transition: all 0.3s linear; -ms-transition: all 0.3s linear; transition: all 0.3s linear ; overflow: hidden }\n#checkbox-owned:checked ~ .settings-owned { opacity: 1; max-height: 420px }\n\n/* Settings autodownload */\n\n.settings-autodownloadoptional { clear: both; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; padding-top: 0px; }\n#checkbox-autodownloadoptional ~ .settings-autodownloadoptional { opacity: 0; max-height: 0px; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; -ms-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out ; overflow: hidden; }\n#checkbox-autodownloadoptional:checked ~ .settings-autodownloadoptional { opacity: 1; max-height: 120px; padding-top: 30px; }\n\n/* Globe */\n.globe { width: 360px; height: 360px }\n.globe.loading { background: url(/uimedia/img/loading-circle.gif) center center no-repeat }\n.globe.error { text-align: center; padding-top: 156px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; opacity: 0.2; }\n\n/* Sign publish */\n.contents { background-color: #3B3B3B; color: white; padding: 7px 10px; font-family: Consolas; font-size: 11px; display: inline-block; margin-bottom: 6px; margin-top: 10px }\n.contents a { color: white }\n.contents a:active { background-color: #6B6B6B }\n\n.contents + .flex.active {\n\tpadding-bottom: 100px;\n}\n#wrapper-sign-publish {\n\tpadding: 0;\n}\n#button-sign-publish, #menu-sign-publish {\n\tdisplay: inline-block;\n\tmargin: 5px 10px;\n\n\ttext-decoration: none;\n}\n#button-sign-publish {\n\tmargin-right: 5px;\n}\n#menu-sign-publish {\n\tmargin-left: 5px;\n\tcolor: #AAA;\n    padding: 7px;\n    margin: 0px;\n}\n#menu-sign-publish:hover { color: white }\n\n/* Small screen */\n@media screen and (max-width: 600px) {\n\t.sidebar .close { display: block }\n}\n"
  },
  {
    "path": "plugins/Sidebar/media/all.js",
    "content": "\n/* ---- Class.coffee ---- */\n\n\n(function() {\n  var Class,\n    slice = [].slice;\n\n  Class = (function() {\n    function Class() {}\n\n    Class.prototype.trace = true;\n\n    Class.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      if (!this.trace) {\n        return;\n      }\n      if (typeof console === 'undefined') {\n        return;\n      }\n      args.unshift(\"[\" + this.constructor.name + \"]\");\n      console.log.apply(console, args);\n      return this;\n    };\n\n    Class.prototype.logStart = function() {\n      var args, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      if (!this.trace) {\n        return;\n      }\n      this.logtimers || (this.logtimers = {});\n      this.logtimers[name] = +(new Date);\n      if (args.length > 0) {\n        this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(started)\"]));\n      }\n      return this;\n    };\n\n    Class.prototype.logEnd = function() {\n      var args, ms, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      ms = +(new Date) - this.logtimers[name];\n      this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(Done in \" + ms + \"ms)\"]));\n      return this;\n    };\n\n    return Class;\n\n  })();\n\n  window.Class = Class;\n\n}).call(this);\n\n/* ---- Console.coffee ---- */\n\n\n(function() {\n  var Console,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  Console = (function(superClass) {\n    extend(Console, superClass);\n\n    function Console(sidebar) {\n      var handleMessageWebsocket_original;\n      this.sidebar = sidebar;\n      this.handleTabClick = bind(this.handleTabClick, this);\n      this.changeFilter = bind(this.changeFilter, this);\n      this.stopDragY = bind(this.stopDragY, this);\n      this.cleanup = bind(this.cleanup, this);\n      this.onClosed = bind(this.onClosed, this);\n      this.onOpened = bind(this.onOpened, this);\n      this.open = bind(this.open, this);\n      this.close = bind(this.close, this);\n      this.loadConsoleText = bind(this.loadConsoleText, this);\n      this.addLines = bind(this.addLines, this);\n      this.formatLine = bind(this.formatLine, this);\n      this.checkTextIsBottom = bind(this.checkTextIsBottom, this);\n      this.tag = null;\n      this.opened = false;\n      this.filter = null;\n      this.tab_types = [\n        {\n          title: \"All\",\n          filter: \"\"\n        }, {\n          title: \"Info\",\n          filter: \"INFO\"\n        }, {\n          title: \"Warning\",\n          filter: \"WARNING\"\n        }, {\n          title: \"Error\",\n          filter: \"ERROR\"\n        }\n      ];\n      this.read_size = 32 * 1024;\n      this.tab_active = \"\";\n      handleMessageWebsocket_original = this.sidebar.wrapper.handleMessageWebsocket;\n      this.sidebar.wrapper.handleMessageWebsocket = (function(_this) {\n        return function(message) {\n          if (message.cmd === \"logLineAdd\" && message.params.stream_id === _this.stream_id) {\n            return _this.addLines(message.params.lines);\n          } else {\n            return handleMessageWebsocket_original(message);\n          }\n        };\n      })(this);\n      $(window).on(\"hashchange\", (function(_this) {\n        return function() {\n          if (window.top.location.hash.startsWith(\"#ZeroNet:Console\")) {\n            return _this.open();\n          }\n        };\n      })(this));\n      if (window.top.location.hash.startsWith(\"#ZeroNet:Console\")) {\n        setTimeout(((function(_this) {\n          return function() {\n            return _this.open();\n          };\n        })(this)), 10);\n      }\n    }\n\n    Console.prototype.createHtmltag = function() {\n      var j, len, ref, tab, tab_type;\n      if (!this.container) {\n        this.container = $(\"<div class=\\\"console-container\\\">\\n\t<div class=\\\"console\\\">\\n\t\t<div class=\\\"console-top\\\">\\n\t\t\t<div class=\\\"console-tabs\\\"></div>\\n\t\t\t<div class=\\\"console-text\\\">Loading...</div>\\n\t\t</div>\\n\t\t<div class=\\\"console-middle\\\">\\n\t\t\t<div class=\\\"mynode\\\"></div>\\n\t\t\t<div class=\\\"peers\\\">\\n\t\t\t\t<div class=\\\"peer\\\"><div class=\\\"line\\\"></div><a href=\\\"#\\\" class=\\\"icon\\\">\\u25BD</div></div>\\n\t\t\t</div>\\n\t\t</div>\\n\t</div>\\n</div>\");\n        this.text = this.container.find(\".console-text\");\n        this.text_elem = this.text[0];\n        this.tabs = this.container.find(\".console-tabs\");\n        this.text.on(\"mousewheel\", (function(_this) {\n          return function(e) {\n            if (e.originalEvent.deltaY < 0) {\n              _this.text.stop();\n            }\n            return RateLimit(300, _this.checkTextIsBottom);\n          };\n        })(this));\n        this.text.is_bottom = true;\n        this.container.appendTo(document.body);\n        this.tag = this.container.find(\".console\");\n        ref = this.tab_types;\n        for (j = 0, len = ref.length; j < len; j++) {\n          tab_type = ref[j];\n          tab = $(\"<a></a>\", {\n            href: \"#\",\n            \"data-filter\": tab_type.filter,\n            \"data-title\": tab_type.title\n          }).text(tab_type.title);\n          if (tab_type.filter === this.tab_active) {\n            tab.addClass(\"active\");\n          }\n          tab.on(\"click\", this.handleTabClick);\n          if (window.top.location.hash.endsWith(tab_type.title)) {\n            this.log(\"Triggering click on\", tab);\n            tab.trigger(\"click\");\n          }\n          this.tabs.append(tab);\n        }\n        this.container.on(\"mousedown touchend touchcancel\", (function(_this) {\n          return function(e) {\n            if (e.target !== e.currentTarget) {\n              return true;\n            }\n            _this.log(\"closing\");\n            if ($(document.body).hasClass(\"body-console\")) {\n              _this.close();\n              return true;\n            }\n          };\n        })(this));\n        return this.loadConsoleText();\n      }\n    };\n\n    Console.prototype.checkTextIsBottom = function() {\n      return this.text.is_bottom = Math.round(this.text_elem.scrollTop + this.text_elem.clientHeight) >= this.text_elem.scrollHeight - 15;\n    };\n\n    Console.prototype.toColor = function(text, saturation, lightness) {\n      var hash, i, j, ref;\n      if (saturation == null) {\n        saturation = 60;\n      }\n      if (lightness == null) {\n        lightness = 70;\n      }\n      hash = 0;\n      for (i = j = 0, ref = text.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {\n        hash += text.charCodeAt(i) * i;\n        hash = hash % 1777;\n      }\n      return \"hsl(\" + (hash % 360) + (\",\" + saturation + \"%,\" + lightness + \"%)\");\n    };\n\n    Console.prototype.formatLine = function(line) {\n      var added, level, match, module, ref, text;\n      match = line.match(/(\\[.*?\\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/);\n      if (!match) {\n        return line.replace(/\\</g, \"&lt;\").replace(/\\>/g, \"&gt;\");\n      }\n      ref = line.match(/(\\[.*?\\])[ ]+(.*?)[ ]+(.*?)[ ]+(.*)/), line = ref[0], added = ref[1], level = ref[2], module = ref[3], text = ref[4];\n      added = \"<span style='color: #dfd0fa'>\" + added + \"</span>\";\n      level = \"<span style='color: \" + (this.toColor(level, 100)) + \";'>\" + level + \"</span>\";\n      module = \"<span style='color: \" + (this.toColor(module, 60)) + \"; font-weight: bold;'>\" + module + \"</span>\";\n      text = text.replace(/(Site:[A-Za-z0-9\\.]+)/g, \"<span style='color: #AAAAFF'>$1</span>\");\n      text = text.replace(/\\</g, \"&lt;\").replace(/\\>/g, \"&gt;\");\n      return added + \" \" + level + \" \" + module + \" \" + text;\n    };\n\n    Console.prototype.addLines = function(lines, animate) {\n      var html_lines, j, len, line;\n      if (animate == null) {\n        animate = true;\n      }\n      html_lines = [];\n      this.logStart(\"formatting\");\n      for (j = 0, len = lines.length; j < len; j++) {\n        line = lines[j];\n        html_lines.push(this.formatLine(line));\n      }\n      this.logEnd(\"formatting\");\n      this.logStart(\"adding\");\n      this.text.append(html_lines.join(\"<br>\") + \"<br>\");\n      this.logEnd(\"adding\");\n      if (this.text.is_bottom && animate) {\n        return this.text.stop().animate({\n          scrollTop: this.text_elem.scrollHeight - this.text_elem.clientHeight + 1\n        }, 600, 'easeInOutCubic');\n      }\n    };\n\n    Console.prototype.loadConsoleText = function() {\n      this.sidebar.wrapper.ws.cmd(\"consoleLogRead\", {\n        filter: this.filter,\n        read_size: this.read_size\n      }, (function(_this) {\n        return function(res) {\n          var pos_diff, size_read, size_total;\n          _this.text.html(\"\");\n          pos_diff = res[\"pos_end\"] - res[\"pos_start\"];\n          size_read = Math.round(pos_diff / 1024);\n          size_total = Math.round(res['pos_end'] / 1024);\n          _this.text.append(\"<br><br>\");\n          _this.text.append(\"Displaying \" + res.lines.length + \" of \" + res.num_found + \" lines found in the last \" + size_read + \"kB of the log file. (\" + size_total + \"kB total)<br>\");\n          _this.addLines(res.lines, false);\n          return _this.text_elem.scrollTop = _this.text_elem.scrollHeight;\n        };\n      })(this));\n      if (this.stream_id) {\n        this.sidebar.wrapper.ws.cmd(\"consoleLogStreamRemove\", {\n          stream_id: this.stream_id\n        });\n      }\n      return this.sidebar.wrapper.ws.cmd(\"consoleLogStream\", {\n        filter: this.filter\n      }, (function(_this) {\n        return function(res) {\n          return _this.stream_id = res.stream_id;\n        };\n      })(this));\n    };\n\n    Console.prototype.close = function() {\n      window.top.location.hash = \"\";\n      this.sidebar.move_lock = \"y\";\n      this.sidebar.startDrag();\n      return this.sidebar.stopDrag();\n    };\n\n    Console.prototype.open = function() {\n      this.sidebar.startDrag();\n      this.sidebar.moved(\"y\");\n      this.sidebar.fixbutton_targety = this.sidebar.page_height - this.sidebar.fixbutton_inity - 50;\n      return this.sidebar.stopDrag();\n    };\n\n    Console.prototype.onOpened = function() {\n      this.sidebar.onClosed();\n      return this.log(\"onOpened\");\n    };\n\n    Console.prototype.onClosed = function() {\n      $(document.body).removeClass(\"body-console\");\n      if (this.stream_id) {\n        return this.sidebar.wrapper.ws.cmd(\"consoleLogStreamRemove\", {\n          stream_id: this.stream_id\n        });\n      }\n    };\n\n    Console.prototype.cleanup = function() {\n      if (this.container) {\n        this.container.remove();\n        return this.container = null;\n      }\n    };\n\n    Console.prototype.stopDragY = function() {\n      var targety;\n      if (this.sidebar.fixbutton_targety === this.sidebar.fixbutton_inity) {\n        targety = 0;\n        this.opened = false;\n      } else {\n        targety = this.sidebar.fixbutton_targety - this.sidebar.fixbutton_inity;\n        this.onOpened();\n        this.opened = true;\n      }\n      if (this.tag) {\n        this.tag.css(\"transition\", \"0.5s ease-out\");\n        this.tag.css(\"transform\", \"translateY(\" + targety + \"px)\").one(transitionEnd, (function(_this) {\n          return function() {\n            _this.tag.css(\"transition\", \"\");\n            if (!_this.opened) {\n              return _this.cleanup();\n            }\n          };\n        })(this));\n      }\n      this.log(\"stopDragY\", \"opened:\", this.opened, targety);\n      if (!this.opened) {\n        return this.onClosed();\n      }\n    };\n\n    Console.prototype.changeFilter = function(filter) {\n      this.filter = filter;\n      if (this.filter === \"\") {\n        this.read_size = 32 * 1024;\n      } else {\n        this.read_size = 5 * 1024 * 1024;\n      }\n      return this.loadConsoleText();\n    };\n\n    Console.prototype.handleTabClick = function(e) {\n      var elem;\n      elem = $(e.currentTarget);\n      this.tab_active = elem.data(\"filter\");\n      $(\"a\", this.tabs).removeClass(\"active\");\n      elem.addClass(\"active\");\n      this.changeFilter(this.tab_active);\n      window.top.location.hash = \"#ZeroNet:Console:\" + elem.data(\"title\");\n      return false;\n    };\n\n    return Console;\n\n  })(Class);\n\n  window.Console = Console;\n\n}).call(this);\n\n/* ---- Menu.coffee ---- */\n\n\n(function() {\n  var Menu,\n    slice = [].slice;\n\n  Menu = (function() {\n    function Menu(button) {\n      this.button = button;\n      this.elem = $(\".menu.template\").clone().removeClass(\"template\");\n      this.elem.appendTo(\"body\");\n      this.items = [];\n    }\n\n    Menu.prototype.show = function() {\n      var button_pos, left;\n      if (window.visible_menu && window.visible_menu.button[0] === this.button[0]) {\n        window.visible_menu.hide();\n        return this.hide();\n      } else {\n        button_pos = this.button.offset();\n        left = button_pos.left;\n        this.elem.css({\n          \"top\": button_pos.top + this.button.outerHeight(),\n          \"left\": left\n        });\n        this.button.addClass(\"menu-active\");\n        this.elem.addClass(\"visible\");\n        if (this.elem.position().left + this.elem.width() + 20 > window.innerWidth) {\n          this.elem.css(\"left\", window.innerWidth - this.elem.width() - 20);\n        }\n        if (window.visible_menu) {\n          window.visible_menu.hide();\n        }\n        return window.visible_menu = this;\n      }\n    };\n\n    Menu.prototype.hide = function() {\n      this.elem.removeClass(\"visible\");\n      this.button.removeClass(\"menu-active\");\n      return window.visible_menu = null;\n    };\n\n    Menu.prototype.addItem = function(title, cb) {\n      var item;\n      item = $(\".menu-item.template\", this.elem).clone().removeClass(\"template\");\n      item.html(title);\n      item.on(\"click\", (function(_this) {\n        return function() {\n          if (!cb(item)) {\n            _this.hide();\n          }\n          return false;\n        };\n      })(this));\n      item.appendTo(this.elem);\n      this.items.push(item);\n      return item;\n    };\n\n    Menu.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      return console.log.apply(console, [\"[Menu]\"].concat(slice.call(args)));\n    };\n\n    return Menu;\n\n  })();\n\n  window.Menu = Menu;\n\n  $(\"body\").on(\"click\", function(e) {\n    if (window.visible_menu && e.target !== window.visible_menu.button[0] && $(e.target).parent()[0] !== window.visible_menu.elem[0]) {\n      return window.visible_menu.hide();\n    }\n  });\n\n}).call(this);\n\n/* ---- Prototypes.coffee ---- */\n\n\n(function() {\n  String.prototype.startsWith = function(s) {\n    return this.slice(0, s.length) === s;\n  };\n\n  String.prototype.endsWith = function(s) {\n    return s === '' || this.slice(-s.length) === s;\n  };\n\n  String.prototype.capitalize = function() {\n    if (this.length) {\n      return this[0].toUpperCase() + this.slice(1);\n    } else {\n      return \"\";\n    }\n  };\n\n  String.prototype.repeat = function(count) {\n    return new Array(count + 1).join(this);\n  };\n\n  window.isEmpty = function(obj) {\n    var key;\n    for (key in obj) {\n      return false;\n    }\n    return true;\n  };\n\n}).call(this);\n\n/* ---- RateLimit.coffee ---- */\n\n\n(function() {\n  var call_after_interval, limits;\n\n  limits = {};\n\n  call_after_interval = {};\n\n  window.RateLimit = function(interval, fn) {\n    if (!limits[fn]) {\n      call_after_interval[fn] = false;\n      fn();\n      return limits[fn] = setTimeout((function() {\n        if (call_after_interval[fn]) {\n          fn();\n        }\n        delete limits[fn];\n        return delete call_after_interval[fn];\n      }), interval);\n    } else {\n      return call_after_interval[fn] = true;\n    }\n  };\n\n}).call(this);\n\n/* ---- Scrollable.js ---- */\n\n\n/* via http://jsfiddle.net/elGrecode/00dgurnn/ */\n\nwindow.initScrollable = function () {\n\n    var scrollContainer = document.querySelector('.scrollable'),\n        scrollContentWrapper = document.querySelector('.scrollable .content-wrapper'),\n        scrollContent = document.querySelector('.scrollable .content'),\n        contentPosition = 0,\n        scrollerBeingDragged = false,\n        scroller,\n        topPosition,\n        scrollerHeight;\n\n    function calculateScrollerHeight() {\n        // *Calculation of how tall scroller should be\n        var visibleRatio = scrollContainer.offsetHeight / scrollContentWrapper.scrollHeight;\n        if (visibleRatio == 1)\n            scroller.style.display = \"none\";\n        else\n            scroller.style.display = \"block\";\n        return visibleRatio * scrollContainer.offsetHeight;\n    }\n\n    function moveScroller(evt) {\n        // Move Scroll bar to top offset\n        var scrollPercentage = evt.target.scrollTop / scrollContentWrapper.scrollHeight;\n        topPosition = scrollPercentage * (scrollContainer.offsetHeight - 5); // 5px arbitrary offset so scroll bar doesn't move too far beyond content wrapper bounding box\n        scroller.style.top = topPosition + 'px';\n    }\n\n    function startDrag(evt) {\n        normalizedPosition = evt.pageY;\n        contentPosition = scrollContentWrapper.scrollTop;\n        scrollerBeingDragged = true;\n        window.addEventListener('mousemove', scrollBarScroll);\n        return false;\n    }\n\n    function stopDrag(evt) {\n        scrollerBeingDragged = false;\n        window.removeEventListener('mousemove', scrollBarScroll);\n    }\n\n    function scrollBarScroll(evt) {\n        if (scrollerBeingDragged === true) {\n            evt.preventDefault();\n            var mouseDifferential = evt.pageY - normalizedPosition;\n            var scrollEquivalent = mouseDifferential * (scrollContentWrapper.scrollHeight / scrollContainer.offsetHeight);\n            scrollContentWrapper.scrollTop = contentPosition + scrollEquivalent;\n        }\n    }\n\n    function updateHeight() {\n        scrollerHeight = calculateScrollerHeight() - 10;\n        scroller.style.height = scrollerHeight + 'px';\n    }\n\n    function createScroller() {\n        // *Creates scroller element and appends to '.scrollable' div\n        // create scroller element\n        scroller = document.createElement(\"div\");\n        scroller.className = 'scroller';\n\n        // determine how big scroller should be based on content\n        scrollerHeight = calculateScrollerHeight() - 10;\n\n        if (scrollerHeight / scrollContainer.offsetHeight < 1) {\n            // *If there is a need to have scroll bar based on content size\n            scroller.style.height = scrollerHeight + 'px';\n\n            // append scroller to scrollContainer div\n            scrollContainer.appendChild(scroller);\n\n            // show scroll path divot\n            scrollContainer.className += ' showScroll';\n\n            // attach related draggable listeners\n            scroller.addEventListener('mousedown', startDrag);\n            window.addEventListener('mouseup', stopDrag);\n        }\n\n    }\n\n    createScroller();\n\n\n    // *** Listeners ***\n    scrollContentWrapper.addEventListener('scroll', moveScroller);\n\n    return updateHeight;\n};\n\n/* ---- Sidebar.coffee ---- */\n\n\n(function() {\n  var Sidebar, wrapper,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty,\n    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n  Sidebar = (function(superClass) {\n    extend(Sidebar, superClass);\n\n    function Sidebar(wrapper1) {\n      this.wrapper = wrapper1;\n      this.unloadGlobe = bind(this.unloadGlobe, this);\n      this.displayGlobe = bind(this.displayGlobe, this);\n      this.loadGlobe = bind(this.loadGlobe, this);\n      this.animDrag = bind(this.animDrag, this);\n      this.setHtmlTag = bind(this.setHtmlTag, this);\n      this.waitMove = bind(this.waitMove, this);\n      this.resized = bind(this.resized, this);\n      this.tag = null;\n      this.container = null;\n      this.opened = false;\n      this.width = 410;\n      this.console = new Console(this);\n      this.fixbutton = $(\".fixbutton\");\n      this.fixbutton_addx = 0;\n      this.fixbutton_addy = 0;\n      this.fixbutton_initx = 0;\n      this.fixbutton_inity = 15;\n      this.fixbutton_targetx = 0;\n      this.move_lock = null;\n      this.page_width = $(window).width();\n      this.page_height = $(window).height();\n      this.frame = $(\"#inner-iframe\");\n      this.initFixbutton();\n      this.dragStarted = 0;\n      this.globe = null;\n      this.preload_html = null;\n      this.original_set_site_info = this.wrapper.setSiteInfo;\n      if (window.top.location.hash === \"#ZeroNet:OpenSidebar\") {\n        this.startDrag();\n        this.moved(\"x\");\n        this.fixbutton_targetx = this.fixbutton_initx - this.width;\n        this.stopDrag();\n      }\n    }\n\n    Sidebar.prototype.initFixbutton = function() {\n      this.fixbutton.on(\"mousedown touchstart\", (function(_this) {\n        return function(e) {\n          if (e.button > 0) {\n            return;\n          }\n          e.preventDefault();\n          _this.fixbutton.off(\"click touchend touchcancel\");\n          _this.dragStarted = +(new Date);\n          $(\".drag-bg\").remove();\n          $(\"<div class='drag-bg'></div>\").appendTo(document.body);\n          return $(\"body\").one(\"mousemove touchmove\", function(e) {\n            var mousex, mousey;\n            mousex = e.pageX;\n            mousey = e.pageY;\n            if (!mousex) {\n              mousex = e.originalEvent.touches[0].pageX;\n              mousey = e.originalEvent.touches[0].pageY;\n            }\n            _this.fixbutton_addx = _this.fixbutton.offset().left - mousex;\n            _this.fixbutton_addy = _this.fixbutton.offset().top - mousey;\n            return _this.startDrag();\n          });\n        };\n      })(this));\n      this.fixbutton.parent().on(\"click touchend touchcancel\", (function(_this) {\n        return function(e) {\n          if ((+(new Date)) - _this.dragStarted < 100) {\n            window.top.location = _this.fixbutton.find(\".fixbutton-bg\").attr(\"href\");\n          }\n          return _this.stopDrag();\n        };\n      })(this));\n      this.resized();\n      return $(window).on(\"resize\", this.resized);\n    };\n\n    Sidebar.prototype.resized = function() {\n      this.page_width = $(window).width();\n      this.page_height = $(window).height();\n      this.fixbutton_initx = this.page_width - 75;\n      if (this.opened) {\n        return this.fixbutton.css({\n          left: this.fixbutton_initx - this.width\n        });\n      } else {\n        return this.fixbutton.css({\n          left: this.fixbutton_initx\n        });\n      }\n    };\n\n    Sidebar.prototype.startDrag = function() {\n      this.log(\"startDrag\", this.fixbutton_initx, this.fixbutton_inity);\n      this.fixbutton_targetx = this.fixbutton_initx;\n      this.fixbutton_targety = this.fixbutton_inity;\n      this.fixbutton.addClass(\"dragging\");\n      if (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0) {\n        this.fixbutton.css(\"pointer-events\", \"none\");\n      }\n      this.fixbutton.one(\"click\", (function(_this) {\n        return function(e) {\n          var moved_x, moved_y;\n          _this.stopDrag();\n          _this.fixbutton.removeClass(\"dragging\");\n          moved_x = Math.abs(_this.fixbutton.offset().left - _this.fixbutton_initx);\n          moved_y = Math.abs(_this.fixbutton.offset().top - _this.fixbutton_inity);\n          if (moved_x > 5 || moved_y > 10) {\n            return e.preventDefault();\n          }\n        };\n      })(this));\n      this.fixbutton.parents().on(\"mousemove touchmove\", this.animDrag);\n      this.fixbutton.parents().on(\"mousemove touchmove\", this.waitMove);\n      return this.fixbutton.parents().one(\"mouseup touchend touchcancel\", (function(_this) {\n        return function(e) {\n          e.preventDefault();\n          return _this.stopDrag();\n        };\n      })(this));\n    };\n\n    Sidebar.prototype.waitMove = function(e) {\n      var moved_x, moved_y;\n      document.body.style.perspective = \"1000px\";\n      document.body.style.height = \"100%\";\n      document.body.style.willChange = \"perspective\";\n      document.documentElement.style.height = \"100%\";\n      moved_x = Math.abs(parseInt(this.fixbutton[0].style.left) - this.fixbutton_targetx);\n      moved_y = Math.abs(parseInt(this.fixbutton[0].style.top) - this.fixbutton_targety);\n      if (moved_x > 5 && (+(new Date)) - this.dragStarted + moved_x > 50) {\n        this.moved(\"x\");\n        this.fixbutton.stop().animate({\n          \"top\": this.fixbutton_inity\n        }, 1000);\n        return this.fixbutton.parents().off(\"mousemove touchmove\", this.waitMove);\n      } else if (moved_y > 5 && (+(new Date)) - this.dragStarted + moved_y > 50) {\n        this.moved(\"y\");\n        return this.fixbutton.parents().off(\"mousemove touchmove\", this.waitMove);\n      }\n    };\n\n    Sidebar.prototype.moved = function(direction) {\n      var img;\n      this.log(\"Moved\", direction);\n      this.move_lock = direction;\n      if (direction === \"y\") {\n        $(document.body).addClass(\"body-console\");\n        return this.console.createHtmltag();\n      }\n      this.createHtmltag();\n      $(document.body).addClass(\"body-sidebar\");\n      this.container.on(\"mousedown touchend touchcancel\", (function(_this) {\n        return function(e) {\n          if (e.target !== e.currentTarget) {\n            return true;\n          }\n          _this.log(\"closing\");\n          if ($(document.body).hasClass(\"body-sidebar\")) {\n            _this.close();\n            return true;\n          }\n        };\n      })(this));\n      $(window).off(\"resize\");\n      $(window).on(\"resize\", (function(_this) {\n        return function() {\n          $(document.body).css(\"height\", $(window).height());\n          _this.scrollable();\n          return _this.resized();\n        };\n      })(this));\n      this.wrapper.setSiteInfo = (function(_this) {\n        return function(site_info) {\n          _this.setSiteInfo(site_info);\n          return _this.original_set_site_info.apply(_this.wrapper, arguments);\n        };\n      })(this);\n      img = new Image();\n      return img.src = \"/uimedia/globe/world.jpg\";\n    };\n\n    Sidebar.prototype.setSiteInfo = function(site_info) {\n      RateLimit(1500, (function(_this) {\n        return function() {\n          return _this.updateHtmlTag();\n        };\n      })(this));\n      return RateLimit(30000, (function(_this) {\n        return function() {\n          return _this.displayGlobe();\n        };\n      })(this));\n    };\n\n    Sidebar.prototype.createHtmltag = function() {\n      this.when_loaded = $.Deferred();\n      if (!this.container) {\n        this.container = $(\"<div class=\\\"sidebar-container\\\"><div class=\\\"sidebar scrollable\\\"><div class=\\\"content-wrapper\\\"><div class=\\\"content\\\">\\n</div></div></div></div>\");\n        this.container.appendTo(document.body);\n        this.tag = this.container.find(\".sidebar\");\n        this.updateHtmlTag();\n        return this.scrollable = window.initScrollable();\n      }\n    };\n\n    Sidebar.prototype.updateHtmlTag = function() {\n      if (this.preload_html) {\n        this.setHtmlTag(this.preload_html);\n        return this.preload_html = null;\n      } else {\n        return this.wrapper.ws.cmd(\"sidebarGetHtmlTag\", {}, this.setHtmlTag);\n      }\n    };\n\n    Sidebar.prototype.setHtmlTag = function(res) {\n      if (this.tag.find(\".content\").children().length === 0) {\n        this.log(\"Creating content\");\n        this.container.addClass(\"loaded\");\n        morphdom(this.tag.find(\".content\")[0], '<div class=\"content\">' + res + '</div>');\n        this.when_loaded.resolve();\n      } else {\n        morphdom(this.tag.find(\".content\")[0], '<div class=\"content\">' + res + '</div>', {\n          onBeforeMorphEl: function(from_el, to_el) {\n            if (from_el.className === \"globe\" || from_el.className.indexOf(\"noupdate\") >= 0) {\n              return false;\n            } else {\n              return true;\n            }\n          }\n        });\n      }\n      this.tag.find(\"#privatekey-add\").off(\"click, touchend\").on(\"click touchend\", (function(_this) {\n        return function(e) {\n          _this.wrapper.displayPrompt(\"Enter your private key:\", \"password\", \"Save\", \"\", function(privatekey) {\n            return _this.wrapper.ws.cmd(\"userSetSitePrivatekey\", [privatekey], function(res) {\n              return _this.wrapper.notifications.add(\"privatekey\", \"done\", \"Private key saved for site signing\", 5000);\n            });\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#privatekey-forget\").off(\"click, touchend\").on(\"click touchend\", (function(_this) {\n        return function(e) {\n          _this.wrapper.displayConfirm(\"Remove saved private key for this site?\", \"Forget\", function(res) {\n            if (!res) {\n              return false;\n            }\n            return _this.wrapper.ws.cmd(\"userSetSitePrivatekey\", [\"\"], function(res) {\n              return _this.wrapper.notifications.add(\"privatekey\", \"done\", \"Saved private key removed\", 5000);\n            });\n          });\n          return false;\n        };\n      })(this));\n      return this.tag.find(\"#browse-files\").attr(\"href\", document.location.pathname.replace(/(\\/.*?(\\/|$)).*$/, \"/list$1\"));\n    };\n\n    Sidebar.prototype.animDrag = function(e) {\n      var mousex, mousey, overdrag, overdrag_percent, targetx, targety;\n      mousex = e.pageX;\n      mousey = e.pageY;\n      if (!mousex && e.originalEvent.touches) {\n        mousex = e.originalEvent.touches[0].pageX;\n        mousey = e.originalEvent.touches[0].pageY;\n      }\n      overdrag = this.fixbutton_initx - this.width - mousex;\n      if (overdrag > 0) {\n        overdrag_percent = 1 + overdrag / 300;\n        mousex = (mousex + (this.fixbutton_initx - this.width) * overdrag_percent) / (1 + overdrag_percent);\n      }\n      targetx = this.fixbutton_initx - mousex - this.fixbutton_addx;\n      targety = this.fixbutton_inity - mousey - this.fixbutton_addy;\n      if (this.move_lock === \"x\") {\n        targety = this.fixbutton_inity;\n      } else if (this.move_lock === \"y\") {\n        targetx = this.fixbutton_initx;\n      }\n      if (!this.move_lock || this.move_lock === \"x\") {\n        this.fixbutton[0].style.left = (mousex + this.fixbutton_addx) + \"px\";\n        if (this.tag) {\n          this.tag[0].style.transform = \"translateX(\" + (0 - targetx) + \"px)\";\n        }\n      }\n      if (!this.move_lock || this.move_lock === \"y\") {\n        this.fixbutton[0].style.top = (mousey + this.fixbutton_addy) + \"px\";\n        if (this.console.tag) {\n          this.console.tag[0].style.transform = \"translateY(\" + (0 - targety) + \"px)\";\n        }\n      }\n      if ((!this.opened && targetx > this.width / 3) || (this.opened && targetx > this.width * 0.9)) {\n        this.fixbutton_targetx = this.fixbutton_initx - this.width;\n      } else {\n        this.fixbutton_targetx = this.fixbutton_initx;\n      }\n      if ((!this.console.opened && 0 - targety > this.page_height / 10) || (this.console.opened && 0 - targety > this.page_height * 0.8)) {\n        return this.fixbutton_targety = this.page_height - this.fixbutton_inity - 50;\n      } else {\n        return this.fixbutton_targety = this.fixbutton_inity;\n      }\n    };\n\n    Sidebar.prototype.stopDrag = function() {\n      var left, top;\n      this.fixbutton.parents().off(\"mousemove touchmove\");\n      this.fixbutton.off(\"mousemove touchmove\");\n      this.fixbutton.css(\"pointer-events\", \"\");\n      $(\".drag-bg\").remove();\n      if (!this.fixbutton.hasClass(\"dragging\")) {\n        return;\n      }\n      this.fixbutton.removeClass(\"dragging\");\n      if (this.fixbutton_targetx !== this.fixbutton.offset().left || this.fixbutton_targety !== this.fixbutton.offset().top) {\n        if (this.move_lock === \"y\") {\n          top = this.fixbutton_targety;\n          left = this.fixbutton_initx;\n        }\n        if (this.move_lock === \"x\") {\n          top = this.fixbutton_inity;\n          left = this.fixbutton_targetx;\n        }\n        this.fixbutton.stop().animate({\n          \"left\": left,\n          \"top\": top\n        }, 500, \"easeOutBack\", (function(_this) {\n          return function() {\n            if (_this.fixbutton_targetx === _this.fixbutton_initx) {\n              _this.fixbutton.css(\"left\", \"auto\");\n            } else {\n              _this.fixbutton.css(\"left\", left);\n            }\n            return $(\".fixbutton-bg\").trigger(\"mouseout\");\n          };\n        })(this));\n        this.stopDragX();\n        this.console.stopDragY();\n      }\n      return this.move_lock = null;\n    };\n\n    Sidebar.prototype.stopDragX = function() {\n      var targetx;\n      if (this.fixbutton_targetx === this.fixbutton_initx || this.move_lock === \"y\") {\n        targetx = 0;\n        this.opened = false;\n      } else {\n        targetx = this.width;\n        if (this.opened) {\n          this.onOpened();\n        } else {\n          this.when_loaded.done((function(_this) {\n            return function() {\n              return _this.onOpened();\n            };\n          })(this));\n        }\n        this.opened = true;\n      }\n      if (this.tag) {\n        this.tag.css(\"transition\", \"0.4s ease-out\");\n        this.tag.css(\"transform\", \"translateX(-\" + targetx + \"px)\").one(transitionEnd, (function(_this) {\n          return function() {\n            _this.tag.css(\"transition\", \"\");\n            if (!_this.opened) {\n              _this.container.remove();\n              _this.container = null;\n              if (_this.tag) {\n                _this.tag.remove();\n                return _this.tag = null;\n              }\n            }\n          };\n        })(this));\n      }\n      this.log(\"stopdrag\", \"opened:\", this.opened);\n      if (!this.opened) {\n        return this.onClosed();\n      }\n    };\n\n    Sidebar.prototype.sign = function(inner_path, privatekey) {\n      this.wrapper.displayProgress(\"sign\", \"Signing: \" + inner_path + \"...\", 0);\n      return this.wrapper.ws.cmd(\"siteSign\", {\n        privatekey: privatekey,\n        inner_path: inner_path,\n        update_changed_files: true\n      }, (function(_this) {\n        return function(res) {\n          if (res === \"ok\") {\n            return _this.wrapper.displayProgress(\"sign\", inner_path + \" signed!\", 100);\n          } else {\n            return _this.wrapper.displayProgress(\"sign\", \"Error signing \" + inner_path, -1);\n          }\n        };\n      })(this));\n    };\n\n    Sidebar.prototype.publish = function(inner_path, privatekey) {\n      return this.wrapper.ws.cmd(\"sitePublish\", {\n        privatekey: privatekey,\n        inner_path: inner_path,\n        sign: true,\n        update_changed_files: true\n      }, (function(_this) {\n        return function(res) {\n          if (res === \"ok\") {\n            return _this.wrapper.notifications.add(\"sign\", \"done\", inner_path + \" Signed and published!\", 5000);\n          }\n        };\n      })(this));\n    };\n\n    Sidebar.prototype.handleSiteDeleteClick = function() {\n      var options, question;\n      if (this.wrapper.site_info.privatekey) {\n        question = \"Are you sure?<br>This site has a saved private key\";\n        options = [\"Forget private key and delete site\"];\n      } else {\n        question = \"Are you sure?\";\n        options = [\"Delete this site\", \"Blacklist\"];\n      }\n      return this.wrapper.displayConfirm(question, options, (function(_this) {\n        return function(confirmed) {\n          if (confirmed === 1) {\n            _this.tag.find(\"#button-delete\").addClass(\"loading\");\n            return _this.wrapper.ws.cmd(\"siteDelete\", _this.wrapper.site_info.address, function() {\n              return document.location = $(\".fixbutton-bg\").attr(\"href\");\n            });\n          } else if (confirmed === 2) {\n            return _this.wrapper.displayPrompt(\"Blacklist this site\", \"text\", \"Delete and Blacklist\", \"Reason\", function(reason) {\n              _this.tag.find(\"#button-delete\").addClass(\"loading\");\n              _this.wrapper.ws.cmd(\"siteblockAdd\", [_this.wrapper.site_info.address, reason]);\n              return _this.wrapper.ws.cmd(\"siteDelete\", _this.wrapper.site_info.address, function() {\n                return document.location = $(\".fixbutton-bg\").attr(\"href\");\n              });\n            });\n          }\n        };\n      })(this));\n    };\n\n    Sidebar.prototype.onOpened = function() {\n      var menu;\n      this.log(\"Opened\");\n      this.scrollable();\n      this.tag.find(\"#checkbox-owned, #checkbox-autodownloadoptional\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          return setTimeout((function() {\n            return _this.scrollable();\n          }), 300);\n        };\n      })(this));\n      this.tag.find(\"#button-sitelimit\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"siteSetLimit\", $(\"#input-sitelimit\").val(), function(res) {\n            if (res === \"ok\") {\n              _this.wrapper.notifications.add(\"done-sitelimit\", \"done\", \"Site storage limit modified!\", 5000);\n            }\n            return _this.updateHtmlTag();\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-autodownload_bigfile_size_limit\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"siteSetAutodownloadBigfileLimit\", $(\"#input-autodownload_bigfile_size_limit\").val(), function(res) {\n            if (res === \"ok\") {\n              _this.wrapper.notifications.add(\"done-bigfilelimit\", \"done\", \"Site bigfile auto download limit modified!\", 5000);\n            }\n            return _this.updateHtmlTag();\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-autodownload_previous\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"siteUpdate\", {\n            \"address\": _this.wrapper.site_info.address,\n            \"check_files\": true\n          }, function() {\n            return _this.wrapper.notifications.add(\"done-download_optional\", \"done\", \"Optional files downloaded\", 5000);\n          });\n          _this.wrapper.notifications.add(\"start-download_optional\", \"info\", \"Optional files download started\", 5000);\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-dbreload\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"dbReload\", [], function() {\n            _this.wrapper.notifications.add(\"done-dbreload\", \"done\", \"Database schema reloaded!\", 5000);\n            return _this.updateHtmlTag();\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-dbrebuild\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.notifications.add(\"done-dbrebuild\", \"info\", \"Database rebuilding....\");\n          _this.wrapper.ws.cmd(\"dbRebuild\", [], function() {\n            _this.wrapper.notifications.add(\"done-dbrebuild\", \"done\", \"Database rebuilt!\", 5000);\n            return _this.updateHtmlTag();\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-update\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.tag.find(\"#button-update\").addClass(\"loading\");\n          _this.wrapper.ws.cmd(\"siteUpdate\", _this.wrapper.site_info.address, function() {\n            _this.wrapper.notifications.add(\"done-updated\", \"done\", \"Site updated!\", 5000);\n            return _this.tag.find(\"#button-update\").removeClass(\"loading\");\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-pause\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.tag.find(\"#button-pause\").addClass(\"hidden\");\n          _this.wrapper.ws.cmd(\"sitePause\", _this.wrapper.site_info.address);\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-resume\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.tag.find(\"#button-resume\").addClass(\"hidden\");\n          _this.wrapper.ws.cmd(\"siteResume\", _this.wrapper.site_info.address);\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-delete\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.handleSiteDeleteClick();\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#checkbox-owned\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          var owned;\n          owned = _this.tag.find(\"#checkbox-owned\").is(\":checked\");\n          return _this.wrapper.ws.cmd(\"siteSetOwned\", [owned], function(res_set_owned) {\n            _this.log(\"Owned\", owned);\n            if (owned) {\n              return _this.wrapper.ws.cmd(\"siteRecoverPrivatekey\", [], function(res_recover) {\n                if (res_recover === \"ok\") {\n                  return _this.wrapper.notifications.add(\"recover\", \"done\", \"Private key recovered from master seed\", 5000);\n                } else {\n                  return _this.log(\"Unable to recover private key: \" + res_recover.error);\n                }\n              });\n            }\n          });\n        };\n      })(this));\n      this.tag.find(\"#checkbox-autodownloadoptional\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          return _this.wrapper.ws.cmd(\"siteSetAutodownloadoptional\", [_this.tag.find(\"#checkbox-autodownloadoptional\").is(\":checked\")]);\n        };\n      })(this));\n      this.tag.find(\"#button-identity\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"certSelect\");\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#button-settings\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"fileGet\", \"content.json\", function(res) {\n            var data, json_raw;\n            data = JSON.parse(res);\n            data[\"title\"] = $(\"#settings-title\").val();\n            data[\"description\"] = $(\"#settings-description\").val();\n            json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\\t')));\n            return _this.wrapper.ws.cmd(\"fileWrite\", [\"content.json\", btoa(json_raw), true], function(res) {\n              if (res !== \"ok\") {\n                return _this.wrapper.notifications.add(\"file-write\", \"error\", \"File write error: \" + res);\n              } else {\n                _this.wrapper.notifications.add(\"file-write\", \"done\", \"Site settings saved!\", 5000);\n                if (_this.wrapper.site_info.privatekey) {\n                  _this.wrapper.ws.cmd(\"siteSign\", {\n                    privatekey: \"stored\",\n                    inner_path: \"content.json\",\n                    update_changed_files: true\n                  });\n                }\n                return _this.updateHtmlTag();\n              }\n            });\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#link-directory\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          _this.wrapper.ws.cmd(\"serverShowdirectory\", [\"site\", _this.wrapper.site_info.address]);\n          return false;\n        };\n      })(this));\n      this.tag.find(\"#link-copypeers\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function(e) {\n          var copy_text, handler;\n          copy_text = e.currentTarget.href;\n          handler = function(e) {\n            e.clipboardData.setData('text/plain', copy_text);\n            e.preventDefault();\n            _this.wrapper.notifications.add(\"copy\", \"done\", \"Site address with peers copied to your clipboard\", 5000);\n            return document.removeEventListener('copy', handler, true);\n          };\n          document.addEventListener('copy', handler, true);\n          document.execCommand('copy');\n          return false;\n        };\n      })(this));\n      $(document).on(\"click touchend\", (function(_this) {\n        return function() {\n          var ref, ref1;\n          if ((ref = _this.tag) != null) {\n            ref.find(\"#button-sign-publish-menu\").removeClass(\"visible\");\n          }\n          return (ref1 = _this.tag) != null ? ref1.find(\".contents + .flex\").removeClass(\"sign-publish-flex\") : void 0;\n        };\n      })(this));\n      this.tag.find(\".contents-content\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function(e) {\n          $(\"#input-contents\").val(e.currentTarget.innerText);\n          return false;\n        };\n      })(this));\n      menu = new Menu(this.tag.find(\"#menu-sign-publish\"));\n      menu.elem.css(\"margin-top\", \"-130px\");\n      menu.addItem(\"Sign\", (function(_this) {\n        return function() {\n          var inner_path;\n          inner_path = _this.tag.find(\"#input-contents\").val();\n          _this.wrapper.ws.cmd(\"fileRules\", {\n            inner_path: inner_path\n          }, function(rules) {\n            var ref;\n            if (ref = _this.wrapper.site_info.auth_address, indexOf.call(rules.signers, ref) >= 0) {\n              return _this.sign(inner_path);\n            } else if (_this.wrapper.site_info.privatekey) {\n              return _this.sign(inner_path, \"stored\");\n            } else {\n              return _this.wrapper.displayPrompt(\"Enter your private key:\", \"password\", \"Sign\", \"\", function(privatekey) {\n                return _this.sign(inner_path, privatekey);\n              });\n            }\n          });\n          _this.tag.find(\".contents + .flex\").removeClass(\"active\");\n          return menu.hide();\n        };\n      })(this));\n      menu.addItem(\"Publish\", (function(_this) {\n        return function() {\n          var inner_path;\n          inner_path = _this.tag.find(\"#input-contents\").val();\n          _this.wrapper.ws.cmd(\"sitePublish\", {\n            \"inner_path\": inner_path,\n            \"sign\": false\n          });\n          _this.tag.find(\".contents + .flex\").removeClass(\"active\");\n          return menu.hide();\n        };\n      })(this));\n      this.tag.find(\"#menu-sign-publish\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          if (window.visible_menu === menu) {\n            _this.tag.find(\".contents + .flex\").removeClass(\"active\");\n            menu.hide();\n          } else {\n            _this.tag.find(\".contents + .flex\").addClass(\"active\");\n            _this.tag.find(\".content-wrapper\").prop(\"scrollTop\", 10000);\n            menu.show();\n          }\n          return false;\n        };\n      })(this));\n      $(\"body\").on(\"click\", (function(_this) {\n        return function() {\n          if (_this.tag) {\n            return _this.tag.find(\".contents + .flex\").removeClass(\"active\");\n          }\n        };\n      })(this));\n      this.tag.find(\"#button-sign-publish\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function() {\n          var inner_path;\n          inner_path = _this.tag.find(\"#input-contents\").val();\n          _this.wrapper.ws.cmd(\"fileRules\", {\n            inner_path: inner_path\n          }, function(rules) {\n            var ref;\n            if (ref = _this.wrapper.site_info.auth_address, indexOf.call(rules.signers, ref) >= 0) {\n              return _this.publish(inner_path, null);\n            } else if (_this.wrapper.site_info.privatekey) {\n              return _this.publish(inner_path, \"stored\");\n            } else {\n              return _this.wrapper.displayPrompt(\"Enter your private key:\", \"password\", \"Sign\", \"\", function(privatekey) {\n                return _this.publish(inner_path, privatekey);\n              });\n            }\n          });\n          return false;\n        };\n      })(this));\n      this.tag.find(\".close\").off(\"click touchend\").on(\"click touchend\", (function(_this) {\n        return function(e) {\n          _this.close();\n          return false;\n        };\n      })(this));\n      return this.loadGlobe();\n    };\n\n    Sidebar.prototype.close = function() {\n      this.move_lock = \"x\";\n      this.startDrag();\n      return this.stopDrag();\n    };\n\n    Sidebar.prototype.onClosed = function() {\n      $(window).off(\"resize\");\n      $(window).on(\"resize\", this.resized);\n      $(document.body).css(\"transition\", \"0.6s ease-in-out\").removeClass(\"body-sidebar\").on(transitionEnd, (function(_this) {\n        return function(e) {\n          if (e.target === document.body && !$(document.body).hasClass(\"body-sidebar\") && !$(document.body).hasClass(\"body-console\")) {\n            $(document.body).css(\"height\", \"auto\").css(\"perspective\", \"\").css(\"will-change\", \"\").css(\"transition\", \"\").off(transitionEnd);\n            return _this.unloadGlobe();\n          }\n        };\n      })(this));\n      return this.wrapper.setSiteInfo = this.original_set_site_info;\n    };\n\n    Sidebar.prototype.loadGlobe = function() {\n      if (this.tag.find(\".globe\").hasClass(\"loading\")) {\n        return setTimeout(((function(_this) {\n          return function() {\n            var script_tag;\n            if (typeof DAT === \"undefined\") {\n              script_tag = $(\"<script>\");\n              script_tag.attr(\"nonce\", _this.wrapper.script_nonce);\n              script_tag.attr(\"src\", \"/uimedia/globe/all.js\");\n              script_tag.on(\"load\", _this.displayGlobe);\n              return document.head.appendChild(script_tag[0]);\n            } else {\n              return _this.displayGlobe();\n            }\n          };\n        })(this)), 600);\n      }\n    };\n\n    Sidebar.prototype.displayGlobe = function() {\n      var img;\n      img = new Image();\n      img.src = \"/uimedia/globe/world.jpg\";\n      return img.onload = (function(_this) {\n        return function() {\n          return _this.wrapper.ws.cmd(\"sidebarGetPeers\", [], function(globe_data) {\n            var e, ref, ref1, ref2;\n            if (_this.globe) {\n              _this.globe.scene.remove(_this.globe.points);\n              _this.globe.addData(globe_data, {\n                format: 'magnitude',\n                name: \"hello\",\n                animated: false\n              });\n              _this.globe.createPoints();\n              return (ref = _this.tag) != null ? ref.find(\".globe\").removeClass(\"loading\") : void 0;\n            } else if (typeof DAT !== \"undefined\") {\n              try {\n                _this.globe = new DAT.Globe(_this.tag.find(\".globe\")[0], {\n                  \"imgDir\": \"/uimedia/globe/\"\n                });\n                _this.globe.addData(globe_data, {\n                  format: 'magnitude',\n                  name: \"hello\"\n                });\n                _this.globe.createPoints();\n                _this.globe.animate();\n              } catch (error) {\n                e = error;\n                console.log(\"WebGL error\", e);\n                if ((ref1 = _this.tag) != null) {\n                  ref1.find(\".globe\").addClass(\"error\").text(\"WebGL not supported\");\n                }\n              }\n              return (ref2 = _this.tag) != null ? ref2.find(\".globe\").removeClass(\"loading\") : void 0;\n            }\n          });\n        };\n      })(this);\n    };\n\n    Sidebar.prototype.unloadGlobe = function() {\n      if (!this.globe) {\n        return false;\n      }\n      this.globe.unload();\n      return this.globe = null;\n    };\n\n    return Sidebar;\n\n  })(Class);\n\n  wrapper = window.wrapper;\n\n  setTimeout((function() {\n    return window.sidebar = new Sidebar(wrapper);\n  }), 500);\n\n  window.transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend';\n\n}).call(this);\n\n\n/* ---- morphdom.js ---- */\n\n\n(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.morphdom = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\nvar specialElHandlers = {\n    /**\n     * Needed for IE. Apparently IE doesn't think\n     * that \"selected\" is an attribute when reading\n     * over the attributes using selectEl.attributes\n     */\n    OPTION: function(fromEl, toEl) {\n        if ((fromEl.selected = toEl.selected)) {\n            fromEl.setAttribute('selected', '');\n        } else {\n            fromEl.removeAttribute('selected', '');\n        }\n    },\n    /**\n     * The \"value\" attribute is special for the <input> element\n     * since it sets the initial value. Changing the \"value\"\n     * attribute without changing the \"value\" property will have\n     * no effect since it is only used to the set the initial value.\n     * Similar for the \"checked\" attribute.\n     */\n    /*INPUT: function(fromEl, toEl) {\n        fromEl.checked = toEl.checked;\n        fromEl.value = toEl.value;\n\n        if (!toEl.hasAttribute('checked')) {\n            fromEl.removeAttribute('checked');\n        }\n\n        if (!toEl.hasAttribute('value')) {\n            fromEl.removeAttribute('value');\n        }\n    }*/\n};\n\nfunction noop() {}\n\n/**\n * Loop over all of the attributes on the target node and make sure the\n * original DOM node has the same attributes. If an attribute\n * found on the original node is not on the new node then remove it from\n * the original node\n * @param  {HTMLElement} fromNode\n * @param  {HTMLElement} toNode\n */\nfunction morphAttrs(fromNode, toNode) {\n    var attrs = toNode.attributes;\n    var i;\n    var attr;\n    var attrName;\n    var attrValue;\n    var foundAttrs = {};\n\n    for (i=attrs.length-1; i>=0; i--) {\n        attr = attrs[i];\n        if (attr.specified !== false) {\n            attrName = attr.name;\n            attrValue = attr.value;\n            foundAttrs[attrName] = true;\n\n            if (fromNode.getAttribute(attrName) !== attrValue) {\n                fromNode.setAttribute(attrName, attrValue);\n            }\n        }\n    }\n\n    // Delete any extra attributes found on the original DOM element that weren't\n    // found on the target element.\n    attrs = fromNode.attributes;\n\n    for (i=attrs.length-1; i>=0; i--) {\n        attr = attrs[i];\n        if (attr.specified !== false) {\n            attrName = attr.name;\n            if (!foundAttrs.hasOwnProperty(attrName)) {\n                fromNode.removeAttribute(attrName);\n            }\n        }\n    }\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(from, to) {\n    var curChild = from.firstChild;\n    while(curChild) {\n        var nextChild = curChild.nextSibling;\n        to.appendChild(curChild);\n        curChild = nextChild;\n    }\n    return to;\n}\n\nfunction morphdom(fromNode, toNode, options) {\n    if (!options) {\n        options = {};\n    }\n\n    if (typeof toNode === 'string') {\n        var newBodyEl = document.createElement('body');\n        newBodyEl.innerHTML = toNode;\n        toNode = newBodyEl.childNodes[0];\n    }\n\n    var savedEls = {}; // Used to save off DOM elements with IDs\n    var unmatchedEls = {};\n    var onNodeDiscarded = options.onNodeDiscarded || noop;\n    var onBeforeMorphEl = options.onBeforeMorphEl || noop;\n    var onBeforeMorphElChildren = options.onBeforeMorphElChildren || noop;\n\n    function removeNodeHelper(node, nestedInSavedEl) {\n        var id = node.id;\n        // If the node has an ID then save it off since we will want\n        // to reuse it in case the target DOM tree has a DOM element\n        // with the same ID\n        if (id) {\n            savedEls[id] = node;\n        } else if (!nestedInSavedEl) {\n            // If we are not nested in a saved element then we know that this node has been\n            // completely discarded and will not exist in the final DOM.\n            onNodeDiscarded(node);\n        }\n\n        if (node.nodeType === 1) {\n            var curChild = node.firstChild;\n            while(curChild) {\n                removeNodeHelper(curChild, nestedInSavedEl || id);\n                curChild = curChild.nextSibling;\n            }\n        }\n    }\n\n    function walkDiscardedChildNodes(node) {\n        if (node.nodeType === 1) {\n            var curChild = node.firstChild;\n            while(curChild) {\n\n\n                if (!curChild.id) {\n                    // We only want to handle nodes that don't have an ID to avoid double\n                    // walking the same saved element.\n\n                    onNodeDiscarded(curChild);\n\n                    // Walk recursively\n                    walkDiscardedChildNodes(curChild);\n                }\n\n                curChild = curChild.nextSibling;\n            }\n        }\n    }\n\n    function removeNode(node, parentNode, alreadyVisited) {\n        parentNode.removeChild(node);\n\n        if (alreadyVisited) {\n            if (!node.id) {\n                onNodeDiscarded(node);\n                walkDiscardedChildNodes(node);\n            }\n        } else {\n            removeNodeHelper(node);\n        }\n    }\n\n    function morphEl(fromNode, toNode, alreadyVisited) {\n        if (toNode.id) {\n            // If an element with an ID is being morphed then it is will be in the final\n            // DOM so clear it out of the saved elements collection\n            delete savedEls[toNode.id];\n        }\n\n        if (onBeforeMorphEl(fromNode, toNode) === false) {\n            return;\n        }\n\n        morphAttrs(fromNode, toNode);\n\n        if (onBeforeMorphElChildren(fromNode, toNode) === false) {\n            return;\n        }\n\n        var curToNodeChild = toNode.firstChild;\n        var curFromNodeChild = fromNode.firstChild;\n        var curToNodeId;\n\n        var fromNextSibling;\n        var toNextSibling;\n        var savedEl;\n        var unmatchedEl;\n\n        outer: while(curToNodeChild) {\n            toNextSibling = curToNodeChild.nextSibling;\n            curToNodeId = curToNodeChild.id;\n\n            while(curFromNodeChild) {\n                var curFromNodeId = curFromNodeChild.id;\n                fromNextSibling = curFromNodeChild.nextSibling;\n\n                if (!alreadyVisited) {\n                    if (curFromNodeId && (unmatchedEl = unmatchedEls[curFromNodeId])) {\n                        unmatchedEl.parentNode.replaceChild(curFromNodeChild, unmatchedEl);\n                        morphEl(curFromNodeChild, unmatchedEl, alreadyVisited);\n                        curFromNodeChild = fromNextSibling;\n                        continue;\n                    }\n                }\n\n                var curFromNodeType = curFromNodeChild.nodeType;\n\n                if (curFromNodeType === curToNodeChild.nodeType) {\n                    var isCompatible = false;\n\n                    if (curFromNodeType === 1) { // Both nodes being compared are Element nodes\n                        if (curFromNodeChild.tagName === curToNodeChild.tagName) {\n                            // We have compatible DOM elements\n                            if (curFromNodeId || curToNodeId) {\n                                // If either DOM element has an ID then we handle\n                                // those differently since we want to match up\n                                // by ID\n                                if (curToNodeId === curFromNodeId) {\n                                    isCompatible = true;\n                                }\n                            } else {\n                                isCompatible = true;\n                            }\n                        }\n\n                        if (isCompatible) {\n                            // We found compatible DOM elements so add a\n                            // task to morph the compatible DOM elements\n                            morphEl(curFromNodeChild, curToNodeChild, alreadyVisited);\n                        }\n                    } else if (curFromNodeType === 3) { // Both nodes being compared are Text nodes\n                        isCompatible = true;\n                        curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n                    }\n\n                    if (isCompatible) {\n                        curToNodeChild = toNextSibling;\n                        curFromNodeChild = fromNextSibling;\n                        continue outer;\n                    }\n                }\n\n                // No compatible match so remove the old node from the DOM\n                removeNode(curFromNodeChild, fromNode, alreadyVisited);\n\n                curFromNodeChild = fromNextSibling;\n            }\n\n            if (curToNodeId) {\n                if ((savedEl = savedEls[curToNodeId])) {\n                    morphEl(savedEl, curToNodeChild, true);\n                    curToNodeChild = savedEl; // We want to append the saved element instead\n                } else {\n                    // The current DOM element in the target tree has an ID\n                    // but we did not find a match in any of the corresponding\n                    // siblings. We just put the target element in the old DOM tree\n                    // but if we later find an element in the old DOM tree that has\n                    // a matching ID then we will replace the target element\n                    // with the corresponding old element and morph the old element\n                    unmatchedEls[curToNodeId] = curToNodeChild;\n                }\n            }\n\n            // If we got this far then we did not find a candidate match for our \"to node\"\n            // and we exhausted all of the children \"from\" nodes. Therefore, we will just\n            // append the current \"to node\" to the end\n            fromNode.appendChild(curToNodeChild);\n\n            curToNodeChild = toNextSibling;\n            curFromNodeChild = fromNextSibling;\n        }\n\n        // We have processed all of the \"to nodes\". If curFromNodeChild is non-null then\n        // we still have some from nodes left over that need to be removed\n        while(curFromNodeChild) {\n            fromNextSibling = curFromNodeChild.nextSibling;\n            removeNode(curFromNodeChild, fromNode, alreadyVisited);\n            curFromNodeChild = fromNextSibling;\n        }\n\n        var specialElHandler = specialElHandlers[fromNode.tagName];\n        if (specialElHandler) {\n            specialElHandler(fromNode, toNode);\n        }\n    }\n\n    var morphedNode = fromNode;\n    var morphedNodeType = morphedNode.nodeType;\n    var toNodeType = toNode.nodeType;\n\n    // Handle the case where we are given two DOM nodes that are not\n    // compatible (e.g. <div> --> <span> or <div> --> TEXT)\n    if (morphedNodeType === 1) {\n        if (toNodeType === 1) {\n            if (morphedNode.tagName !== toNode.tagName) {\n                onNodeDiscarded(fromNode);\n                morphedNode = moveChildren(morphedNode, document.createElement(toNode.tagName));\n            }\n        } else {\n            // Going from an element node to a text node\n            return toNode;\n        }\n    } else if (morphedNodeType === 3) { // Text node\n        if (toNodeType === 3) {\n            morphedNode.nodeValue = toNode.nodeValue;\n            return morphedNode;\n        } else {\n            onNodeDiscarded(fromNode);\n            // Text node to something else\n            return toNode;\n        }\n    }\n\n    morphEl(morphedNode, toNode, false);\n\n    // Fire the \"onNodeDiscarded\" event for any saved elements\n    // that never found a new home in the morphed DOM\n    for (var savedElId in savedEls) {\n        if (savedEls.hasOwnProperty(savedElId)) {\n            var savedEl = savedEls[savedElId];\n            onNodeDiscarded(savedEl);\n            walkDiscardedChildNodes(savedEl);\n        }\n    }\n\n    if (morphedNode !== fromNode && fromNode.parentNode) {\n        fromNode.parentNode.replaceChild(morphedNode, fromNode);\n    }\n\n    return morphedNode;\n}\n\nmodule.exports = morphdom;\n},{}]},{},[1])(1)\n});"
  },
  {
    "path": "plugins/Sidebar/media/morphdom.js",
    "content": "(function(f){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=f()}else if(typeof define===\"function\"&&define.amd){define([],f)}else{var g;if(typeof window!==\"undefined\"){g=window}else if(typeof global!==\"undefined\"){g=global}else if(typeof self!==\"undefined\"){g=self}else{g=this}g.morphdom = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){\nvar specialElHandlers = {\n    /**\n     * Needed for IE. Apparently IE doesn't think\n     * that \"selected\" is an attribute when reading\n     * over the attributes using selectEl.attributes\n     */\n    OPTION: function(fromEl, toEl) {\n        if ((fromEl.selected = toEl.selected)) {\n            fromEl.setAttribute('selected', '');\n        } else {\n            fromEl.removeAttribute('selected', '');\n        }\n    },\n    /**\n     * The \"value\" attribute is special for the <input> element\n     * since it sets the initial value. Changing the \"value\"\n     * attribute without changing the \"value\" property will have\n     * no effect since it is only used to the set the initial value.\n     * Similar for the \"checked\" attribute.\n     */\n    /*INPUT: function(fromEl, toEl) {\n        fromEl.checked = toEl.checked;\n        fromEl.value = toEl.value;\n\n        if (!toEl.hasAttribute('checked')) {\n            fromEl.removeAttribute('checked');\n        }\n\n        if (!toEl.hasAttribute('value')) {\n            fromEl.removeAttribute('value');\n        }\n    }*/\n};\n\nfunction noop() {}\n\n/**\n * Loop over all of the attributes on the target node and make sure the\n * original DOM node has the same attributes. If an attribute\n * found on the original node is not on the new node then remove it from\n * the original node\n * @param  {HTMLElement} fromNode\n * @param  {HTMLElement} toNode\n */\nfunction morphAttrs(fromNode, toNode) {\n    var attrs = toNode.attributes;\n    var i;\n    var attr;\n    var attrName;\n    var attrValue;\n    var foundAttrs = {};\n\n    for (i=attrs.length-1; i>=0; i--) {\n        attr = attrs[i];\n        if (attr.specified !== false) {\n            attrName = attr.name;\n            attrValue = attr.value;\n            foundAttrs[attrName] = true;\n\n            if (fromNode.getAttribute(attrName) !== attrValue) {\n                fromNode.setAttribute(attrName, attrValue);\n            }\n        }\n    }\n\n    // Delete any extra attributes found on the original DOM element that weren't\n    // found on the target element.\n    attrs = fromNode.attributes;\n\n    for (i=attrs.length-1; i>=0; i--) {\n        attr = attrs[i];\n        if (attr.specified !== false) {\n            attrName = attr.name;\n            if (!foundAttrs.hasOwnProperty(attrName)) {\n                fromNode.removeAttribute(attrName);\n            }\n        }\n    }\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(from, to) {\n    var curChild = from.firstChild;\n    while(curChild) {\n        var nextChild = curChild.nextSibling;\n        to.appendChild(curChild);\n        curChild = nextChild;\n    }\n    return to;\n}\n\nfunction morphdom(fromNode, toNode, options) {\n    if (!options) {\n        options = {};\n    }\n\n    if (typeof toNode === 'string') {\n        var newBodyEl = document.createElement('body');\n        newBodyEl.innerHTML = toNode;\n        toNode = newBodyEl.childNodes[0];\n    }\n\n    var savedEls = {}; // Used to save off DOM elements with IDs\n    var unmatchedEls = {};\n    var onNodeDiscarded = options.onNodeDiscarded || noop;\n    var onBeforeMorphEl = options.onBeforeMorphEl || noop;\n    var onBeforeMorphElChildren = options.onBeforeMorphElChildren || noop;\n\n    function removeNodeHelper(node, nestedInSavedEl) {\n        var id = node.id;\n        // If the node has an ID then save it off since we will want\n        // to reuse it in case the target DOM tree has a DOM element\n        // with the same ID\n        if (id) {\n            savedEls[id] = node;\n        } else if (!nestedInSavedEl) {\n            // If we are not nested in a saved element then we know that this node has been\n            // completely discarded and will not exist in the final DOM.\n            onNodeDiscarded(node);\n        }\n\n        if (node.nodeType === 1) {\n            var curChild = node.firstChild;\n            while(curChild) {\n                removeNodeHelper(curChild, nestedInSavedEl || id);\n                curChild = curChild.nextSibling;\n            }\n        }\n    }\n\n    function walkDiscardedChildNodes(node) {\n        if (node.nodeType === 1) {\n            var curChild = node.firstChild;\n            while(curChild) {\n\n\n                if (!curChild.id) {\n                    // We only want to handle nodes that don't have an ID to avoid double\n                    // walking the same saved element.\n\n                    onNodeDiscarded(curChild);\n\n                    // Walk recursively\n                    walkDiscardedChildNodes(curChild);\n                }\n\n                curChild = curChild.nextSibling;\n            }\n        }\n    }\n\n    function removeNode(node, parentNode, alreadyVisited) {\n        parentNode.removeChild(node);\n\n        if (alreadyVisited) {\n            if (!node.id) {\n                onNodeDiscarded(node);\n                walkDiscardedChildNodes(node);\n            }\n        } else {\n            removeNodeHelper(node);\n        }\n    }\n\n    function morphEl(fromNode, toNode, alreadyVisited) {\n        if (toNode.id) {\n            // If an element with an ID is being morphed then it is will be in the final\n            // DOM so clear it out of the saved elements collection\n            delete savedEls[toNode.id];\n        }\n\n        if (onBeforeMorphEl(fromNode, toNode) === false) {\n            return;\n        }\n\n        morphAttrs(fromNode, toNode);\n\n        if (onBeforeMorphElChildren(fromNode, toNode) === false) {\n            return;\n        }\n\n        var curToNodeChild = toNode.firstChild;\n        var curFromNodeChild = fromNode.firstChild;\n        var curToNodeId;\n\n        var fromNextSibling;\n        var toNextSibling;\n        var savedEl;\n        var unmatchedEl;\n\n        outer: while(curToNodeChild) {\n            toNextSibling = curToNodeChild.nextSibling;\n            curToNodeId = curToNodeChild.id;\n\n            while(curFromNodeChild) {\n                var curFromNodeId = curFromNodeChild.id;\n                fromNextSibling = curFromNodeChild.nextSibling;\n\n                if (!alreadyVisited) {\n                    if (curFromNodeId && (unmatchedEl = unmatchedEls[curFromNodeId])) {\n                        unmatchedEl.parentNode.replaceChild(curFromNodeChild, unmatchedEl);\n                        morphEl(curFromNodeChild, unmatchedEl, alreadyVisited);\n                        curFromNodeChild = fromNextSibling;\n                        continue;\n                    }\n                }\n\n                var curFromNodeType = curFromNodeChild.nodeType;\n\n                if (curFromNodeType === curToNodeChild.nodeType) {\n                    var isCompatible = false;\n\n                    if (curFromNodeType === 1) { // Both nodes being compared are Element nodes\n                        if (curFromNodeChild.tagName === curToNodeChild.tagName) {\n                            // We have compatible DOM elements\n                            if (curFromNodeId || curToNodeId) {\n                                // If either DOM element has an ID then we handle\n                                // those differently since we want to match up\n                                // by ID\n                                if (curToNodeId === curFromNodeId) {\n                                    isCompatible = true;\n                                }\n                            } else {\n                                isCompatible = true;\n                            }\n                        }\n\n                        if (isCompatible) {\n                            // We found compatible DOM elements so add a\n                            // task to morph the compatible DOM elements\n                            morphEl(curFromNodeChild, curToNodeChild, alreadyVisited);\n                        }\n                    } else if (curFromNodeType === 3) { // Both nodes being compared are Text nodes\n                        isCompatible = true;\n                        curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n                    }\n\n                    if (isCompatible) {\n                        curToNodeChild = toNextSibling;\n                        curFromNodeChild = fromNextSibling;\n                        continue outer;\n                    }\n                }\n\n                // No compatible match so remove the old node from the DOM\n                removeNode(curFromNodeChild, fromNode, alreadyVisited);\n\n                curFromNodeChild = fromNextSibling;\n            }\n\n            if (curToNodeId) {\n                if ((savedEl = savedEls[curToNodeId])) {\n                    morphEl(savedEl, curToNodeChild, true);\n                    curToNodeChild = savedEl; // We want to append the saved element instead\n                } else {\n                    // The current DOM element in the target tree has an ID\n                    // but we did not find a match in any of the corresponding\n                    // siblings. We just put the target element in the old DOM tree\n                    // but if we later find an element in the old DOM tree that has\n                    // a matching ID then we will replace the target element\n                    // with the corresponding old element and morph the old element\n                    unmatchedEls[curToNodeId] = curToNodeChild;\n                }\n            }\n\n            // If we got this far then we did not find a candidate match for our \"to node\"\n            // and we exhausted all of the children \"from\" nodes. Therefore, we will just\n            // append the current \"to node\" to the end\n            fromNode.appendChild(curToNodeChild);\n\n            curToNodeChild = toNextSibling;\n            curFromNodeChild = fromNextSibling;\n        }\n\n        // We have processed all of the \"to nodes\". If curFromNodeChild is non-null then\n        // we still have some from nodes left over that need to be removed\n        while(curFromNodeChild) {\n            fromNextSibling = curFromNodeChild.nextSibling;\n            removeNode(curFromNodeChild, fromNode, alreadyVisited);\n            curFromNodeChild = fromNextSibling;\n        }\n\n        var specialElHandler = specialElHandlers[fromNode.tagName];\n        if (specialElHandler) {\n            specialElHandler(fromNode, toNode);\n        }\n    }\n\n    var morphedNode = fromNode;\n    var morphedNodeType = morphedNode.nodeType;\n    var toNodeType = toNode.nodeType;\n\n    // Handle the case where we are given two DOM nodes that are not\n    // compatible (e.g. <div> --> <span> or <div> --> TEXT)\n    if (morphedNodeType === 1) {\n        if (toNodeType === 1) {\n            if (morphedNode.tagName !== toNode.tagName) {\n                onNodeDiscarded(fromNode);\n                morphedNode = moveChildren(morphedNode, document.createElement(toNode.tagName));\n            }\n        } else {\n            // Going from an element node to a text node\n            return toNode;\n        }\n    } else if (morphedNodeType === 3) { // Text node\n        if (toNodeType === 3) {\n            morphedNode.nodeValue = toNode.nodeValue;\n            return morphedNode;\n        } else {\n            onNodeDiscarded(fromNode);\n            // Text node to something else\n            return toNode;\n        }\n    }\n\n    morphEl(morphedNode, toNode, false);\n\n    // Fire the \"onNodeDiscarded\" event for any saved elements\n    // that never found a new home in the morphed DOM\n    for (var savedElId in savedEls) {\n        if (savedEls.hasOwnProperty(savedElId)) {\n            var savedEl = savedEls[savedElId];\n            onNodeDiscarded(savedEl);\n            walkDiscardedChildNodes(savedEl);\n        }\n    }\n\n    if (morphedNode !== fromNode && fromNode.parentNode) {\n        fromNode.parentNode.replaceChild(morphedNode, fromNode);\n    }\n\n    return morphedNode;\n}\n\nmodule.exports = morphdom;\n},{}]},{},[1])(1)\n});"
  },
  {
    "path": "plugins/Sidebar/media_globe/Detector.js",
    "content": "/**\n * @author alteredq / http://alteredqualia.com/\n * @author mr.doob / http://mrdoob.com/\n */\n\nDetector = {\n\n  canvas : !! window.CanvasRenderingContext2D,\n  webgl : ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),\n  workers : !! window.Worker,\n  fileapi : window.File && window.FileReader && window.FileList && window.Blob,\n\n  getWebGLErrorMessage : function () {\n\n    var domElement = document.createElement( 'div' );\n\n    domElement.style.fontFamily = 'monospace';\n    domElement.style.fontSize = '13px';\n    domElement.style.textAlign = 'center';\n    domElement.style.background = '#eee';\n    domElement.style.color = '#000';\n    domElement.style.padding = '1em';\n    domElement.style.width = '475px';\n    domElement.style.margin = '5em auto 0';\n\n    if ( ! this.webgl ) {\n\n      domElement.innerHTML = window.WebGLRenderingContext ? [\n        'Sorry, your graphics card doesn\\'t support <a href=\"http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation\">WebGL</a>'\n      ].join( '\\n' ) : [\n        'Sorry, your browser doesn\\'t support <a href=\"http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation\">WebGL</a><br/>',\n        'Please try with',\n        '<a href=\"http://www.google.com/chrome\">Chrome</a>, ',\n        '<a href=\"http://www.mozilla.com/en-US/firefox/new/\">Firefox 4</a> or',\n        '<a href=\"http://nightly.webkit.org/\">Webkit Nightly (Mac)</a>'\n      ].join( '\\n' );\n\n    }\n\n    return domElement;\n\n  },\n\n  addGetWebGLMessage : function ( parameters ) {\n\n    var parent, id, domElement;\n\n    parameters = parameters || {};\n\n    parent = parameters.parent !== undefined ? parameters.parent : document.body;\n    id = parameters.id !== undefined ? parameters.id : 'oldie';\n\n    domElement = Detector.getWebGLErrorMessage();\n    domElement.id = id;\n\n    parent.appendChild( domElement );\n\n  }\n\n};\n"
  },
  {
    "path": "plugins/Sidebar/media_globe/Tween.js",
    "content": "// Tween.js - http://github.com/sole/tween.js\nvar TWEEN=TWEEN||function(){var a,e,c,d,f=[];return{start:function(g){c=setInterval(this.update,1E3/(g||60))},stop:function(){clearInterval(c)},add:function(g){f.push(g)},remove:function(g){a=f.indexOf(g);a!==-1&&f.splice(a,1)},update:function(){a=0;e=f.length;for(d=(new Date).getTime();a<e;)if(f[a].update(d))a++;else{f.splice(a,1);e--}}}}();\nTWEEN.Tween=function(a){var e={},c={},d={},f=1E3,g=0,j=null,n=TWEEN.Easing.Linear.EaseNone,k=null,l=null,m=null;this.to=function(b,h){if(h!==null)f=h;for(var i in b)if(a[i]!==null)d[i]=b[i];return this};this.start=function(){TWEEN.add(this);j=(new Date).getTime()+g;for(var b in d)if(a[b]!==null){e[b]=a[b];c[b]=d[b]-a[b]}return this};this.stop=function(){TWEEN.remove(this);return this};this.delay=function(b){g=b;return this};this.easing=function(b){n=b;return this};this.chain=function(b){k=b};this.onUpdate=\nfunction(b){l=b;return this};this.onComplete=function(b){m=b;return this};this.update=function(b){var h,i;if(b<j)return true;b=(b-j)/f;b=b>1?1:b;i=n(b);for(h in c)a[h]=e[h]+c[h]*i;l!==null&&l.call(a,i);if(b==1){m!==null&&m.call(a);k!==null&&k.start();return false}return true}};TWEEN.Easing={Linear:{},Quadratic:{},Cubic:{},Quartic:{},Quintic:{},Sinusoidal:{},Exponential:{},Circular:{},Elastic:{},Back:{},Bounce:{}};TWEEN.Easing.Linear.EaseNone=function(a){return a};\nTWEEN.Easing.Quadratic.EaseIn=function(a){return a*a};TWEEN.Easing.Quadratic.EaseOut=function(a){return-a*(a-2)};TWEEN.Easing.Quadratic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a;return-0.5*(--a*(a-2)-1)};TWEEN.Easing.Cubic.EaseIn=function(a){return a*a*a};TWEEN.Easing.Cubic.EaseOut=function(a){return--a*a*a+1};TWEEN.Easing.Cubic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a;return 0.5*((a-=2)*a*a+2)};TWEEN.Easing.Quartic.EaseIn=function(a){return a*a*a*a};\nTWEEN.Easing.Quartic.EaseOut=function(a){return-(--a*a*a*a-1)};TWEEN.Easing.Quartic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a;return-0.5*((a-=2)*a*a*a-2)};TWEEN.Easing.Quintic.EaseIn=function(a){return a*a*a*a*a};TWEEN.Easing.Quintic.EaseOut=function(a){return(a-=1)*a*a*a*a+1};TWEEN.Easing.Quintic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a*a;return 0.5*((a-=2)*a*a*a*a+2)};TWEEN.Easing.Sinusoidal.EaseIn=function(a){return-Math.cos(a*Math.PI/2)+1};\nTWEEN.Easing.Sinusoidal.EaseOut=function(a){return Math.sin(a*Math.PI/2)};TWEEN.Easing.Sinusoidal.EaseInOut=function(a){return-0.5*(Math.cos(Math.PI*a)-1)};TWEEN.Easing.Exponential.EaseIn=function(a){return a==0?0:Math.pow(2,10*(a-1))};TWEEN.Easing.Exponential.EaseOut=function(a){return a==1?1:-Math.pow(2,-10*a)+1};TWEEN.Easing.Exponential.EaseInOut=function(a){if(a==0)return 0;if(a==1)return 1;if((a*=2)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*(a-1))+2)};\nTWEEN.Easing.Circular.EaseIn=function(a){return-(Math.sqrt(1-a*a)-1)};TWEEN.Easing.Circular.EaseOut=function(a){return Math.sqrt(1- --a*a)};TWEEN.Easing.Circular.EaseInOut=function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)};TWEEN.Easing.Elastic.EaseIn=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);return-(c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/d))};\nTWEEN.Easing.Elastic.EaseOut=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);return c*Math.pow(2,-10*a)*Math.sin((a-e)*2*Math.PI/d)+1};\nTWEEN.Easing.Elastic.EaseInOut=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);if((a*=2)<1)return-0.5*c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/d);return c*Math.pow(2,-10*(a-=1))*Math.sin((a-e)*2*Math.PI/d)*0.5+1};TWEEN.Easing.Back.EaseIn=function(a){return a*a*(2.70158*a-1.70158)};TWEEN.Easing.Back.EaseOut=function(a){return(a-=1)*a*(2.70158*a+1.70158)+1};\nTWEEN.Easing.Back.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*(3.5949095*a-2.5949095);return 0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)};TWEEN.Easing.Bounce.EaseIn=function(a){return 1-TWEEN.Easing.Bounce.EaseOut(1-a)};TWEEN.Easing.Bounce.EaseOut=function(a){return(a/=1)<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375};\nTWEEN.Easing.Bounce.EaseInOut=function(a){if(a<0.5)return TWEEN.Easing.Bounce.EaseIn(a*2)*0.5;return TWEEN.Easing.Bounce.EaseOut(a*2-1)*0.5+0.5};\n"
  },
  {
    "path": "plugins/Sidebar/media_globe/all.js",
    "content": "\n\n/* ---- plugins/Sidebar/media_globe/Detector.js ---- */\n\n\n/**\n * @author alteredq / http://alteredqualia.com/\n * @author mr.doob / http://mrdoob.com/\n */\n\nDetector = {\n\n  canvas : !! window.CanvasRenderingContext2D,\n  webgl : ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),\n  workers : !! window.Worker,\n  fileapi : window.File && window.FileReader && window.FileList && window.Blob,\n\n  getWebGLErrorMessage : function () {\n\n    var domElement = document.createElement( 'div' );\n\n    domElement.style.fontFamily = 'monospace';\n    domElement.style.fontSize = '13px';\n    domElement.style.textAlign = 'center';\n    domElement.style.background = '#eee';\n    domElement.style.color = '#000';\n    domElement.style.padding = '1em';\n    domElement.style.width = '475px';\n    domElement.style.margin = '5em auto 0';\n\n    if ( ! this.webgl ) {\n\n      domElement.innerHTML = window.WebGLRenderingContext ? [\n        'Sorry, your graphics card doesn\\'t support <a href=\"http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation\">WebGL</a>'\n      ].join( '\\n' ) : [\n        'Sorry, your browser doesn\\'t support <a href=\"http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation\">WebGL</a><br/>',\n        'Please try with',\n        '<a href=\"http://www.google.com/chrome\">Chrome</a>, ',\n        '<a href=\"http://www.mozilla.com/en-US/firefox/new/\">Firefox 4</a> or',\n        '<a href=\"http://nightly.webkit.org/\">Webkit Nightly (Mac)</a>'\n      ].join( '\\n' );\n\n    }\n\n    return domElement;\n\n  },\n\n  addGetWebGLMessage : function ( parameters ) {\n\n    var parent, id, domElement;\n\n    parameters = parameters || {};\n\n    parent = parameters.parent !== undefined ? parameters.parent : document.body;\n    id = parameters.id !== undefined ? parameters.id : 'oldie';\n\n    domElement = Detector.getWebGLErrorMessage();\n    domElement.id = id;\n\n    parent.appendChild( domElement );\n\n  }\n\n};\n\n\n\n/* ---- plugins/Sidebar/media_globe/Tween.js ---- */\n\n\n// Tween.js - http://github.com/sole/tween.js\nvar TWEEN=TWEEN||function(){var a,e,c,d,f=[];return{start:function(g){c=setInterval(this.update,1E3/(g||60))},stop:function(){clearInterval(c)},add:function(g){f.push(g)},remove:function(g){a=f.indexOf(g);a!==-1&&f.splice(a,1)},update:function(){a=0;e=f.length;for(d=(new Date).getTime();a<e;)if(f[a].update(d))a++;else{f.splice(a,1);e--}}}}();\nTWEEN.Tween=function(a){var e={},c={},d={},f=1E3,g=0,j=null,n=TWEEN.Easing.Linear.EaseNone,k=null,l=null,m=null;this.to=function(b,h){if(h!==null)f=h;for(var i in b)if(a[i]!==null)d[i]=b[i];return this};this.start=function(){TWEEN.add(this);j=(new Date).getTime()+g;for(var b in d)if(a[b]!==null){e[b]=a[b];c[b]=d[b]-a[b]}return this};this.stop=function(){TWEEN.remove(this);return this};this.delay=function(b){g=b;return this};this.easing=function(b){n=b;return this};this.chain=function(b){k=b};this.onUpdate=\nfunction(b){l=b;return this};this.onComplete=function(b){m=b;return this};this.update=function(b){var h,i;if(b<j)return true;b=(b-j)/f;b=b>1?1:b;i=n(b);for(h in c)a[h]=e[h]+c[h]*i;l!==null&&l.call(a,i);if(b==1){m!==null&&m.call(a);k!==null&&k.start();return false}return true}};TWEEN.Easing={Linear:{},Quadratic:{},Cubic:{},Quartic:{},Quintic:{},Sinusoidal:{},Exponential:{},Circular:{},Elastic:{},Back:{},Bounce:{}};TWEEN.Easing.Linear.EaseNone=function(a){return a};\nTWEEN.Easing.Quadratic.EaseIn=function(a){return a*a};TWEEN.Easing.Quadratic.EaseOut=function(a){return-a*(a-2)};TWEEN.Easing.Quadratic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a;return-0.5*(--a*(a-2)-1)};TWEEN.Easing.Cubic.EaseIn=function(a){return a*a*a};TWEEN.Easing.Cubic.EaseOut=function(a){return--a*a*a+1};TWEEN.Easing.Cubic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a;return 0.5*((a-=2)*a*a+2)};TWEEN.Easing.Quartic.EaseIn=function(a){return a*a*a*a};\nTWEEN.Easing.Quartic.EaseOut=function(a){return-(--a*a*a*a-1)};TWEEN.Easing.Quartic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a;return-0.5*((a-=2)*a*a*a-2)};TWEEN.Easing.Quintic.EaseIn=function(a){return a*a*a*a*a};TWEEN.Easing.Quintic.EaseOut=function(a){return(a-=1)*a*a*a*a+1};TWEEN.Easing.Quintic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a*a;return 0.5*((a-=2)*a*a*a*a+2)};TWEEN.Easing.Sinusoidal.EaseIn=function(a){return-Math.cos(a*Math.PI/2)+1};\nTWEEN.Easing.Sinusoidal.EaseOut=function(a){return Math.sin(a*Math.PI/2)};TWEEN.Easing.Sinusoidal.EaseInOut=function(a){return-0.5*(Math.cos(Math.PI*a)-1)};TWEEN.Easing.Exponential.EaseIn=function(a){return a==0?0:Math.pow(2,10*(a-1))};TWEEN.Easing.Exponential.EaseOut=function(a){return a==1?1:-Math.pow(2,-10*a)+1};TWEEN.Easing.Exponential.EaseInOut=function(a){if(a==0)return 0;if(a==1)return 1;if((a*=2)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*(a-1))+2)};\nTWEEN.Easing.Circular.EaseIn=function(a){return-(Math.sqrt(1-a*a)-1)};TWEEN.Easing.Circular.EaseOut=function(a){return Math.sqrt(1- --a*a)};TWEEN.Easing.Circular.EaseInOut=function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)};TWEEN.Easing.Elastic.EaseIn=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);return-(c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/d))};\nTWEEN.Easing.Elastic.EaseOut=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);return c*Math.pow(2,-10*a)*Math.sin((a-e)*2*Math.PI/d)+1};\nTWEEN.Easing.Elastic.EaseInOut=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);if((a*=2)<1)return-0.5*c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/d);return c*Math.pow(2,-10*(a-=1))*Math.sin((a-e)*2*Math.PI/d)*0.5+1};TWEEN.Easing.Back.EaseIn=function(a){return a*a*(2.70158*a-1.70158)};TWEEN.Easing.Back.EaseOut=function(a){return(a-=1)*a*(2.70158*a+1.70158)+1};\nTWEEN.Easing.Back.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*(3.5949095*a-2.5949095);return 0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)};TWEEN.Easing.Bounce.EaseIn=function(a){return 1-TWEEN.Easing.Bounce.EaseOut(1-a)};TWEEN.Easing.Bounce.EaseOut=function(a){return(a/=1)<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375};\nTWEEN.Easing.Bounce.EaseInOut=function(a){if(a<0.5)return TWEEN.Easing.Bounce.EaseIn(a*2)*0.5;return TWEEN.Easing.Bounce.EaseOut(a*2-1)*0.5+0.5};\n\n\n\n/* ---- plugins/Sidebar/media_globe/globe.js ---- */\n\n\n/**\n * dat.globe Javascript WebGL Globe Toolkit\n * http://dataarts.github.com/dat.globe\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\nvar DAT = DAT || {};\n\nDAT.Globe = function(container, opts) {\n  opts = opts || {};\n\n  var colorFn = opts.colorFn || function(x) {\n    var c = new THREE.Color();\n    c.setHSL( ( 0.5 - (x * 2) ), Math.max(0.8, 1.0 - (x * 3)), 0.5 );\n    return c;\n  };\n  var imgDir = opts.imgDir || '/globe/';\n\n  var Shaders = {\n    'earth' : {\n      uniforms: {\n        'texture': { type: 't', value: null }\n      },\n      vertexShader: [\n        'varying vec3 vNormal;',\n        'varying vec2 vUv;',\n        'void main() {',\n          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',\n          'vNormal = normalize( normalMatrix * normal );',\n          'vUv = uv;',\n        '}'\n      ].join('\\n'),\n      fragmentShader: [\n        'uniform sampler2D texture;',\n        'varying vec3 vNormal;',\n        'varying vec2 vUv;',\n        'void main() {',\n          'vec3 diffuse = texture2D( texture, vUv ).xyz;',\n          'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );',\n          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );',\n          'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );',\n        '}'\n      ].join('\\n')\n    },\n    'atmosphere' : {\n      uniforms: {},\n      vertexShader: [\n        'varying vec3 vNormal;',\n        'void main() {',\n          'vNormal = normalize( normalMatrix * normal );',\n          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',\n        '}'\n      ].join('\\n'),\n      fragmentShader: [\n        'varying vec3 vNormal;',\n        'void main() {',\n          'float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 12.0 );',\n          'gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity;',\n        '}'\n      ].join('\\n')\n    }\n  };\n\n  var camera, scene, renderer, w, h;\n  var mesh, atmosphere, point, running;\n\n  var overRenderer;\n  var running = true;\n\n  var curZoomSpeed = 0;\n  var zoomSpeed = 50;\n\n  var mouse = { x: 0, y: 0 }, mouseOnDown = { x: 0, y: 0 };\n  var rotation = { x: 0, y: 0 },\n      target = { x: Math.PI*3/2, y: Math.PI / 6.0 },\n      targetOnDown = { x: 0, y: 0 };\n\n  var distance = 100000, distanceTarget = 100000;\n  var padding = 10;\n  var PI_HALF = Math.PI / 2;\n\n  function init() {\n\n    container.style.color = '#fff';\n    container.style.font = '13px/20px Arial, sans-serif';\n\n    var shader, uniforms, material;\n    w = container.offsetWidth || window.innerWidth;\n    h = container.offsetHeight || window.innerHeight;\n\n    camera = new THREE.PerspectiveCamera(30, w / h, 1, 10000);\n    camera.position.z = distance;\n\n    scene = new THREE.Scene();\n\n    var geometry = new THREE.SphereGeometry(200, 40, 30);\n\n    shader = Shaders['earth'];\n    uniforms = THREE.UniformsUtils.clone(shader.uniforms);\n\n    uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir+'world.jpg');\n\n    material = new THREE.ShaderMaterial({\n\n          uniforms: uniforms,\n          vertexShader: shader.vertexShader,\n          fragmentShader: shader.fragmentShader\n\n        });\n\n    mesh = new THREE.Mesh(geometry, material);\n    mesh.rotation.y = Math.PI;\n    scene.add(mesh);\n\n    shader = Shaders['atmosphere'];\n    uniforms = THREE.UniformsUtils.clone(shader.uniforms);\n\n    material = new THREE.ShaderMaterial({\n\n          uniforms: uniforms,\n          vertexShader: shader.vertexShader,\n          fragmentShader: shader.fragmentShader,\n          side: THREE.BackSide,\n          blending: THREE.AdditiveBlending,\n          transparent: true\n\n        });\n\n    mesh = new THREE.Mesh(geometry, material);\n    mesh.scale.set( 1.1, 1.1, 1.1 );\n    scene.add(mesh);\n\n    geometry = new THREE.BoxGeometry(2.75, 2.75, 1);\n    geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,-0.5));\n\n    point = new THREE.Mesh(geometry);\n\n    renderer = new THREE.WebGLRenderer({antialias: true});\n    renderer.setSize(w, h);\n    renderer.setClearColor( 0x212121, 1 );\n\n    renderer.domElement.style.position = 'relative';\n\n    container.appendChild(renderer.domElement);\n\n    container.addEventListener('mousedown', onMouseDown, false);\n\n    if ('onwheel' in document) {\n      container.addEventListener('wheel', onMouseWheel, false);\n    } else {\n      container.addEventListener('mousewheel', onMouseWheel, false);\n    }\n\n    document.addEventListener('keydown', onDocumentKeyDown, false);\n\n    window.addEventListener('resize', onWindowResize, false);\n\n    container.addEventListener('mouseover', function() {\n      overRenderer = true;\n    }, false);\n\n    container.addEventListener('mouseout', function() {\n      overRenderer = false;\n    }, false);\n  }\n\n  function addData(data, opts) {\n    var lat, lng, size, color, i, step, colorFnWrapper;\n\n    opts.animated = opts.animated || false;\n    this.is_animated = opts.animated;\n    opts.format = opts.format || 'magnitude'; // other option is 'legend'\n    if (opts.format === 'magnitude') {\n      step = 3;\n      colorFnWrapper = function(data, i) { return colorFn(data[i+2]); }\n    } else if (opts.format === 'legend') {\n      step = 4;\n      colorFnWrapper = function(data, i) { return colorFn(data[i+3]); }\n    } else if (opts.format === 'peer') {\n      colorFnWrapper = function(data, i) { return colorFn(data[i+2]); }\n    } else {\n      throw('error: format not supported: '+opts.format);\n    }\n\n    if (opts.animated) {\n      if (this._baseGeometry === undefined) {\n        this._baseGeometry = new THREE.Geometry();\n        for (i = 0; i < data.length; i += step) {\n          lat = data[i];\n          lng = data[i + 1];\n//        size = data[i + 2];\n          color = colorFnWrapper(data,i);\n          size = 0;\n          addPoint(lat, lng, size, color, this._baseGeometry);\n        }\n      }\n      if(this._morphTargetId === undefined) {\n        this._morphTargetId = 0;\n      } else {\n        this._morphTargetId += 1;\n      }\n      opts.name = opts.name || 'morphTarget'+this._morphTargetId;\n    }\n    var subgeo = new THREE.Geometry();\n    for (i = 0; i < data.length; i += step) {\n      lat = data[i];\n      lng = data[i + 1];\n      color = colorFnWrapper(data,i);\n      size = data[i + 2];\n      size = size*200;\n      addPoint(lat, lng, size, color, subgeo);\n    }\n    if (opts.animated) {\n      this._baseGeometry.morphTargets.push({'name': opts.name, vertices: subgeo.vertices});\n    } else {\n      this._baseGeometry = subgeo;\n    }\n\n  };\n\n  function createPoints() {\n    if (this._baseGeometry !== undefined) {\n      if (this.is_animated === false) {\n        this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({\n              color: 0xffffff,\n              vertexColors: THREE.FaceColors,\n              morphTargets: false\n            }));\n      } else {\n        if (this._baseGeometry.morphTargets.length < 8) {\n          console.log('t l',this._baseGeometry.morphTargets.length);\n          var padding = 8-this._baseGeometry.morphTargets.length;\n          console.log('padding', padding);\n          for(var i=0; i<=padding; i++) {\n            console.log('padding',i);\n            this._baseGeometry.morphTargets.push({'name': 'morphPadding'+i, vertices: this._baseGeometry.vertices});\n          }\n        }\n        this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({\n              color: 0xffffff,\n              vertexColors: THREE.FaceColors,\n              morphTargets: true\n            }));\n      }\n      scene.add(this.points);\n    }\n  }\n\n  function addPoint(lat, lng, size, color, subgeo) {\n\n    var phi = (90 - lat) * Math.PI / 180;\n    var theta = (180 - lng) * Math.PI / 180;\n\n    point.position.x = 200 * Math.sin(phi) * Math.cos(theta);\n    point.position.y = 200 * Math.cos(phi);\n    point.position.z = 200 * Math.sin(phi) * Math.sin(theta);\n\n    point.lookAt(mesh.position);\n\n    point.scale.z = Math.max( size, 0.1 ); // avoid non-invertible matrix\n    point.updateMatrix();\n\n    for (var i = 0; i < point.geometry.faces.length; i++) {\n\n      point.geometry.faces[i].color = color;\n\n    }\n    if(point.matrixAutoUpdate){\n      point.updateMatrix();\n    }\n    subgeo.merge(point.geometry, point.matrix);\n  }\n\n  function onMouseDown(event) {\n    event.preventDefault();\n\n    container.addEventListener('mousemove', onMouseMove, false);\n    container.addEventListener('mouseup', onMouseUp, false);\n    container.addEventListener('mouseout', onMouseOut, false);\n\n    mouseOnDown.x = - event.clientX;\n    mouseOnDown.y = event.clientY;\n\n    targetOnDown.x = target.x;\n    targetOnDown.y = target.y;\n\n    container.style.cursor = 'move';\n  }\n\n  function onMouseMove(event) {\n    mouse.x = - event.clientX;\n    mouse.y = event.clientY;\n\n    var zoomDamp = distance/1000;\n\n    target.x = targetOnDown.x + (mouse.x - mouseOnDown.x) * 0.005 * zoomDamp;\n    target.y = targetOnDown.y + (mouse.y - mouseOnDown.y) * 0.005 * zoomDamp;\n\n    target.y = target.y > PI_HALF ? PI_HALF : target.y;\n    target.y = target.y < - PI_HALF ? - PI_HALF : target.y;\n  }\n\n  function onMouseUp(event) {\n    container.removeEventListener('mousemove', onMouseMove, false);\n    container.removeEventListener('mouseup', onMouseUp, false);\n    container.removeEventListener('mouseout', onMouseOut, false);\n    container.style.cursor = 'auto';\n  }\n\n  function onMouseOut(event) {\n    container.removeEventListener('mousemove', onMouseMove, false);\n    container.removeEventListener('mouseup', onMouseUp, false);\n    container.removeEventListener('mouseout', onMouseOut, false);\n  }\n\n  function onMouseWheel(event) {\n    if (container.style.cursor != \"move\") return false;\n    event.preventDefault();\n    if (overRenderer) {\n      if (event.deltaY) {\n        zoom(-event.deltaY * (event.deltaMode == 0 ? 1 : 50));\n      } else {\n        zoom(event.wheelDeltaY * 0.3);\n      }\n    }\n    return false;\n  }\n\n  function onDocumentKeyDown(event) {\n    switch (event.keyCode) {\n      case 38:\n        zoom(100);\n        event.preventDefault();\n        break;\n      case 40:\n        zoom(-100);\n        event.preventDefault();\n        break;\n    }\n  }\n\n  function onWindowResize( event ) {\n    camera.aspect = container.offsetWidth / container.offsetHeight;\n    camera.updateProjectionMatrix();\n    renderer.setSize( container.offsetWidth, container.offsetHeight );\n  }\n\n  function zoom(delta) {\n    distanceTarget -= delta;\n    distanceTarget = distanceTarget > 855 ? 855 : distanceTarget;\n    distanceTarget = distanceTarget < 350 ? 350 : distanceTarget;\n  }\n\n  function animate() {\n    if (!running) return\n    requestAnimationFrame(animate);\n    render();\n  }\n\n  function render() {\n    zoom(curZoomSpeed);\n\n    rotation.x += (target.x - rotation.x) * 0.1;\n    rotation.y += (target.y - rotation.y) * 0.1;\n    distance += (distanceTarget - distance) * 0.3;\n\n    camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y);\n    camera.position.y = distance * Math.sin(rotation.y);\n    camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y);\n\n    camera.lookAt(mesh.position);\n\n    renderer.render(scene, camera);\n  }\n\n  function unload() {\n    running = false\n    container.removeEventListener('mousedown', onMouseDown, false);\n    if ('onwheel' in document) {\n      container.removeEventListener('wheel', onMouseWheel, false);\n    } else {\n      container.removeEventListener('mousewheel', onMouseWheel, false);\n    }\n    document.removeEventListener('keydown', onDocumentKeyDown, false);\n    window.removeEventListener('resize', onWindowResize, false);\n\n  }\n\n  init();\n  this.animate = animate;\n  this.unload = unload;\n\n\n  this.__defineGetter__('time', function() {\n    return this._time || 0;\n  });\n\n  this.__defineSetter__('time', function(t) {\n    var validMorphs = [];\n    var morphDict = this.points.morphTargetDictionary;\n    for(var k in morphDict) {\n      if(k.indexOf('morphPadding') < 0) {\n        validMorphs.push(morphDict[k]);\n      }\n    }\n    validMorphs.sort();\n    var l = validMorphs.length-1;\n    var scaledt = t*l+1;\n    var index = Math.floor(scaledt);\n    for (i=0;i<validMorphs.length;i++) {\n      this.points.morphTargetInfluences[validMorphs[i]] = 0;\n    }\n    var lastIndex = index - 1;\n    var leftover = scaledt - index;\n    if (lastIndex >= 0) {\n      this.points.morphTargetInfluences[lastIndex] = 1 - leftover;\n    }\n    this.points.morphTargetInfluences[index] = leftover;\n    this._time = t;\n  });\n\n  this.addData = addData;\n  this.createPoints = createPoints;\n  this.renderer = renderer;\n  this.scene = scene;\n\n  return this;\n\n};\n\n\n\n/* ---- plugins/Sidebar/media_globe/three.min.js ---- */\n\n\n// threejs.org/license\n'use strict';var THREE={REVISION:\"69\"};\"object\"===typeof module&&(module.exports=THREE);void 0===Math.sign&&(Math.sign=function(a){return 0>a?-1:0<a?1:0});THREE.MOUSE={LEFT:0,MIDDLE:1,RIGHT:2};THREE.CullFaceNone=0;THREE.CullFaceBack=1;THREE.CullFaceFront=2;THREE.CullFaceFrontBack=3;THREE.FrontFaceDirectionCW=0;THREE.FrontFaceDirectionCCW=1;THREE.BasicShadowMap=0;THREE.PCFShadowMap=1;THREE.PCFSoftShadowMap=2;THREE.FrontSide=0;THREE.BackSide=1;THREE.DoubleSide=2;THREE.NoShading=0;\nTHREE.FlatShading=1;THREE.SmoothShading=2;THREE.NoColors=0;THREE.FaceColors=1;THREE.VertexColors=2;THREE.NoBlending=0;THREE.NormalBlending=1;THREE.AdditiveBlending=2;THREE.SubtractiveBlending=3;THREE.MultiplyBlending=4;THREE.CustomBlending=5;THREE.AddEquation=100;THREE.SubtractEquation=101;THREE.ReverseSubtractEquation=102;THREE.MinEquation=103;THREE.MaxEquation=104;THREE.ZeroFactor=200;THREE.OneFactor=201;THREE.SrcColorFactor=202;THREE.OneMinusSrcColorFactor=203;THREE.SrcAlphaFactor=204;\nTHREE.OneMinusSrcAlphaFactor=205;THREE.DstAlphaFactor=206;THREE.OneMinusDstAlphaFactor=207;THREE.DstColorFactor=208;THREE.OneMinusDstColorFactor=209;THREE.SrcAlphaSaturateFactor=210;THREE.MultiplyOperation=0;THREE.MixOperation=1;THREE.AddOperation=2;THREE.UVMapping=function(){};THREE.CubeReflectionMapping=function(){};THREE.CubeRefractionMapping=function(){};THREE.SphericalReflectionMapping=function(){};THREE.SphericalRefractionMapping=function(){};THREE.RepeatWrapping=1E3;\nTHREE.ClampToEdgeWrapping=1001;THREE.MirroredRepeatWrapping=1002;THREE.NearestFilter=1003;THREE.NearestMipMapNearestFilter=1004;THREE.NearestMipMapLinearFilter=1005;THREE.LinearFilter=1006;THREE.LinearMipMapNearestFilter=1007;THREE.LinearMipMapLinearFilter=1008;THREE.UnsignedByteType=1009;THREE.ByteType=1010;THREE.ShortType=1011;THREE.UnsignedShortType=1012;THREE.IntType=1013;THREE.UnsignedIntType=1014;THREE.FloatType=1015;THREE.UnsignedShort4444Type=1016;THREE.UnsignedShort5551Type=1017;\nTHREE.UnsignedShort565Type=1018;THREE.AlphaFormat=1019;THREE.RGBFormat=1020;THREE.RGBAFormat=1021;THREE.LuminanceFormat=1022;THREE.LuminanceAlphaFormat=1023;THREE.RGB_S3TC_DXT1_Format=2001;THREE.RGBA_S3TC_DXT1_Format=2002;THREE.RGBA_S3TC_DXT3_Format=2003;THREE.RGBA_S3TC_DXT5_Format=2004;THREE.RGB_PVRTC_4BPPV1_Format=2100;THREE.RGB_PVRTC_2BPPV1_Format=2101;THREE.RGBA_PVRTC_4BPPV1_Format=2102;THREE.RGBA_PVRTC_2BPPV1_Format=2103;\nTHREE.Color=function(a){return 3===arguments.length?this.setRGB(arguments[0],arguments[1],arguments[2]):this.set(a)};\nTHREE.Color.prototype={constructor:THREE.Color,r:1,g:1,b:1,set:function(a){a instanceof THREE.Color?this.copy(a):\"number\"===typeof a?this.setHex(a):\"string\"===typeof a&&this.setStyle(a);return this},setHex:function(a){a=Math.floor(a);this.r=(a>>16&255)/255;this.g=(a>>8&255)/255;this.b=(a&255)/255;return this},setRGB:function(a,b,c){this.r=a;this.g=b;this.b=c;return this},setHSL:function(a,b,c){if(0===b)this.r=this.g=this.b=c;else{var d=function(a,b,c){0>c&&(c+=1);1<c&&(c-=1);return c<1/6?a+6*(b-a)*\nc:.5>c?b:c<2/3?a+6*(b-a)*(2/3-c):a};b=.5>=c?c*(1+b):c+b-c*b;c=2*c-b;this.r=d(c,b,a+1/3);this.g=d(c,b,a);this.b=d(c,b,a-1/3)}return this},setStyle:function(a){if(/^rgb\\((\\d+), ?(\\d+), ?(\\d+)\\)$/i.test(a))return a=/^rgb\\((\\d+), ?(\\d+), ?(\\d+)\\)$/i.exec(a),this.r=Math.min(255,parseInt(a[1],10))/255,this.g=Math.min(255,parseInt(a[2],10))/255,this.b=Math.min(255,parseInt(a[3],10))/255,this;if(/^rgb\\((\\d+)\\%, ?(\\d+)\\%, ?(\\d+)\\%\\)$/i.test(a))return a=/^rgb\\((\\d+)\\%, ?(\\d+)\\%, ?(\\d+)\\%\\)$/i.exec(a),this.r=\nMath.min(100,parseInt(a[1],10))/100,this.g=Math.min(100,parseInt(a[2],10))/100,this.b=Math.min(100,parseInt(a[3],10))/100,this;if(/^\\#([0-9a-f]{6})$/i.test(a))return a=/^\\#([0-9a-f]{6})$/i.exec(a),this.setHex(parseInt(a[1],16)),this;if(/^\\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test(a))return a=/^\\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(a),this.setHex(parseInt(a[1]+a[1]+a[2]+a[2]+a[3]+a[3],16)),this;if(/^(\\w+)$/i.test(a))return this.setHex(THREE.ColorKeywords[a]),this},copy:function(a){this.r=a.r;this.g=\na.g;this.b=a.b;return this},copyGammaToLinear:function(a){this.r=a.r*a.r;this.g=a.g*a.g;this.b=a.b*a.b;return this},copyLinearToGamma:function(a){this.r=Math.sqrt(a.r);this.g=Math.sqrt(a.g);this.b=Math.sqrt(a.b);return this},convertGammaToLinear:function(){var a=this.r,b=this.g,c=this.b;this.r=a*a;this.g=b*b;this.b=c*c;return this},convertLinearToGamma:function(){this.r=Math.sqrt(this.r);this.g=Math.sqrt(this.g);this.b=Math.sqrt(this.b);return this},getHex:function(){return 255*this.r<<16^255*this.g<<\n8^255*this.b<<0},getHexString:function(){return(\"000000\"+this.getHex().toString(16)).slice(-6)},getHSL:function(a){a=a||{h:0,s:0,l:0};var b=this.r,c=this.g,d=this.b,e=Math.max(b,c,d),f=Math.min(b,c,d),g,h=(f+e)/2;if(f===e)f=g=0;else{var k=e-f,f=.5>=h?k/(e+f):k/(2-e-f);switch(e){case b:g=(c-d)/k+(c<d?6:0);break;case c:g=(d-b)/k+2;break;case d:g=(b-c)/k+4}g/=6}a.h=g;a.s=f;a.l=h;return a},getStyle:function(){return\"rgb(\"+(255*this.r|0)+\",\"+(255*this.g|0)+\",\"+(255*this.b|0)+\")\"},offsetHSL:function(a,\nb,c){var d=this.getHSL();d.h+=a;d.s+=b;d.l+=c;this.setHSL(d.h,d.s,d.l);return this},add:function(a){this.r+=a.r;this.g+=a.g;this.b+=a.b;return this},addColors:function(a,b){this.r=a.r+b.r;this.g=a.g+b.g;this.b=a.b+b.b;return this},addScalar:function(a){this.r+=a;this.g+=a;this.b+=a;return this},multiply:function(a){this.r*=a.r;this.g*=a.g;this.b*=a.b;return this},multiplyScalar:function(a){this.r*=a;this.g*=a;this.b*=a;return this},lerp:function(a,b){this.r+=(a.r-this.r)*b;this.g+=(a.g-this.g)*b;\nthis.b+=(a.b-this.b)*b;return this},equals:function(a){return a.r===this.r&&a.g===this.g&&a.b===this.b},fromArray:function(a){this.r=a[0];this.g=a[1];this.b=a[2];return this},toArray:function(){return[this.r,this.g,this.b]},clone:function(){return(new THREE.Color).setRGB(this.r,this.g,this.b)}};\nTHREE.ColorKeywords={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,\ndarkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,\ngrey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,\nlime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,\npalegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,\ntomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};THREE.Quaternion=function(a,b,c,d){this._x=a||0;this._y=b||0;this._z=c||0;this._w=void 0!==d?d:1};\nTHREE.Quaternion.prototype={constructor:THREE.Quaternion,_x:0,_y:0,_z:0,_w:0,get x(){return this._x},set x(a){this._x=a;this.onChangeCallback()},get y(){return this._y},set y(a){this._y=a;this.onChangeCallback()},get z(){return this._z},set z(a){this._z=a;this.onChangeCallback()},get w(){return this._w},set w(a){this._w=a;this.onChangeCallback()},set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._w=d;this.onChangeCallback();return this},copy:function(a){this._x=a.x;this._y=a.y;this._z=a.z;\nthis._w=a.w;this.onChangeCallback();return this},setFromEuler:function(a,b){if(!1===a instanceof THREE.Euler)throw Error(\"THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.\");var c=Math.cos(a._x/2),d=Math.cos(a._y/2),e=Math.cos(a._z/2),f=Math.sin(a._x/2),g=Math.sin(a._y/2),h=Math.sin(a._z/2);\"XYZ\"===a.order?(this._x=f*d*e+c*g*h,this._y=c*g*e-f*d*h,this._z=c*d*h+f*g*e,this._w=c*d*e-f*g*h):\"YXZ\"===a.order?(this._x=f*d*e+c*g*h,this._y=c*g*e-f*d*h,this._z=\nc*d*h-f*g*e,this._w=c*d*e+f*g*h):\"ZXY\"===a.order?(this._x=f*d*e-c*g*h,this._y=c*g*e+f*d*h,this._z=c*d*h+f*g*e,this._w=c*d*e-f*g*h):\"ZYX\"===a.order?(this._x=f*d*e-c*g*h,this._y=c*g*e+f*d*h,this._z=c*d*h-f*g*e,this._w=c*d*e+f*g*h):\"YZX\"===a.order?(this._x=f*d*e+c*g*h,this._y=c*g*e+f*d*h,this._z=c*d*h-f*g*e,this._w=c*d*e-f*g*h):\"XZY\"===a.order&&(this._x=f*d*e-c*g*h,this._y=c*g*e-f*d*h,this._z=c*d*h+f*g*e,this._w=c*d*e+f*g*h);if(!1!==b)this.onChangeCallback();return this},setFromAxisAngle:function(a,\nb){var c=b/2,d=Math.sin(c);this._x=a.x*d;this._y=a.y*d;this._z=a.z*d;this._w=Math.cos(c);this.onChangeCallback();return this},setFromRotationMatrix:function(a){var b=a.elements,c=b[0];a=b[4];var d=b[8],e=b[1],f=b[5],g=b[9],h=b[2],k=b[6],b=b[10],n=c+f+b;0<n?(c=.5/Math.sqrt(n+1),this._w=.25/c,this._x=(k-g)*c,this._y=(d-h)*c,this._z=(e-a)*c):c>f&&c>b?(c=2*Math.sqrt(1+c-f-b),this._w=(k-g)/c,this._x=.25*c,this._y=(a+e)/c,this._z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this._w=(d-h)/c,this._x=(a+e)/c,this._y=\n.25*c,this._z=(g+k)/c):(c=2*Math.sqrt(1+b-c-f),this._w=(e-a)/c,this._x=(d+h)/c,this._y=(g+k)/c,this._z=.25*c);this.onChangeCallback();return this},setFromUnitVectors:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector3);b=c.dot(d)+1;1E-6>b?(b=0,Math.abs(c.x)>Math.abs(c.z)?a.set(-c.y,c.x,0):a.set(0,-c.z,c.y)):a.crossVectors(c,d);this._x=a.x;this._y=a.y;this._z=a.z;this._w=b;this.normalize();return this}}(),inverse:function(){this.conjugate().normalize();return this},conjugate:function(){this._x*=\n-1;this._y*=-1;this._z*=-1;this.onChangeCallback();return this},dot:function(a){return this._x*a._x+this._y*a._y+this._z*a._z+this._w*a._w},lengthSq:function(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w},length:function(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)},normalize:function(){var a=this.length();0===a?(this._z=this._y=this._x=0,this._w=1):(a=1/a,this._x*=a,this._y*=a,this._z*=a,this._w*=a);this.onChangeCallback();return this},\nmultiply:function(a,b){return void 0!==b?(console.warn(\"THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.\"),this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this,a)},multiplyQuaternions:function(a,b){var c=a._x,d=a._y,e=a._z,f=a._w,g=b._x,h=b._y,k=b._z,n=b._w;this._x=c*n+f*g+d*k-e*h;this._y=d*n+f*h+e*g-c*k;this._z=e*n+f*k+c*h-d*g;this._w=f*n-c*g-d*h-e*k;this.onChangeCallback();return this},multiplyVector3:function(a){console.warn(\"THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.\");\nreturn a.applyQuaternion(this)},slerp:function(a,b){if(0===b)return this;if(1===b)return this.copy(a);var c=this._x,d=this._y,e=this._z,f=this._w,g=f*a._w+c*a._x+d*a._y+e*a._z;0>g?(this._w=-a._w,this._x=-a._x,this._y=-a._y,this._z=-a._z,g=-g):this.copy(a);if(1<=g)return this._w=f,this._x=c,this._y=d,this._z=e,this;var h=Math.acos(g),k=Math.sqrt(1-g*g);if(.001>Math.abs(k))return this._w=.5*(f+this._w),this._x=.5*(c+this._x),this._y=.5*(d+this._y),this._z=.5*(e+this._z),this;g=Math.sin((1-b)*h)/k;h=\nMath.sin(b*h)/k;this._w=f*g+this._w*h;this._x=c*g+this._x*h;this._y=d*g+this._y*h;this._z=e*g+this._z*h;this.onChangeCallback();return this},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._w===this._w},fromArray:function(a,b){void 0===b&&(b=0);this._x=a[b];this._y=a[b+1];this._z=a[b+2];this._w=a[b+3];this.onChangeCallback();return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this._x;a[b+1]=this._y;a[b+2]=this._z;a[b+3]=this._w;return a},onChange:function(a){this.onChangeCallback=\na;return this},onChangeCallback:function(){},clone:function(){return new THREE.Quaternion(this._x,this._y,this._z,this._w)}};THREE.Quaternion.slerp=function(a,b,c,d){return c.copy(a).slerp(b,d)};THREE.Vector2=function(a,b){this.x=a||0;this.y=b||0};\nTHREE.Vector2.prototype={constructor:THREE.Vector2,set:function(a,b){this.x=a;this.y=b;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error(\"index is out of range: \"+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error(\"index is out of range: \"+a);}},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a,\nb){if(void 0!==b)return console.warn(\"THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.\"),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},sub:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.\"),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;return this},\nsubVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiply:function(a){this.x*=a.x;this.y*=a.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divide:function(a){this.x/=a.x;this.y/=a.y;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a):this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);return this},max:function(a){this.x<a.x&&(this.x=a.x);this.y<a.y&&(this.y=a.y);return this},clamp:function(a,\nb){this.x<a.x?this.x=a.x:this.x>b.x&&(this.x=b.x);this.y<a.y?this.y=a.y:this.y>b.y&&(this.y=b.y);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector2,b=new THREE.Vector2);a.set(c,c);b.set(d,d);return this.clamp(a,b)}}(),floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this},\nroundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);return this},negate:function(){this.x=-this.x;this.y=-this.y;return this},dot:function(a){return this.x*a.x+this.y*a.y},lengthSq:function(){return this.x*this.x+this.y*this.y},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y)},normalize:function(){return this.divideScalar(this.length())},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=\nthis.x-a.x;a=this.y-a.y;return b*b+a*a},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;return a},clone:function(){return new THREE.Vector2(this.x,this.y)}};\nTHREE.Vector3=function(a,b,c){this.x=a||0;this.y=b||0;this.z=c||0};\nTHREE.Vector3.prototype={constructor:THREE.Vector3,set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error(\"index is out of range: \"+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error(\"index is out of range: \"+\na);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.\"),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;return this},sub:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.\"),\nthis.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;return this},multiply:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.\"),this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;return this},multiplyVectors:function(a,b){this.x=a.x*b.x;this.y=\na.y*b.y;this.z=a.z*b.z;return this},applyEuler:function(){var a;return function(b){!1===b instanceof THREE.Euler&&console.error(\"THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.\");void 0===a&&(a=new THREE.Quaternion);this.applyQuaternion(a.setFromEuler(b));return this}}(),applyAxisAngle:function(){var a;return function(b,c){void 0===a&&(a=new THREE.Quaternion);this.applyQuaternion(a.setFromAxisAngle(b,c));return this}}(),applyMatrix3:function(a){var b=this.x,\nc=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12];this.y=a[1]*b+a[5]*c+a[9]*d+a[13];this.z=a[2]*b+a[6]*c+a[10]*d+a[14];return this},applyProjection:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;var e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]);this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y=(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z=\n(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x,c=this.y,d=this.z,e=a.x,f=a.y,g=a.z;a=a.w;var h=a*b+f*d-g*c,k=a*c+g*b-e*d,n=a*d+e*c-f*b,b=-e*b-f*c-g*d;this.x=h*a+b*-e+k*-g-n*-f;this.y=k*a+b*-f+n*-e-h*-g;this.z=n*a+b*-g+h*-f-k*-e;return this},project:function(){var a;return function(b){void 0===a&&(a=new THREE.Matrix4);a.multiplyMatrices(b.projectionMatrix,a.getInverse(b.matrixWorld));return this.applyProjection(a)}}(),unproject:function(){var a;return function(b){void 0===\na&&(a=new THREE.Matrix4);a.multiplyMatrices(b.matrixWorld,a.getInverse(b.projectionMatrix));return this.applyProjection(a)}}(),transformDirection:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*b+a[6]*c+a[10]*d;this.normalize();return this},divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a):this.z=this.y=this.x=0;return this},min:function(a){this.x>\na.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);return this},max:function(a){this.x<a.x&&(this.x=a.x);this.y<a.y&&(this.y=a.y);this.z<a.z&&(this.z=a.z);return this},clamp:function(a,b){this.x<a.x?this.x=a.x:this.x>b.x&&(this.x=b.x);this.y<a.y?this.y=a.y:this.y>b.y&&(this.y=b.y);this.z<a.z?this.z=a.z:this.z>b.z&&(this.z=b.z);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector3,b=new THREE.Vector3);a.set(c,c,c);b.set(d,d,d);return this.clamp(a,\nb)}}(),floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);\nreturn this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},lengthManhattan:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)},normalize:function(){return this.divideScalar(this.length())},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/\nb);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},cross:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.\"),this.crossVectors(a,b);var c=this.x,d=this.y,e=this.z;this.x=d*a.z-e*a.y;this.y=e*a.x-c*a.z;this.z=c*a.y-d*a.x;return this},crossVectors:function(a,b){var c=a.x,d=a.y,e=a.z,f=b.x,g=b.y,h=b.z;this.x=d*h-e*g;this.y=e*f-c*h;this.z=c*g-d*f;return this},\nprojectOnVector:function(){var a,b;return function(c){void 0===a&&(a=new THREE.Vector3);a.copy(c).normalize();b=this.dot(a);return this.copy(a).multiplyScalar(b)}}(),projectOnPlane:function(){var a;return function(b){void 0===a&&(a=new THREE.Vector3);a.copy(this).projectOnVector(b);return this.sub(a)}}(),reflect:function(){var a;return function(b){void 0===a&&(a=new THREE.Vector3);return this.sub(a.copy(b).multiplyScalar(2*this.dot(b)))}}(),angleTo:function(a){a=this.dot(a)/(this.length()*a.length());\nreturn Math.acos(THREE.Math.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y;a=this.z-a.z;return b*b+c*c+a*a},setEulerFromRotationMatrix:function(a,b){console.error(\"THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.\")},setEulerFromQuaternion:function(a,b){console.error(\"THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.\")},\ngetPositionFromMatrix:function(a){console.warn(\"THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().\");return this.setFromMatrixPosition(a)},getScaleFromMatrix:function(a){console.warn(\"THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().\");return this.setFromMatrixScale(a)},getColumnFromMatrix:function(a,b){console.warn(\"THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().\");return this.setFromMatrixColumn(a,\nb)},setFromMatrixPosition:function(a){this.x=a.elements[12];this.y=a.elements[13];this.z=a.elements[14];return this},setFromMatrixScale:function(a){var b=this.set(a.elements[0],a.elements[1],a.elements[2]).length(),c=this.set(a.elements[4],a.elements[5],a.elements[6]).length();a=this.set(a.elements[8],a.elements[9],a.elements[10]).length();this.x=b;this.y=c;this.z=a;return this},setFromMatrixColumn:function(a,b){var c=4*a,d=b.elements;this.x=d[c];this.y=d[c+1];this.z=d[c+2];return this},equals:function(a){return a.x===\nthis.x&&a.y===this.y&&a.z===this.z},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=this.z;return a},clone:function(){return new THREE.Vector3(this.x,this.y,this.z)}};THREE.Vector4=function(a,b,c,d){this.x=a||0;this.y=b||0;this.z=c||0;this.w=void 0!==d?d:1};\nTHREE.Vector4.prototype={constructor:THREE.Vector4,set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=b;break;default:throw Error(\"index is out of range: \"+a);}},getComponent:function(a){switch(a){case 0:return this.x;\ncase 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw Error(\"index is out of range: \"+a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.\"),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;return this},\naddVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;this.w=a.w+b.w;return this},sub:function(a,b){if(void 0!==b)return console.warn(\"THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.\"),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.w;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;this.w=a.w-b.w;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this},applyMatrix4:function(a){var b=\nthis.x,c=this.y,d=this.z,e=this.w;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a,this.w*=a):(this.z=this.y=this.x=0,this.w=1);return this},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b,this.y=a.y/b,this.z=a.z/b);return this},\nsetAxisAngleFromRotationMatrix:function(a){var b,c,d;a=a.elements;var e=a[0];d=a[4];var f=a[8],g=a[1],h=a[5],k=a[9];c=a[2];b=a[6];var n=a[10];if(.01>Math.abs(d-g)&&.01>Math.abs(f-c)&&.01>Math.abs(k-b)){if(.1>Math.abs(d+g)&&.1>Math.abs(f+c)&&.1>Math.abs(k+b)&&.1>Math.abs(e+h+n-3))return this.set(1,0,0,0),this;a=Math.PI;e=(e+1)/2;h=(h+1)/2;n=(n+1)/2;d=(d+g)/4;f=(f+c)/4;k=(k+b)/4;e>h&&e>n?.01>e?(b=0,d=c=.707106781):(b=Math.sqrt(e),c=d/b,d=f/b):h>n?.01>h?(b=.707106781,c=0,d=.707106781):(c=Math.sqrt(h),\nb=d/c,d=k/c):.01>n?(c=b=.707106781,d=0):(d=Math.sqrt(n),b=f/d,c=k/d);this.set(b,c,d,a);return this}a=Math.sqrt((b-k)*(b-k)+(f-c)*(f-c)+(g-d)*(g-d));.001>Math.abs(a)&&(a=1);this.x=(b-k)/a;this.y=(f-c)/a;this.z=(g-d)/a;this.w=Math.acos((e+h+n-1)/2);return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);this.w>a.w&&(this.w=a.w);return this},max:function(a){this.x<a.x&&(this.x=a.x);this.y<a.y&&(this.y=a.y);this.z<a.z&&(this.z=a.z);this.w<a.w&&(this.w=a.w);\nreturn this},clamp:function(a,b){this.x<a.x?this.x=a.x:this.x>b.x&&(this.x=b.x);this.y<a.y?this.y=a.y:this.y>b.y&&(this.y=b.y);this.z<a.z?this.z=a.z:this.z>b.z&&(this.z=b.z);this.w<a.w?this.w=a.w:this.w>b.w&&(this.w=b.w);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector4,b=new THREE.Vector4);a.set(c,c,c,c);b.set(d,d,d,d);return this.clamp(a,b)}}(),floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);this.w=Math.floor(this.w);\nreturn this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);this.w=Math.ceil(this.w);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);this.w=Math.round(this.w);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);this.w=0>this.w?Math.ceil(this.w):Math.floor(this.w);\nreturn this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;this.w=-this.w;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.w},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w)},lengthManhattan:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)+Math.abs(this.w)},normalize:function(){return this.divideScalar(this.length())},\nsetLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&a.w===this.w},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];this.w=a[b+3];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=\nthis.z;a[b+3]=this.w;return a},clone:function(){return new THREE.Vector4(this.x,this.y,this.z,this.w)}};THREE.Euler=function(a,b,c,d){this._x=a||0;this._y=b||0;this._z=c||0;this._order=d||THREE.Euler.DefaultOrder};THREE.Euler.RotationOrders=\"XYZ YZX ZXY XZY YXZ ZYX\".split(\" \");THREE.Euler.DefaultOrder=\"XYZ\";\nTHREE.Euler.prototype={constructor:THREE.Euler,_x:0,_y:0,_z:0,_order:THREE.Euler.DefaultOrder,get x(){return this._x},set x(a){this._x=a;this.onChangeCallback()},get y(){return this._y},set y(a){this._y=a;this.onChangeCallback()},get z(){return this._z},set z(a){this._z=a;this.onChangeCallback()},get order(){return this._order},set order(a){this._order=a;this.onChangeCallback()},set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._order=d||this._order;this.onChangeCallback();return this},copy:function(a){this._x=\na._x;this._y=a._y;this._z=a._z;this._order=a._order;this.onChangeCallback();return this},setFromRotationMatrix:function(a,b){var c=THREE.Math.clamp,d=a.elements,e=d[0],f=d[4],g=d[8],h=d[1],k=d[5],n=d[9],p=d[2],q=d[6],d=d[10];b=b||this._order;\"XYZ\"===b?(this._y=Math.asin(c(g,-1,1)),.99999>Math.abs(g)?(this._x=Math.atan2(-n,d),this._z=Math.atan2(-f,e)):(this._x=Math.atan2(q,k),this._z=0)):\"YXZ\"===b?(this._x=Math.asin(-c(n,-1,1)),.99999>Math.abs(n)?(this._y=Math.atan2(g,d),this._z=Math.atan2(h,k)):(this._y=\nMath.atan2(-p,e),this._z=0)):\"ZXY\"===b?(this._x=Math.asin(c(q,-1,1)),.99999>Math.abs(q)?(this._y=Math.atan2(-p,d),this._z=Math.atan2(-f,k)):(this._y=0,this._z=Math.atan2(h,e))):\"ZYX\"===b?(this._y=Math.asin(-c(p,-1,1)),.99999>Math.abs(p)?(this._x=Math.atan2(q,d),this._z=Math.atan2(h,e)):(this._x=0,this._z=Math.atan2(-f,k))):\"YZX\"===b?(this._z=Math.asin(c(h,-1,1)),.99999>Math.abs(h)?(this._x=Math.atan2(-n,k),this._y=Math.atan2(-p,e)):(this._x=0,this._y=Math.atan2(g,d))):\"XZY\"===b?(this._z=Math.asin(-c(f,\n-1,1)),.99999>Math.abs(f)?(this._x=Math.atan2(q,k),this._y=Math.atan2(g,e)):(this._x=Math.atan2(-n,d),this._y=0)):console.warn(\"THREE.Euler: .setFromRotationMatrix() given unsupported order: \"+b);this._order=b;this.onChangeCallback();return this},setFromQuaternion:function(a,b,c){var d=THREE.Math.clamp,e=a.x*a.x,f=a.y*a.y,g=a.z*a.z,h=a.w*a.w;b=b||this._order;\"XYZ\"===b?(this._x=Math.atan2(2*(a.x*a.w-a.y*a.z),h-e-f+g),this._y=Math.asin(d(2*(a.x*a.z+a.y*a.w),-1,1)),this._z=Math.atan2(2*(a.z*a.w-a.x*\na.y),h+e-f-g)):\"YXZ\"===b?(this._x=Math.asin(d(2*(a.x*a.w-a.y*a.z),-1,1)),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h-e-f+g),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h-e+f-g)):\"ZXY\"===b?(this._x=Math.asin(d(2*(a.x*a.w+a.y*a.z),-1,1)),this._y=Math.atan2(2*(a.y*a.w-a.z*a.x),h-e-f+g),this._z=Math.atan2(2*(a.z*a.w-a.x*a.y),h-e+f-g)):\"ZYX\"===b?(this._x=Math.atan2(2*(a.x*a.w+a.z*a.y),h-e-f+g),this._y=Math.asin(d(2*(a.y*a.w-a.x*a.z),-1,1)),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h+e-f-g)):\"YZX\"===b?(this._x=Math.atan2(2*\n(a.x*a.w-a.z*a.y),h-e+f-g),this._y=Math.atan2(2*(a.y*a.w-a.x*a.z),h+e-f-g),this._z=Math.asin(d(2*(a.x*a.y+a.z*a.w),-1,1))):\"XZY\"===b?(this._x=Math.atan2(2*(a.x*a.w+a.y*a.z),h-e+f-g),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h+e-f-g),this._z=Math.asin(d(2*(a.z*a.w-a.x*a.y),-1,1))):console.warn(\"THREE.Euler: .setFromQuaternion() given unsupported order: \"+b);this._order=b;if(!1!==c)this.onChangeCallback();return this},reorder:function(){var a=new THREE.Quaternion;return function(b){a.setFromEuler(this);\nthis.setFromQuaternion(a,b)}}(),equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._order===this._order},fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];void 0!==a[3]&&(this._order=a[3]);this.onChangeCallback();return this},toArray:function(){return[this._x,this._y,this._z,this._order]},onChange:function(a){this.onChangeCallback=a;return this},onChangeCallback:function(){},clone:function(){return new THREE.Euler(this._x,this._y,this._z,this._order)}};\nTHREE.Line3=function(a,b){this.start=void 0!==a?a:new THREE.Vector3;this.end=void 0!==b?b:new THREE.Vector3};\nTHREE.Line3.prototype={constructor:THREE.Line3,set:function(a,b){this.start.copy(a);this.end.copy(b);return this},copy:function(a){this.start.copy(a.start);this.end.copy(a.end);return this},center:function(a){return(a||new THREE.Vector3).addVectors(this.start,this.end).multiplyScalar(.5)},delta:function(a){return(a||new THREE.Vector3).subVectors(this.end,this.start)},distanceSq:function(){return this.start.distanceToSquared(this.end)},distance:function(){return this.start.distanceTo(this.end)},at:function(a,\nb){var c=b||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},closestPointToPointParameter:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d){a.subVectors(c,this.start);b.subVectors(this.end,this.start);var e=b.dot(b),e=b.dot(a)/e;d&&(e=THREE.Math.clamp(e,0,1));return e}}(),closestPointToPoint:function(a,b,c){a=this.closestPointToPointParameter(a,b);c=c||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},applyMatrix4:function(a){this.start.applyMatrix4(a);\nthis.end.applyMatrix4(a);return this},equals:function(a){return a.start.equals(this.start)&&a.end.equals(this.end)},clone:function(){return(new THREE.Line3).copy(this)}};THREE.Box2=function(a,b){this.min=void 0!==a?a:new THREE.Vector2(Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector2(-Infinity,-Infinity)};\nTHREE.Box2.prototype={constructor:THREE.Box2,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){this.makeEmpty();for(var b=0,c=a.length;b<c;b++)this.expandByPoint(a[b]);return this},setFromCenterAndSize:function(){var a=new THREE.Vector2;return function(b,c){var d=a.copy(c).multiplyScalar(.5);this.min.copy(b).sub(d);this.max.copy(b).add(d);return this}}(),copy:function(a){this.min.copy(a.min);this.max.copy(a.max);return this},makeEmpty:function(){this.min.x=\nthis.min.y=Infinity;this.max.x=this.max.y=-Infinity;return this},empty:function(){return this.max.x<this.min.x||this.max.y<this.min.y},center:function(a){return(a||new THREE.Vector2).addVectors(this.min,this.max).multiplyScalar(.5)},size:function(a){return(a||new THREE.Vector2).subVectors(this.max,this.min)},expandByPoint:function(a){this.min.min(a);this.max.max(a);return this},expandByVector:function(a){this.min.sub(a);this.max.add(a);return this},expandByScalar:function(a){this.min.addScalar(-a);\nthis.max.addScalar(a);return this},containsPoint:function(a){return a.x<this.min.x||a.x>this.max.x||a.y<this.min.y||a.y>this.max.y?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector2).set((a.x-this.min.x)/(this.max.x-this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y))},isIntersectionBox:function(a){return a.max.x<this.min.x||a.min.x>this.max.x||a.max.y<this.min.y||a.min.y>\nthis.max.y?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector2).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector2;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&\na.max.equals(this.max)},clone:function(){return(new THREE.Box2).copy(this)}};THREE.Box3=function(a,b){this.min=void 0!==a?a:new THREE.Vector3(Infinity,Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector3(-Infinity,-Infinity,-Infinity)};\nTHREE.Box3.prototype={constructor:THREE.Box3,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){this.makeEmpty();for(var b=0,c=a.length;b<c;b++)this.expandByPoint(a[b]);return this},setFromCenterAndSize:function(){var a=new THREE.Vector3;return function(b,c){var d=a.copy(c).multiplyScalar(.5);this.min.copy(b).sub(d);this.max.copy(b).add(d);return this}}(),setFromObject:function(){var a=new THREE.Vector3;return function(b){var c=this;b.updateMatrixWorld(!0);\nthis.makeEmpty();b.traverse(function(b){var e=b.geometry;if(void 0!==e)if(e instanceof THREE.Geometry)for(var f=e.vertices,e=0,g=f.length;e<g;e++)a.copy(f[e]),a.applyMatrix4(b.matrixWorld),c.expandByPoint(a);else if(e instanceof THREE.BufferGeometry&&void 0!==e.attributes.position)for(f=e.attributes.position.array,e=0,g=f.length;e<g;e+=3)a.set(f[e],f[e+1],f[e+2]),a.applyMatrix4(b.matrixWorld),c.expandByPoint(a)});return this}}(),copy:function(a){this.min.copy(a.min);this.max.copy(a.max);return this},\nmakeEmpty:function(){this.min.x=this.min.y=this.min.z=Infinity;this.max.x=this.max.y=this.max.z=-Infinity;return this},empty:function(){return this.max.x<this.min.x||this.max.y<this.min.y||this.max.z<this.min.z},center:function(a){return(a||new THREE.Vector3).addVectors(this.min,this.max).multiplyScalar(.5)},size:function(a){return(a||new THREE.Vector3).subVectors(this.max,this.min)},expandByPoint:function(a){this.min.min(a);this.max.max(a);return this},expandByVector:function(a){this.min.sub(a);\nthis.max.add(a);return this},expandByScalar:function(a){this.min.addScalar(-a);this.max.addScalar(a);return this},containsPoint:function(a){return a.x<this.min.x||a.x>this.max.x||a.y<this.min.y||a.y>this.max.y||a.z<this.min.z||a.z>this.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector3).set((a.x-this.min.x)/(this.max.x-\nthis.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},isIntersectionBox:function(a){return a.max.x<this.min.x||a.min.x>this.max.x||a.max.y<this.min.y||a.min.y>this.max.y||a.max.z<this.min.z||a.min.z>this.max.z?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector3).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a=\nnew THREE.Vector3;return function(b){b=b||new THREE.Sphere;b.center=this.center();b.radius=.5*this.size(a).length();return b}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(){var a=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];return function(b){a[0].set(this.min.x,this.min.y,\nthis.min.z).applyMatrix4(b);a[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(b);a[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(b);a[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(b);a[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(b);a[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(b);a[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(b);a[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(b);this.makeEmpty();this.setFromPoints(a);return this}}(),translate:function(a){this.min.add(a);\nthis.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box3).copy(this)}};THREE.Matrix3=function(){this.elements=new Float32Array([1,0,0,0,1,0,0,0,1]);0<arguments.length&&console.error(\"THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.\")};\nTHREE.Matrix3.prototype={constructor:THREE.Matrix3,set:function(a,b,c,d,e,f,g,h,k){var n=this.elements;n[0]=a;n[3]=b;n[6]=c;n[1]=d;n[4]=e;n[7]=f;n[2]=g;n[5]=h;n[8]=k;return this},identity:function(){this.set(1,0,0,0,1,0,0,0,1);return this},copy:function(a){a=a.elements;this.set(a[0],a[3],a[6],a[1],a[4],a[7],a[2],a[5],a[8]);return this},multiplyVector3:function(a){console.warn(\"THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.\");return a.applyMatrix3(this)},\nmultiplyVector3Array:function(a){console.warn(\"THREE.Matrix3: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.\");return this.applyToVector3Array(a)},applyToVector3Array:function(){var a=new THREE.Vector3;return function(b,c,d){void 0===c&&(c=0);void 0===d&&(d=b.length);for(var e=0;e<d;e+=3,c+=3)a.x=b[c],a.y=b[c+1],a.z=b[c+2],a.applyMatrix3(this),b[c]=a.x,b[c+1]=a.y,b[c+2]=a.z;return b}}(),multiplyScalar:function(a){var b=this.elements;b[0]*=a;b[3]*=a;b[6]*=\na;b[1]*=a;b[4]*=a;b[7]*=a;b[2]*=a;b[5]*=a;b[8]*=a;return this},determinant:function(){var a=this.elements,b=a[0],c=a[1],d=a[2],e=a[3],f=a[4],g=a[5],h=a[6],k=a[7],a=a[8];return b*f*a-b*g*k-c*e*a+c*g*h+d*e*k-d*f*h},getInverse:function(a,b){var c=a.elements,d=this.elements;d[0]=c[10]*c[5]-c[6]*c[9];d[1]=-c[10]*c[1]+c[2]*c[9];d[2]=c[6]*c[1]-c[2]*c[5];d[3]=-c[10]*c[4]+c[6]*c[8];d[4]=c[10]*c[0]-c[2]*c[8];d[5]=-c[6]*c[0]+c[2]*c[4];d[6]=c[9]*c[4]-c[5]*c[8];d[7]=-c[9]*c[0]+c[1]*c[8];d[8]=c[5]*c[0]-c[1]*c[4];\nc=c[0]*d[0]+c[1]*d[3]+c[2]*d[6];if(0===c){if(b)throw Error(\"Matrix3.getInverse(): can't invert matrix, determinant is 0\");console.warn(\"Matrix3.getInverse(): can't invert matrix, determinant is 0\");this.identity();return this}this.multiplyScalar(1/c);return this},transpose:function(){var a,b=this.elements;a=b[1];b[1]=b[3];b[3]=a;a=b[2];b[2]=b[6];b[6]=a;a=b[5];b[5]=b[7];b[7]=a;return this},flattenToArrayOffset:function(a,b){var c=this.elements;a[b]=c[0];a[b+1]=c[1];a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];\na[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];return a},getNormalMatrix:function(a){this.getInverse(a).transpose();return this},transposeIntoArray:function(a){var b=this.elements;a[0]=b[0];a[1]=b[3];a[2]=b[6];a[3]=b[1];a[4]=b[4];a[5]=b[7];a[6]=b[2];a[7]=b[5];a[8]=b[8];return this},fromArray:function(a){this.elements.set(a);return this},toArray:function(){var a=this.elements;return[a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8]]},clone:function(){return(new THREE.Matrix3).fromArray(this.elements)}};\nTHREE.Matrix4=function(){this.elements=new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);0<arguments.length&&console.error(\"THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.\")};\nTHREE.Matrix4.prototype={constructor:THREE.Matrix4,set:function(a,b,c,d,e,f,g,h,k,n,p,q,m,r,t,s){var u=this.elements;u[0]=a;u[4]=b;u[8]=c;u[12]=d;u[1]=e;u[5]=f;u[9]=g;u[13]=h;u[2]=k;u[6]=n;u[10]=p;u[14]=q;u[3]=m;u[7]=r;u[11]=t;u[15]=s;return this},identity:function(){this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return this},copy:function(a){this.elements.set(a.elements);return this},extractPosition:function(a){console.warn(\"THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().\");return this.copyPosition(a)},\ncopyPosition:function(a){var b=this.elements;a=a.elements;b[12]=a[12];b[13]=a[13];b[14]=a[14];return this},extractRotation:function(){var a=new THREE.Vector3;return function(b){var c=this.elements;b=b.elements;var d=1/a.set(b[0],b[1],b[2]).length(),e=1/a.set(b[4],b[5],b[6]).length(),f=1/a.set(b[8],b[9],b[10]).length();c[0]=b[0]*d;c[1]=b[1]*d;c[2]=b[2]*d;c[4]=b[4]*e;c[5]=b[5]*e;c[6]=b[6]*e;c[8]=b[8]*f;c[9]=b[9]*f;c[10]=b[10]*f;return this}}(),makeRotationFromEuler:function(a){!1===a instanceof THREE.Euler&&\nconsole.error(\"THREE.Matrix: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.\");var b=this.elements,c=a.x,d=a.y,e=a.z,f=Math.cos(c),c=Math.sin(c),g=Math.cos(d),d=Math.sin(d),h=Math.cos(e),e=Math.sin(e);if(\"XYZ\"===a.order){a=f*h;var k=f*e,n=c*h,p=c*e;b[0]=g*h;b[4]=-g*e;b[8]=d;b[1]=k+n*d;b[5]=a-p*d;b[9]=-c*g;b[2]=p-a*d;b[6]=n+k*d;b[10]=f*g}else\"YXZ\"===a.order?(a=g*h,k=g*e,n=d*h,p=d*e,b[0]=a+p*c,b[4]=n*c-k,b[8]=f*d,b[1]=f*e,b[5]=f*h,b[9]=-c,b[2]=k*c-n,b[6]=p+a*c,\nb[10]=f*g):\"ZXY\"===a.order?(a=g*h,k=g*e,n=d*h,p=d*e,b[0]=a-p*c,b[4]=-f*e,b[8]=n+k*c,b[1]=k+n*c,b[5]=f*h,b[9]=p-a*c,b[2]=-f*d,b[6]=c,b[10]=f*g):\"ZYX\"===a.order?(a=f*h,k=f*e,n=c*h,p=c*e,b[0]=g*h,b[4]=n*d-k,b[8]=a*d+p,b[1]=g*e,b[5]=p*d+a,b[9]=k*d-n,b[2]=-d,b[6]=c*g,b[10]=f*g):\"YZX\"===a.order?(a=f*g,k=f*d,n=c*g,p=c*d,b[0]=g*h,b[4]=p-a*e,b[8]=n*e+k,b[1]=e,b[5]=f*h,b[9]=-c*h,b[2]=-d*h,b[6]=k*e+n,b[10]=a-p*e):\"XZY\"===a.order&&(a=f*g,k=f*d,n=c*g,p=c*d,b[0]=g*h,b[4]=-e,b[8]=d*h,b[1]=a*e+p,b[5]=f*h,b[9]=k*\ne-n,b[2]=n*e-k,b[6]=c*h,b[10]=p*e+a);b[3]=0;b[7]=0;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return this},setRotationFromQuaternion:function(a){console.warn(\"THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().\");return this.makeRotationFromQuaternion(a)},makeRotationFromQuaternion:function(a){var b=this.elements,c=a.x,d=a.y,e=a.z,f=a.w,g=c+c,h=d+d,k=e+e;a=c*g;var n=c*h,c=c*k,p=d*h,d=d*k,e=e*k,g=f*g,h=f*h,f=f*k;b[0]=1-(p+e);b[4]=n-f;b[8]=c+h;b[1]=n+f;b[5]=1-\n(a+e);b[9]=d-g;b[2]=c-h;b[6]=d+g;b[10]=1-(a+p);b[3]=0;b[7]=0;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return this},lookAt:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3;return function(d,e,f){var g=this.elements;c.subVectors(d,e).normalize();0===c.length()&&(c.z=1);a.crossVectors(f,c).normalize();0===a.length()&&(c.x+=1E-4,a.crossVectors(f,c).normalize());b.crossVectors(c,a);g[0]=a.x;g[4]=b.x;g[8]=c.x;g[1]=a.y;g[5]=b.y;g[9]=c.y;g[2]=a.z;g[6]=b.z;g[10]=c.z;return this}}(),\nmultiply:function(a,b){return void 0!==b?(console.warn(\"THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.\"),this.multiplyMatrices(a,b)):this.multiplyMatrices(this,a)},multiplyMatrices:function(a,b){var c=a.elements,d=b.elements,e=this.elements,f=c[0],g=c[4],h=c[8],k=c[12],n=c[1],p=c[5],q=c[9],m=c[13],r=c[2],t=c[6],s=c[10],u=c[14],v=c[3],y=c[7],G=c[11],c=c[15],w=d[0],K=d[4],x=d[8],D=d[12],E=d[1],A=d[5],B=d[9],F=d[13],R=d[2],H=d[6],C=d[10],T=d[14],Q=d[3],\nO=d[7],S=d[11],d=d[15];e[0]=f*w+g*E+h*R+k*Q;e[4]=f*K+g*A+h*H+k*O;e[8]=f*x+g*B+h*C+k*S;e[12]=f*D+g*F+h*T+k*d;e[1]=n*w+p*E+q*R+m*Q;e[5]=n*K+p*A+q*H+m*O;e[9]=n*x+p*B+q*C+m*S;e[13]=n*D+p*F+q*T+m*d;e[2]=r*w+t*E+s*R+u*Q;e[6]=r*K+t*A+s*H+u*O;e[10]=r*x+t*B+s*C+u*S;e[14]=r*D+t*F+s*T+u*d;e[3]=v*w+y*E+G*R+c*Q;e[7]=v*K+y*A+G*H+c*O;e[11]=v*x+y*B+G*C+c*S;e[15]=v*D+y*F+G*T+c*d;return this},multiplyToArray:function(a,b,c){var d=this.elements;this.multiplyMatrices(a,b);c[0]=d[0];c[1]=d[1];c[2]=d[2];c[3]=d[3];c[4]=\nd[4];c[5]=d[5];c[6]=d[6];c[7]=d[7];c[8]=d[8];c[9]=d[9];c[10]=d[10];c[11]=d[11];c[12]=d[12];c[13]=d[13];c[14]=d[14];c[15]=d[15];return this},multiplyScalar:function(a){var b=this.elements;b[0]*=a;b[4]*=a;b[8]*=a;b[12]*=a;b[1]*=a;b[5]*=a;b[9]*=a;b[13]*=a;b[2]*=a;b[6]*=a;b[10]*=a;b[14]*=a;b[3]*=a;b[7]*=a;b[11]*=a;b[15]*=a;return this},multiplyVector3:function(a){console.warn(\"THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.\");\nreturn a.applyProjection(this)},multiplyVector4:function(a){console.warn(\"THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.\");return a.applyMatrix4(this)},multiplyVector3Array:function(a){console.warn(\"THREE.Matrix4: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.\");return this.applyToVector3Array(a)},applyToVector3Array:function(){var a=new THREE.Vector3;return function(b,c,d){void 0===c&&(c=0);void 0===d&&(d=\nb.length);for(var e=0;e<d;e+=3,c+=3)a.x=b[c],a.y=b[c+1],a.z=b[c+2],a.applyMatrix4(this),b[c]=a.x,b[c+1]=a.y,b[c+2]=a.z;return b}}(),rotateAxis:function(a){console.warn(\"THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.\");a.transformDirection(this)},crossVector:function(a){console.warn(\"THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.\");return a.applyMatrix4(this)},determinant:function(){var a=this.elements,b=\na[0],c=a[4],d=a[8],e=a[12],f=a[1],g=a[5],h=a[9],k=a[13],n=a[2],p=a[6],q=a[10],m=a[14];return a[3]*(+e*h*p-d*k*p-e*g*q+c*k*q+d*g*m-c*h*m)+a[7]*(+b*h*m-b*k*q+e*f*q-d*f*m+d*k*n-e*h*n)+a[11]*(+b*k*p-b*g*m-e*f*p+c*f*m+e*g*n-c*k*n)+a[15]*(-d*g*n-b*h*p+b*g*q+d*f*p-c*f*q+c*h*n)},transpose:function(){var a=this.elements,b;b=a[1];a[1]=a[4];a[4]=b;b=a[2];a[2]=a[8];a[8]=b;b=a[6];a[6]=a[9];a[9]=b;b=a[3];a[3]=a[12];a[12]=b;b=a[7];a[7]=a[13];a[13]=b;b=a[11];a[11]=a[14];a[14]=b;return this},flattenToArrayOffset:function(a,\nb){var c=this.elements;a[b]=c[0];a[b+1]=c[1];a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];a[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];a[b+9]=c[9];a[b+10]=c[10];a[b+11]=c[11];a[b+12]=c[12];a[b+13]=c[13];a[b+14]=c[14];a[b+15]=c[15];return a},getPosition:function(){var a=new THREE.Vector3;return function(){console.warn(\"THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.\");var b=this.elements;return a.set(b[12],b[13],b[14])}}(),setPosition:function(a){var b=\nthis.elements;b[12]=a.x;b[13]=a.y;b[14]=a.z;return this},getInverse:function(a,b){var c=this.elements,d=a.elements,e=d[0],f=d[4],g=d[8],h=d[12],k=d[1],n=d[5],p=d[9],q=d[13],m=d[2],r=d[6],t=d[10],s=d[14],u=d[3],v=d[7],y=d[11],d=d[15];c[0]=p*s*v-q*t*v+q*r*y-n*s*y-p*r*d+n*t*d;c[4]=h*t*v-g*s*v-h*r*y+f*s*y+g*r*d-f*t*d;c[8]=g*q*v-h*p*v+h*n*y-f*q*y-g*n*d+f*p*d;c[12]=h*p*r-g*q*r-h*n*t+f*q*t+g*n*s-f*p*s;c[1]=q*t*u-p*s*u-q*m*y+k*s*y+p*m*d-k*t*d;c[5]=g*s*u-h*t*u+h*m*y-e*s*y-g*m*d+e*t*d;c[9]=h*p*u-g*q*u-h*k*\ny+e*q*y+g*k*d-e*p*d;c[13]=g*q*m-h*p*m+h*k*t-e*q*t-g*k*s+e*p*s;c[2]=n*s*u-q*r*u+q*m*v-k*s*v-n*m*d+k*r*d;c[6]=h*r*u-f*s*u-h*m*v+e*s*v+f*m*d-e*r*d;c[10]=f*q*u-h*n*u+h*k*v-e*q*v-f*k*d+e*n*d;c[14]=h*n*m-f*q*m-h*k*r+e*q*r+f*k*s-e*n*s;c[3]=p*r*u-n*t*u-p*m*v+k*t*v+n*m*y-k*r*y;c[7]=f*t*u-g*r*u+g*m*v-e*t*v-f*m*y+e*r*y;c[11]=g*n*u-f*p*u-g*k*v+e*p*v+f*k*y-e*n*y;c[15]=f*p*m-g*n*m+g*k*r-e*p*r-f*k*t+e*n*t;c=e*c[0]+k*c[4]+m*c[8]+u*c[12];if(0==c){if(b)throw Error(\"Matrix4.getInverse(): can't invert matrix, determinant is 0\");\nconsole.warn(\"Matrix4.getInverse(): can't invert matrix, determinant is 0\");this.identity();return this}this.multiplyScalar(1/c);return this},translate:function(a){console.warn(\"THREE.Matrix4: .translate() has been removed.\")},rotateX:function(a){console.warn(\"THREE.Matrix4: .rotateX() has been removed.\")},rotateY:function(a){console.warn(\"THREE.Matrix4: .rotateY() has been removed.\")},rotateZ:function(a){console.warn(\"THREE.Matrix4: .rotateZ() has been removed.\")},rotateByAxis:function(a,b){console.warn(\"THREE.Matrix4: .rotateByAxis() has been removed.\")},\nscale:function(a){var b=this.elements,c=a.x,d=a.y;a=a.z;b[0]*=c;b[4]*=d;b[8]*=a;b[1]*=c;b[5]*=d;b[9]*=a;b[2]*=c;b[6]*=d;b[10]*=a;b[3]*=c;b[7]*=d;b[11]*=a;return this},getMaxScaleOnAxis:function(){var a=this.elements;return Math.sqrt(Math.max(a[0]*a[0]+a[1]*a[1]+a[2]*a[2],Math.max(a[4]*a[4]+a[5]*a[5]+a[6]*a[6],a[8]*a[8]+a[9]*a[9]+a[10]*a[10])))},makeTranslation:function(a,b,c){this.set(1,0,0,a,0,1,0,b,0,0,1,c,0,0,0,1);return this},makeRotationX:function(a){var b=Math.cos(a);a=Math.sin(a);this.set(1,\n0,0,0,0,b,-a,0,0,a,b,0,0,0,0,1);return this},makeRotationY:function(a){var b=Math.cos(a);a=Math.sin(a);this.set(b,0,a,0,0,1,0,0,-a,0,b,0,0,0,0,1);return this},makeRotationZ:function(a){var b=Math.cos(a);a=Math.sin(a);this.set(b,-a,0,0,a,b,0,0,0,0,1,0,0,0,0,1);return this},makeRotationAxis:function(a,b){var c=Math.cos(b),d=Math.sin(b),e=1-c,f=a.x,g=a.y,h=a.z,k=e*f,n=e*g;this.set(k*f+c,k*g-d*h,k*h+d*g,0,k*g+d*h,n*g+c,n*h-d*f,0,k*h-d*g,n*h+d*f,e*h*h+c,0,0,0,0,1);return this},makeScale:function(a,b,c){this.set(a,\n0,0,0,0,b,0,0,0,0,c,0,0,0,0,1);return this},compose:function(a,b,c){this.makeRotationFromQuaternion(b);this.scale(c);this.setPosition(a);return this},decompose:function(){var a=new THREE.Vector3,b=new THREE.Matrix4;return function(c,d,e){var f=this.elements,g=a.set(f[0],f[1],f[2]).length(),h=a.set(f[4],f[5],f[6]).length(),k=a.set(f[8],f[9],f[10]).length();0>this.determinant()&&(g=-g);c.x=f[12];c.y=f[13];c.z=f[14];b.elements.set(this.elements);c=1/g;var f=1/h,n=1/k;b.elements[0]*=c;b.elements[1]*=\nc;b.elements[2]*=c;b.elements[4]*=f;b.elements[5]*=f;b.elements[6]*=f;b.elements[8]*=n;b.elements[9]*=n;b.elements[10]*=n;d.setFromRotationMatrix(b);e.x=g;e.y=h;e.z=k;return this}}(),makeFrustum:function(a,b,c,d,e,f){var g=this.elements;g[0]=2*e/(b-a);g[4]=0;g[8]=(b+a)/(b-a);g[12]=0;g[1]=0;g[5]=2*e/(d-c);g[9]=(d+c)/(d-c);g[13]=0;g[2]=0;g[6]=0;g[10]=-(f+e)/(f-e);g[14]=-2*f*e/(f-e);g[3]=0;g[7]=0;g[11]=-1;g[15]=0;return this},makePerspective:function(a,b,c,d){a=c*Math.tan(THREE.Math.degToRad(.5*a));\nvar e=-a;return this.makeFrustum(e*b,a*b,e,a,c,d)},makeOrthographic:function(a,b,c,d,e,f){var g=this.elements,h=b-a,k=c-d,n=f-e;g[0]=2/h;g[4]=0;g[8]=0;g[12]=-((b+a)/h);g[1]=0;g[5]=2/k;g[9]=0;g[13]=-((c+d)/k);g[2]=0;g[6]=0;g[10]=-2/n;g[14]=-((f+e)/n);g[3]=0;g[7]=0;g[11]=0;g[15]=1;return this},fromArray:function(a){this.elements.set(a);return this},toArray:function(){var a=this.elements;return[a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]]},clone:function(){return(new THREE.Matrix4).fromArray(this.elements)}};\nTHREE.Ray=function(a,b){this.origin=void 0!==a?a:new THREE.Vector3;this.direction=void 0!==b?b:new THREE.Vector3};\nTHREE.Ray.prototype={constructor:THREE.Ray,set:function(a,b){this.origin.copy(a);this.direction.copy(b);return this},copy:function(a){this.origin.copy(a.origin);this.direction.copy(a.direction);return this},at:function(a,b){return(b||new THREE.Vector3).copy(this.direction).multiplyScalar(a).add(this.origin)},recast:function(){var a=new THREE.Vector3;return function(b){this.origin.copy(this.at(b,a));return this}}(),closestPointToPoint:function(a,b){var c=b||new THREE.Vector3;c.subVectors(a,this.origin);\nvar d=c.dot(this.direction);return 0>d?c.copy(this.origin):c.copy(this.direction).multiplyScalar(d).add(this.origin)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){var c=a.subVectors(b,this.origin).dot(this.direction);if(0>c)return this.origin.distanceTo(b);a.copy(this.direction).multiplyScalar(c).add(this.origin);return a.distanceTo(b)}}(),distanceSqToSegment:function(a,b,c,d){var e=a.clone().add(b).multiplyScalar(.5),f=b.clone().sub(a).normalize(),g=.5*a.distanceTo(b),h=\nthis.origin.clone().sub(e);a=-this.direction.dot(f);b=h.dot(this.direction);var k=-h.dot(f),n=h.lengthSq(),p=Math.abs(1-a*a),q,m;0<=p?(h=a*k-b,q=a*b-k,m=g*p,0<=h?q>=-m?q<=m?(g=1/p,h*=g,q*=g,a=h*(h+a*q+2*b)+q*(a*h+q+2*k)+n):(q=g,h=Math.max(0,-(a*q+b)),a=-h*h+q*(q+2*k)+n):(q=-g,h=Math.max(0,-(a*q+b)),a=-h*h+q*(q+2*k)+n):q<=-m?(h=Math.max(0,-(-a*g+b)),q=0<h?-g:Math.min(Math.max(-g,-k),g),a=-h*h+q*(q+2*k)+n):q<=m?(h=0,q=Math.min(Math.max(-g,-k),g),a=q*(q+2*k)+n):(h=Math.max(0,-(a*g+b)),q=0<h?g:Math.min(Math.max(-g,\n-k),g),a=-h*h+q*(q+2*k)+n)):(q=0<a?-g:g,h=Math.max(0,-(a*q+b)),a=-h*h+q*(q+2*k)+n);c&&c.copy(this.direction.clone().multiplyScalar(h).add(this.origin));d&&d.copy(f.clone().multiplyScalar(q).add(e));return a},isIntersectionSphere:function(a){return this.distanceToPoint(a.center)<=a.radius},intersectSphere:function(){var a=new THREE.Vector3;return function(b,c){a.subVectors(b.center,this.origin);var d=a.dot(this.direction),e=a.dot(a)-d*d,f=b.radius*b.radius;if(e>f)return null;f=Math.sqrt(f-e);e=d-f;\nd+=f;return 0>e&&0>d?null:0>e?this.at(d,c):this.at(e,c)}}(),isIntersectionPlane:function(a){var b=a.distanceToPoint(this.origin);return 0===b||0>a.normal.dot(this.direction)*b?!0:!1},distanceToPlane:function(a){var b=a.normal.dot(this.direction);if(0==b)return 0==a.distanceToPoint(this.origin)?0:null;a=-(this.origin.dot(a.normal)+a.constant)/b;return 0<=a?a:null},intersectPlane:function(a,b){var c=this.distanceToPlane(a);return null===c?null:this.at(c,b)},isIntersectionBox:function(){var a=new THREE.Vector3;\nreturn function(b){return null!==this.intersectBox(b,a)}}(),intersectBox:function(a,b){var c,d,e,f,g;d=1/this.direction.x;f=1/this.direction.y;g=1/this.direction.z;var h=this.origin;0<=d?(c=(a.min.x-h.x)*d,d*=a.max.x-h.x):(c=(a.max.x-h.x)*d,d*=a.min.x-h.x);0<=f?(e=(a.min.y-h.y)*f,f*=a.max.y-h.y):(e=(a.max.y-h.y)*f,f*=a.min.y-h.y);if(c>f||e>d)return null;if(e>c||c!==c)c=e;if(f<d||d!==d)d=f;0<=g?(e=(a.min.z-h.z)*g,g*=a.max.z-h.z):(e=(a.max.z-h.z)*g,g*=a.min.z-h.z);if(c>g||e>d)return null;if(e>c||c!==\nc)c=e;if(g<d||d!==d)d=g;return 0>d?null:this.at(0<=c?c:d,b)},intersectTriangle:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3,d=new THREE.Vector3;return function(e,f,g,h,k){b.subVectors(f,e);c.subVectors(g,e);d.crossVectors(b,c);f=this.direction.dot(d);if(0<f){if(h)return null;h=1}else if(0>f)h=-1,f=-f;else return null;a.subVectors(this.origin,e);e=h*this.direction.dot(c.crossVectors(a,c));if(0>e)return null;g=h*this.direction.dot(b.cross(a));if(0>g||e+g>f)return null;\ne=-h*a.dot(d);return 0>e?null:this.at(e/f,k)}}(),applyMatrix4:function(a){this.direction.add(this.origin).applyMatrix4(a);this.origin.applyMatrix4(a);this.direction.sub(this.origin);this.direction.normalize();return this},equals:function(a){return a.origin.equals(this.origin)&&a.direction.equals(this.direction)},clone:function(){return(new THREE.Ray).copy(this)}};THREE.Sphere=function(a,b){this.center=void 0!==a?a:new THREE.Vector3;this.radius=void 0!==b?b:0};\nTHREE.Sphere.prototype={constructor:THREE.Sphere,set:function(a,b){this.center.copy(a);this.radius=b;return this},setFromPoints:function(){var a=new THREE.Box3;return function(b,c){var d=this.center;void 0!==c?d.copy(c):a.setFromPoints(b).center(d);for(var e=0,f=0,g=b.length;f<g;f++)e=Math.max(e,d.distanceToSquared(b[f]));this.radius=Math.sqrt(e);return this}}(),copy:function(a){this.center.copy(a.center);this.radius=a.radius;return this},empty:function(){return 0>=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<=\nthis.radius*this.radius},distanceToPoint:function(a){return a.distanceTo(this.center)-this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},clampPoint:function(a,b){var c=this.center.distanceToSquared(a),d=b||new THREE.Vector3;d.copy(a);c>this.radius*this.radius&&(d.sub(this.center).normalize(),d.multiplyScalar(this.radius).add(this.center));return d},getBoundingBox:function(a){a=a||new THREE.Box3;a.set(this.center,this.center);a.expandByScalar(this.radius);\nreturn a},applyMatrix4:function(a){this.center.applyMatrix4(a);this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&a.radius===this.radius},clone:function(){return(new THREE.Sphere).copy(this)}};\nTHREE.Frustum=function(a,b,c,d,e,f){this.planes=[void 0!==a?a:new THREE.Plane,void 0!==b?b:new THREE.Plane,void 0!==c?c:new THREE.Plane,void 0!==d?d:new THREE.Plane,void 0!==e?e:new THREE.Plane,void 0!==f?f:new THREE.Plane]};\nTHREE.Frustum.prototype={constructor:THREE.Frustum,set:function(a,b,c,d,e,f){var g=this.planes;g[0].copy(a);g[1].copy(b);g[2].copy(c);g[3].copy(d);g[4].copy(e);g[5].copy(f);return this},copy:function(a){for(var b=this.planes,c=0;6>c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements;a=c[0];var d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],k=c[6],n=c[7],p=c[8],q=c[9],m=c[10],r=c[11],t=c[12],s=c[13],u=c[14],c=c[15];b[0].setComponents(f-a,n-g,r-p,c-t).normalize();b[1].setComponents(f+\na,n+g,r+p,c+t).normalize();b[2].setComponents(f+d,n+h,r+q,c+s).normalize();b[3].setComponents(f-d,n-h,r-q,c-s).normalize();b[4].setComponents(f-e,n-k,r-m,c-u).normalize();b[5].setComponents(f+e,n+k,r+m,c+u).normalize();return this},intersectsObject:function(){var a=new THREE.Sphere;return function(b){var c=b.geometry;null===c.boundingSphere&&c.computeBoundingSphere();a.copy(c.boundingSphere);a.applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSphere:function(a){var b=this.planes,\nc=a.center;a=-a.radius;for(var d=0;6>d;d++)if(b[d].distanceToPoint(c)<a)return!1;return!0},intersectsBox:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c){for(var d=this.planes,e=0;6>e;e++){var f=d[e];a.x=0<f.normal.x?c.min.x:c.max.x;b.x=0<f.normal.x?c.max.x:c.min.x;a.y=0<f.normal.y?c.min.y:c.max.y;b.y=0<f.normal.y?c.max.y:c.min.y;a.z=0<f.normal.z?c.min.z:c.max.z;b.z=0<f.normal.z?c.max.z:c.min.z;var g=f.distanceToPoint(a),f=f.distanceToPoint(b);if(0>g&&0>f)return!1}return!0}}(),\ncontainsPoint:function(a){for(var b=this.planes,c=0;6>c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0},clone:function(){return(new THREE.Frustum).copy(this)}};THREE.Plane=function(a,b){this.normal=void 0!==a?a:new THREE.Vector3(1,0,0);this.constant=void 0!==b?b:0};\nTHREE.Plane.prototype={constructor:THREE.Plane,set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d,\nc);return this}}(),copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){return this.orthoPoint(a,b).sub(a).negate()},orthoPoint:function(a,\nb){var c=this.distanceToPoint(a);return(b||new THREE.Vector3).copy(this.normal).multiplyScalar(c)},isIntersectionLine:function(a){var b=this.distanceToPoint(a.start);a=this.distanceToPoint(a.end);return 0>b&&0<a||0>a&&0<b},intersectLine:function(){var a=new THREE.Vector3;return function(b,c){var d=c||new THREE.Vector3,e=b.delta(a),f=this.normal.dot(e);if(0==f){if(0==this.distanceToPoint(b.start))return d.copy(b.start)}else return f=-(b.start.dot(this.normal)+this.constant)/f,0>f||1<f?void 0:d.copy(e).multiplyScalar(f).add(b.start)}}(),\ncoplanarPoint:function(a){return(a||new THREE.Vector3).copy(this.normal).multiplyScalar(-this.constant)},applyMatrix4:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Matrix3;return function(d,e){var f=e||c.getNormalMatrix(d),f=a.copy(this.normal).applyMatrix3(f),g=this.coplanarPoint(b);g.applyMatrix4(d);this.setFromNormalAndCoplanarPoint(f,g);return this}}(),translate:function(a){this.constant-=a.dot(this.normal);return this},equals:function(a){return a.normal.equals(this.normal)&&\na.constant==this.constant},clone:function(){return(new THREE.Plane).copy(this)}};\nTHREE.Math={generateUUID:function(){var a=\"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\".split(\"\"),b=Array(36),c=0,d;return function(){for(var e=0;36>e;e++)8==e||13==e||18==e||23==e?b[e]=\"-\":14==e?b[e]=\"4\":(2>=c&&(c=33554432+16777216*Math.random()|0),d=c&15,c>>=4,b[e]=a[19==e?d&3|8:d]);return b.join(\"\")}}(),clamp:function(a,b,c){return a<b?b:a>c?c:a},clampBottom:function(a,b){return a<b?b:a},mapLinear:function(a,b,c,d,e){return d+(a-b)*(e-d)/(c-b)},smoothstep:function(a,b,c){if(a<=\nb)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},random16:function(){return(65280*Math.random()+255*Math.random())/65535},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a,b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(.5-Math.random())},degToRad:function(){var a=Math.PI/180;return function(b){return b*a}}(),radToDeg:function(){var a=\n180/Math.PI;return function(b){return b*a}}(),isPowerOfTwo:function(a){return 0===(a&a-1)&&0!==a}};\nTHREE.Spline=function(a){function b(a,b,c,d,e,f,g){a=.5*(c-a);d=.5*(d-b);return(2*(b-c)+a+d)*g+(-3*(b-c)-2*a-d)*f+a*e+b}this.points=a;var c=[],d={x:0,y:0,z:0},e,f,g,h,k,n,p,q,m;this.initFromArray=function(a){this.points=[];for(var b=0;b<a.length;b++)this.points[b]={x:a[b][0],y:a[b][1],z:a[b][2]}};this.getPoint=function(a){e=(this.points.length-1)*a;f=Math.floor(e);g=e-f;c[0]=0===f?f:f-1;c[1]=f;c[2]=f>this.points.length-2?this.points.length-1:f+1;c[3]=f>this.points.length-3?this.points.length-1:f+\n2;n=this.points[c[0]];p=this.points[c[1]];q=this.points[c[2]];m=this.points[c[3]];h=g*g;k=g*h;d.x=b(n.x,p.x,q.x,m.x,g,h,k);d.y=b(n.y,p.y,q.y,m.y,g,h,k);d.z=b(n.z,p.z,q.z,m.z,g,h,k);return d};this.getControlPointsArray=function(){var a,b,c=this.points.length,d=[];for(a=0;a<c;a++)b=this.points[a],d[a]=[b.x,b.y,b.z];return d};this.getLength=function(a){var b,c,d,e=b=b=0,f=new THREE.Vector3,g=new THREE.Vector3,h=[],k=0;h[0]=0;a||(a=100);c=this.points.length*a;f.copy(this.points[0]);for(a=1;a<c;a++)b=\na/c,d=this.getPoint(b),g.copy(d),k+=g.distanceTo(f),f.copy(d),b*=this.points.length-1,b=Math.floor(b),b!=e&&(h[b]=k,e=b);h[h.length]=k;return{chunks:h,total:k}};this.reparametrizeByArcLength=function(a){var b,c,d,e,f,g,h=[],k=new THREE.Vector3,m=this.getLength();h.push(k.copy(this.points[0]).clone());for(b=1;b<this.points.length;b++){c=m.chunks[b]-m.chunks[b-1];g=Math.ceil(a*c/m.total);e=(b-1)/(this.points.length-1);f=b/(this.points.length-1);for(c=1;c<g-1;c++)d=e+1/g*c*(f-e),d=this.getPoint(d),h.push(k.copy(d).clone());\nh.push(k.copy(this.points[b]).clone())}this.points=h}};THREE.Triangle=function(a,b,c){this.a=void 0!==a?a:new THREE.Vector3;this.b=void 0!==b?b:new THREE.Vector3;this.c=void 0!==c?c:new THREE.Vector3};THREE.Triangle.normal=function(){var a=new THREE.Vector3;return function(b,c,d,e){e=e||new THREE.Vector3;e.subVectors(d,c);a.subVectors(b,c);e.cross(a);b=e.lengthSq();return 0<b?e.multiplyScalar(1/Math.sqrt(b)):e.set(0,0,0)}}();\nTHREE.Triangle.barycoordFromPoint=function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3;return function(d,e,f,g,h){a.subVectors(g,e);b.subVectors(f,e);c.subVectors(d,e);d=a.dot(a);e=a.dot(b);f=a.dot(c);var k=b.dot(b);g=b.dot(c);var n=d*k-e*e;h=h||new THREE.Vector3;if(0==n)return h.set(-2,-1,-1);n=1/n;k=(k*f-e*g)*n;d=(d*g-e*f)*n;return h.set(1-k-d,d,k)}}();\nTHREE.Triangle.containsPoint=function(){var a=new THREE.Vector3;return function(b,c,d,e){b=THREE.Triangle.barycoordFromPoint(b,c,d,e,a);return 0<=b.x&&0<=b.y&&1>=b.x+b.y}}();\nTHREE.Triangle.prototype={constructor:THREE.Triangle,set:function(a,b,c){this.a.copy(a);this.b.copy(b);this.c.copy(c);return this},setFromPointsAndIndices:function(a,b,c,d){this.a.copy(a[b]);this.b.copy(a[c]);this.c.copy(a[d]);return this},copy:function(a){this.a.copy(a.a);this.b.copy(a.b);this.c.copy(a.c);return this},area:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(){a.subVectors(this.c,this.b);b.subVectors(this.a,this.b);return.5*a.cross(b).length()}}(),midpoint:function(a){return(a||\nnew THREE.Vector3).addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)},normal:function(a){return THREE.Triangle.normal(this.a,this.b,this.c,a)},plane:function(a){return(a||new THREE.Plane).setFromCoplanarPoints(this.a,this.b,this.c)},barycoordFromPoint:function(a,b){return THREE.Triangle.barycoordFromPoint(a,this.a,this.b,this.c,b)},containsPoint:function(a){return THREE.Triangle.containsPoint(a,this.a,this.b,this.c)},equals:function(a){return a.a.equals(this.a)&&a.b.equals(this.b)&&a.c.equals(this.c)},\nclone:function(){return(new THREE.Triangle).copy(this)}};THREE.Clock=function(a){this.autoStart=void 0!==a?a:!0;this.elapsedTime=this.oldTime=this.startTime=0;this.running=!1};\nTHREE.Clock.prototype={constructor:THREE.Clock,start:function(){this.oldTime=this.startTime=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now();this.running=!0},stop:function(){this.getElapsedTime();this.running=!1},getElapsedTime:function(){this.getDelta();return this.elapsedTime},getDelta:function(){var a=0;this.autoStart&&!this.running&&this.start();if(this.running){var b=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now(),\na=.001*(b-this.oldTime);this.oldTime=b;this.elapsedTime+=a}return a}};THREE.EventDispatcher=function(){};\nTHREE.EventDispatcher.prototype={constructor:THREE.EventDispatcher,apply:function(a){a.addEventListener=THREE.EventDispatcher.prototype.addEventListener;a.hasEventListener=THREE.EventDispatcher.prototype.hasEventListener;a.removeEventListener=THREE.EventDispatcher.prototype.removeEventListener;a.dispatchEvent=THREE.EventDispatcher.prototype.dispatchEvent},addEventListener:function(a,b){void 0===this._listeners&&(this._listeners={});var c=this._listeners;void 0===c[a]&&(c[a]=[]);-1===c[a].indexOf(b)&&\nc[a].push(b)},hasEventListener:function(a,b){if(void 0===this._listeners)return!1;var c=this._listeners;return void 0!==c[a]&&-1!==c[a].indexOf(b)?!0:!1},removeEventListener:function(a,b){if(void 0!==this._listeners){var c=this._listeners[a];if(void 0!==c){var d=c.indexOf(b);-1!==d&&c.splice(d,1)}}},dispatchEvent:function(a){if(void 0!==this._listeners){var b=this._listeners[a.type];if(void 0!==b){a.target=this;for(var c=[],d=b.length,e=0;e<d;e++)c[e]=b[e];for(e=0;e<d;e++)c[e].call(this,a)}}}};\n(function(a){a.Raycaster=function(b,c,f,g){this.ray=new a.Ray(b,c);this.near=f||0;this.far=g||Infinity;this.params={Sprite:{},Mesh:{},PointCloud:{threshold:1},LOD:{},Line:{}}};var b=function(a,b){return a.distance-b.distance},c=function(a,b,f,g){a.raycast(b,f);if(!0===g){a=a.children;g=0;for(var h=a.length;g<h;g++)c(a[g],b,f,!0)}};a.Raycaster.prototype={constructor:a.Raycaster,precision:1E-4,linePrecision:1,set:function(a,b){this.ray.set(a,b)},intersectObject:function(a,e){var f=[];c(a,this,f,e);\nf.sort(b);return f},intersectObjects:function(a,e){var f=[];if(!1===a instanceof Array)return console.log(\"THREE.Raycaster.intersectObjects: objects is not an Array.\"),f;for(var g=0,h=a.length;g<h;g++)c(a[g],this,f,e);f.sort(b);return f}}})(THREE);\nTHREE.Object3D=function(){Object.defineProperty(this,\"id\",{value:THREE.Object3DIdCount++});this.uuid=THREE.Math.generateUUID();this.name=\"\";this.type=\"Object3D\";this.parent=void 0;this.children=[];this.up=THREE.Object3D.DefaultUp.clone();var a=new THREE.Vector3,b=new THREE.Euler,c=new THREE.Quaternion,d=new THREE.Vector3(1,1,1);b.onChange(function(){c.setFromEuler(b,!1)});c.onChange(function(){b.setFromQuaternion(c,void 0,!1)});Object.defineProperties(this,{position:{enumerable:!0,value:a},rotation:{enumerable:!0,\nvalue:b},quaternion:{enumerable:!0,value:c},scale:{enumerable:!0,value:d}});this.renderDepth=null;this.rotationAutoUpdate=!0;this.matrix=new THREE.Matrix4;this.matrixWorld=new THREE.Matrix4;this.matrixAutoUpdate=!0;this.matrixWorldNeedsUpdate=!1;this.visible=!0;this.receiveShadow=this.castShadow=!1;this.frustumCulled=!0;this.userData={}};THREE.Object3D.DefaultUp=new THREE.Vector3(0,1,0);\nTHREE.Object3D.prototype={constructor:THREE.Object3D,get eulerOrder(){console.warn(\"THREE.Object3D: .eulerOrder has been moved to .rotation.order.\");return this.rotation.order},set eulerOrder(a){console.warn(\"THREE.Object3D: .eulerOrder has been moved to .rotation.order.\");this.rotation.order=a},get useQuaternion(){console.warn(\"THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.\")},set useQuaternion(a){console.warn(\"THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.\")},\napplyMatrix:function(a){this.matrix.multiplyMatrices(a,this.matrix);this.matrix.decompose(this.position,this.quaternion,this.scale)},setRotationFromAxisAngle:function(a,b){this.quaternion.setFromAxisAngle(a,b)},setRotationFromEuler:function(a){this.quaternion.setFromEuler(a,!0)},setRotationFromMatrix:function(a){this.quaternion.setFromRotationMatrix(a)},setRotationFromQuaternion:function(a){this.quaternion.copy(a)},rotateOnAxis:function(){var a=new THREE.Quaternion;return function(b,c){a.setFromAxisAngle(b,\nc);this.quaternion.multiply(a);return this}}(),rotateX:function(){var a=new THREE.Vector3(1,0,0);return function(b){return this.rotateOnAxis(a,b)}}(),rotateY:function(){var a=new THREE.Vector3(0,1,0);return function(b){return this.rotateOnAxis(a,b)}}(),rotateZ:function(){var a=new THREE.Vector3(0,0,1);return function(b){return this.rotateOnAxis(a,b)}}(),translateOnAxis:function(){var a=new THREE.Vector3;return function(b,c){a.copy(b).applyQuaternion(this.quaternion);this.position.add(a.multiplyScalar(c));\nreturn this}}(),translate:function(a,b){console.warn(\"THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.\");return this.translateOnAxis(b,a)},translateX:function(){var a=new THREE.Vector3(1,0,0);return function(b){return this.translateOnAxis(a,b)}}(),translateY:function(){var a=new THREE.Vector3(0,1,0);return function(b){return this.translateOnAxis(a,b)}}(),translateZ:function(){var a=new THREE.Vector3(0,0,1);return function(b){return this.translateOnAxis(a,\nb)}}(),localToWorld:function(a){return a.applyMatrix4(this.matrixWorld)},worldToLocal:function(){var a=new THREE.Matrix4;return function(b){return b.applyMatrix4(a.getInverse(this.matrixWorld))}}(),lookAt:function(){var a=new THREE.Matrix4;return function(b){a.lookAt(b,this.position,this.up);this.quaternion.setFromRotationMatrix(a)}}(),add:function(a){if(1<arguments.length){for(var b=0;b<arguments.length;b++)this.add(arguments[b]);return this}if(a===this)return console.error(\"THREE.Object3D.add:\",\na,\"can't be added as a child of itself.\"),this;a instanceof THREE.Object3D?(void 0!==a.parent&&a.parent.remove(a),a.parent=this,a.dispatchEvent({type:\"added\"}),this.children.push(a)):console.error(\"THREE.Object3D.add:\",a,\"is not an instance of THREE.Object3D.\");return this},remove:function(a){if(1<arguments.length)for(var b=0;b<arguments.length;b++)this.remove(arguments[b]);b=this.children.indexOf(a);-1!==b&&(a.parent=void 0,a.dispatchEvent({type:\"removed\"}),this.children.splice(b,1))},getChildByName:function(a,\nb){console.warn(\"THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().\");return this.getObjectByName(a,b)},getObjectById:function(a,b){if(this.id===a)return this;for(var c=0,d=this.children.length;c<d;c++){var e=this.children[c].getObjectById(a,b);if(void 0!==e)return e}},getObjectByName:function(a,b){if(this.name===a)return this;for(var c=0,d=this.children.length;c<d;c++){var e=this.children[c].getObjectByName(a,b);if(void 0!==e)return e}},getWorldPosition:function(a){a=a||new THREE.Vector3;\nthis.updateMatrixWorld(!0);return a.setFromMatrixPosition(this.matrixWorld)},getWorldQuaternion:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c){c=c||new THREE.Quaternion;this.updateMatrixWorld(!0);this.matrixWorld.decompose(a,c,b);return c}}(),getWorldRotation:function(){var a=new THREE.Quaternion;return function(b){b=b||new THREE.Euler;this.getWorldQuaternion(a);return b.setFromQuaternion(a,this.rotation.order,!1)}}(),getWorldScale:function(){var a=new THREE.Vector3,b=new THREE.Quaternion;\nreturn function(c){c=c||new THREE.Vector3;this.updateMatrixWorld(!0);this.matrixWorld.decompose(a,b,c);return c}}(),getWorldDirection:function(){var a=new THREE.Quaternion;return function(b){b=b||new THREE.Vector3;this.getWorldQuaternion(a);return b.set(0,0,1).applyQuaternion(a)}}(),raycast:function(){},traverse:function(a){a(this);for(var b=0,c=this.children.length;b<c;b++)this.children[b].traverse(a)},traverseVisible:function(a){if(!1!==this.visible){a(this);for(var b=0,c=this.children.length;b<\nc;b++)this.children[b].traverseVisible(a)}},updateMatrix:function(){this.matrix.compose(this.position,this.quaternion,this.scale);this.matrixWorldNeedsUpdate=!0},updateMatrixWorld:function(a){!0===this.matrixAutoUpdate&&this.updateMatrix();if(!0===this.matrixWorldNeedsUpdate||!0===a)void 0===this.parent?this.matrixWorld.copy(this.matrix):this.matrixWorld.multiplyMatrices(this.parent.matrixWorld,this.matrix),this.matrixWorldNeedsUpdate=!1,a=!0;for(var b=0,c=this.children.length;b<c;b++)this.children[b].updateMatrixWorld(a)},\ntoJSON:function(){var a={metadata:{version:4.3,type:\"Object\",generator:\"ObjectExporter\"}},b={},c=function(c){void 0===a.geometries&&(a.geometries=[]);if(void 0===b[c.uuid]){var d=c.toJSON();delete d.metadata;b[c.uuid]=d;a.geometries.push(d)}return c.uuid},d={},e=function(b){void 0===a.materials&&(a.materials=[]);if(void 0===d[b.uuid]){var c=b.toJSON();delete c.metadata;d[b.uuid]=c;a.materials.push(c)}return b.uuid},f=function(a){var b={};b.uuid=a.uuid;b.type=a.type;\"\"!==a.name&&(b.name=a.name);\"{}\"!==\nJSON.stringify(a.userData)&&(b.userData=a.userData);!0!==a.visible&&(b.visible=a.visible);a instanceof THREE.PerspectiveCamera?(b.fov=a.fov,b.aspect=a.aspect,b.near=a.near,b.far=a.far):a instanceof THREE.OrthographicCamera?(b.left=a.left,b.right=a.right,b.top=a.top,b.bottom=a.bottom,b.near=a.near,b.far=a.far):a instanceof THREE.AmbientLight?b.color=a.color.getHex():a instanceof THREE.DirectionalLight?(b.color=a.color.getHex(),b.intensity=a.intensity):a instanceof THREE.PointLight?(b.color=a.color.getHex(),\nb.intensity=a.intensity,b.distance=a.distance):a instanceof THREE.SpotLight?(b.color=a.color.getHex(),b.intensity=a.intensity,b.distance=a.distance,b.angle=a.angle,b.exponent=a.exponent):a instanceof THREE.HemisphereLight?(b.color=a.color.getHex(),b.groundColor=a.groundColor.getHex()):a instanceof THREE.Mesh?(b.geometry=c(a.geometry),b.material=e(a.material)):a instanceof THREE.Line?(b.geometry=c(a.geometry),b.material=e(a.material)):a instanceof THREE.Sprite&&(b.material=e(a.material));b.matrix=\na.matrix.toArray();if(0<a.children.length){b.children=[];for(var d=0;d<a.children.length;d++)b.children.push(f(a.children[d]))}return b};a.object=f(this);return a},clone:function(a,b){void 0===a&&(a=new THREE.Object3D);void 0===b&&(b=!0);a.name=this.name;a.up.copy(this.up);a.position.copy(this.position);a.quaternion.copy(this.quaternion);a.scale.copy(this.scale);a.renderDepth=this.renderDepth;a.rotationAutoUpdate=this.rotationAutoUpdate;a.matrix.copy(this.matrix);a.matrixWorld.copy(this.matrixWorld);\na.matrixAutoUpdate=this.matrixAutoUpdate;a.matrixWorldNeedsUpdate=this.matrixWorldNeedsUpdate;a.visible=this.visible;a.castShadow=this.castShadow;a.receiveShadow=this.receiveShadow;a.frustumCulled=this.frustumCulled;a.userData=JSON.parse(JSON.stringify(this.userData));if(!0===b)for(var c=0;c<this.children.length;c++)a.add(this.children[c].clone());return a}};THREE.EventDispatcher.prototype.apply(THREE.Object3D.prototype);THREE.Object3DIdCount=0;\nTHREE.Projector=function(){console.warn(\"THREE.Projector has been moved to /examples/renderers/Projector.js.\");this.projectVector=function(a,b){console.warn(\"THREE.Projector: .projectVector() is now vector.project().\");a.project(b)};this.unprojectVector=function(a,b){console.warn(\"THREE.Projector: .unprojectVector() is now vector.unproject().\");a.unproject(b)};this.pickingRay=function(a,b){console.error(\"THREE.Projector: .pickingRay() has been removed.\")}};\nTHREE.Face3=function(a,b,c,d,e,f){this.a=a;this.b=b;this.c=c;this.normal=d instanceof THREE.Vector3?d:new THREE.Vector3;this.vertexNormals=d instanceof Array?d:[];this.color=e instanceof THREE.Color?e:new THREE.Color;this.vertexColors=e instanceof Array?e:[];this.vertexTangents=[];this.materialIndex=void 0!==f?f:0};\nTHREE.Face3.prototype={constructor:THREE.Face3,clone:function(){var a=new THREE.Face3(this.a,this.b,this.c);a.normal.copy(this.normal);a.color.copy(this.color);a.materialIndex=this.materialIndex;for(var b=0,c=this.vertexNormals.length;b<c;b++)a.vertexNormals[b]=this.vertexNormals[b].clone();b=0;for(c=this.vertexColors.length;b<c;b++)a.vertexColors[b]=this.vertexColors[b].clone();b=0;for(c=this.vertexTangents.length;b<c;b++)a.vertexTangents[b]=this.vertexTangents[b].clone();return a}};\nTHREE.Face4=function(a,b,c,d,e,f,g){console.warn(\"THREE.Face4 has been removed. A THREE.Face3 will be created instead.\");return new THREE.Face3(a,b,c,e,f,g)};THREE.BufferAttribute=function(a,b){this.array=a;this.itemSize=b;this.needsUpdate=!1};\nTHREE.BufferAttribute.prototype={constructor:THREE.BufferAttribute,get length(){return this.array.length},copyAt:function(a,b,c){a*=this.itemSize;c*=b.itemSize;for(var d=0,e=this.itemSize;d<e;d++)this.array[a+d]=b.array[c+d]},set:function(a){this.array.set(a);return this},setX:function(a,b){this.array[a*this.itemSize]=b;return this},setY:function(a,b){this.array[a*this.itemSize+1]=b;return this},setZ:function(a,b){this.array[a*this.itemSize+2]=b;return this},setXY:function(a,b,c){a*=this.itemSize;\nthis.array[a]=b;this.array[a+1]=c;return this},setXYZ:function(a,b,c,d){a*=this.itemSize;this.array[a]=b;this.array[a+1]=c;this.array[a+2]=d;return this},setXYZW:function(a,b,c,d,e){a*=this.itemSize;this.array[a]=b;this.array[a+1]=c;this.array[a+2]=d;this.array[a+3]=e;return this},clone:function(){return new THREE.BufferAttribute(new this.array.constructor(this.array),this.itemSize)}};\nTHREE.Int8Attribute=function(a,b){console.warn(\"THREE.Int8Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};THREE.Uint8Attribute=function(a,b){console.warn(\"THREE.Uint8Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};\nTHREE.Uint8ClampedAttribute=function(a,b){console.warn(\"THREE.Uint8ClampedAttribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};THREE.Int16Attribute=function(a,b){console.warn(\"THREE.Int16Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};\nTHREE.Uint16Attribute=function(a,b){console.warn(\"THREE.Uint16Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};THREE.Int32Attribute=function(a,b){console.warn(\"THREE.Int32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};\nTHREE.Uint32Attribute=function(a,b){console.warn(\"THREE.Uint32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};THREE.Float32Attribute=function(a,b){console.warn(\"THREE.Float32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};\nTHREE.Float64Attribute=function(a,b){console.warn(\"THREE.Float64Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.\");return new THREE.BufferAttribute(a,b)};THREE.BufferGeometry=function(){Object.defineProperty(this,\"id\",{value:THREE.GeometryIdCount++});this.uuid=THREE.Math.generateUUID();this.name=\"\";this.type=\"BufferGeometry\";this.attributes={};this.attributesKeys=[];this.offsets=this.drawcalls=[];this.boundingSphere=this.boundingBox=null};\nTHREE.BufferGeometry.prototype={constructor:THREE.BufferGeometry,addAttribute:function(a,b,c){!1===b instanceof THREE.BufferAttribute?(console.warn(\"THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).\"),this.attributes[a]={array:b,itemSize:c}):(this.attributes[a]=b,this.attributesKeys=Object.keys(this.attributes))},getAttribute:function(a){return this.attributes[a]},addDrawCall:function(a,b,c){this.drawcalls.push({start:a,count:b,index:void 0!==c?c:0})},applyMatrix:function(a){var b=\nthis.attributes.position;void 0!==b&&(a.applyToVector3Array(b.array),b.needsUpdate=!0);b=this.attributes.normal;void 0!==b&&((new THREE.Matrix3).getNormalMatrix(a).applyToVector3Array(b.array),b.needsUpdate=!0)},center:function(){},fromGeometry:function(a,b){b=b||{vertexColors:THREE.NoColors};var c=a.vertices,d=a.faces,e=a.faceVertexUvs,f=b.vertexColors,g=0<e[0].length,h=3==d[0].vertexNormals.length,k=new Float32Array(9*d.length);this.addAttribute(\"position\",new THREE.BufferAttribute(k,3));var n=\nnew Float32Array(9*d.length);this.addAttribute(\"normal\",new THREE.BufferAttribute(n,3));if(f!==THREE.NoColors){var p=new Float32Array(9*d.length);this.addAttribute(\"color\",new THREE.BufferAttribute(p,3))}if(!0===g){var q=new Float32Array(6*d.length);this.addAttribute(\"uv\",new THREE.BufferAttribute(q,2))}for(var m=0,r=0,t=0;m<d.length;m++,r+=6,t+=9){var s=d[m],u=c[s.a],v=c[s.b],y=c[s.c];k[t]=u.x;k[t+1]=u.y;k[t+2]=u.z;k[t+3]=v.x;k[t+4]=v.y;k[t+5]=v.z;k[t+6]=y.x;k[t+7]=y.y;k[t+8]=y.z;!0===h?(u=s.vertexNormals[0],\nv=s.vertexNormals[1],y=s.vertexNormals[2],n[t]=u.x,n[t+1]=u.y,n[t+2]=u.z,n[t+3]=v.x,n[t+4]=v.y,n[t+5]=v.z,n[t+6]=y.x,n[t+7]=y.y,n[t+8]=y.z):(u=s.normal,n[t]=u.x,n[t+1]=u.y,n[t+2]=u.z,n[t+3]=u.x,n[t+4]=u.y,n[t+5]=u.z,n[t+6]=u.x,n[t+7]=u.y,n[t+8]=u.z);f===THREE.FaceColors?(s=s.color,p[t]=s.r,p[t+1]=s.g,p[t+2]=s.b,p[t+3]=s.r,p[t+4]=s.g,p[t+5]=s.b,p[t+6]=s.r,p[t+7]=s.g,p[t+8]=s.b):f===THREE.VertexColors&&(u=s.vertexColors[0],v=s.vertexColors[1],s=s.vertexColors[2],p[t]=u.r,p[t+1]=u.g,p[t+2]=u.b,p[t+3]=\nv.r,p[t+4]=v.g,p[t+5]=v.b,p[t+6]=s.r,p[t+7]=s.g,p[t+8]=s.b);!0===g&&(s=e[0][m][0],u=e[0][m][1],v=e[0][m][2],q[r]=s.x,q[r+1]=s.y,q[r+2]=u.x,q[r+3]=u.y,q[r+4]=v.x,q[r+5]=v.y)}this.computeBoundingSphere();return this},computeBoundingBox:function(){var a=new THREE.Vector3;return function(){null===this.boundingBox&&(this.boundingBox=new THREE.Box3);var b=this.attributes.position.array;if(b){var c=this.boundingBox;c.makeEmpty();for(var d=0,e=b.length;d<e;d+=3)a.set(b[d],b[d+1],b[d+2]),c.expandByPoint(a)}if(void 0===\nb||0===b.length)this.boundingBox.min.set(0,0,0),this.boundingBox.max.set(0,0,0);(isNaN(this.boundingBox.min.x)||isNaN(this.boundingBox.min.y)||isNaN(this.boundingBox.min.z))&&console.error('THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The \"position\" attribute is likely to have NaN values.')}}(),computeBoundingSphere:function(){var a=new THREE.Box3,b=new THREE.Vector3;return function(){null===this.boundingSphere&&(this.boundingSphere=new THREE.Sphere);var c=this.attributes.position.array;\nif(c){a.makeEmpty();for(var d=this.boundingSphere.center,e=0,f=c.length;e<f;e+=3)b.set(c[e],c[e+1],c[e+2]),a.expandByPoint(b);a.center(d);for(var g=0,e=0,f=c.length;e<f;e+=3)b.set(c[e],c[e+1],c[e+2]),g=Math.max(g,d.distanceToSquared(b));this.boundingSphere.radius=Math.sqrt(g);isNaN(this.boundingSphere.radius)&&console.error('THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The \"position\" attribute is likely to have NaN values.')}}}(),computeFaceNormals:function(){},computeVertexNormals:function(){var a=\nthis.attributes;if(a.position){var b=a.position.array;if(void 0===a.normal)this.addAttribute(\"normal\",new THREE.BufferAttribute(new Float32Array(b.length),3));else for(var c=a.normal.array,d=0,e=c.length;d<e;d++)c[d]=0;var c=a.normal.array,f,g,h,k=new THREE.Vector3,n=new THREE.Vector3,p=new THREE.Vector3,q=new THREE.Vector3,m=new THREE.Vector3;if(a.index)for(var r=a.index.array,t=0<this.offsets.length?this.offsets:[{start:0,count:r.length,index:0}],s=0,u=t.length;s<u;++s){e=t[s].start;f=t[s].count;\nfor(var v=t[s].index,d=e,e=e+f;d<e;d+=3)f=3*(v+r[d]),g=3*(v+r[d+1]),h=3*(v+r[d+2]),k.fromArray(b,f),n.fromArray(b,g),p.fromArray(b,h),q.subVectors(p,n),m.subVectors(k,n),q.cross(m),c[f]+=q.x,c[f+1]+=q.y,c[f+2]+=q.z,c[g]+=q.x,c[g+1]+=q.y,c[g+2]+=q.z,c[h]+=q.x,c[h+1]+=q.y,c[h+2]+=q.z}else for(d=0,e=b.length;d<e;d+=9)k.fromArray(b,d),n.fromArray(b,d+3),p.fromArray(b,d+6),q.subVectors(p,n),m.subVectors(k,n),q.cross(m),c[d]=q.x,c[d+1]=q.y,c[d+2]=q.z,c[d+3]=q.x,c[d+4]=q.y,c[d+5]=q.z,c[d+6]=q.x,c[d+7]=q.y,\nc[d+8]=q.z;this.normalizeNormals();a.normal.needsUpdate=!0}},computeTangents:function(){function a(a,b,c){q.fromArray(d,3*a);m.fromArray(d,3*b);r.fromArray(d,3*c);t.fromArray(f,2*a);s.fromArray(f,2*b);u.fromArray(f,2*c);v=m.x-q.x;y=r.x-q.x;G=m.y-q.y;w=r.y-q.y;K=m.z-q.z;x=r.z-q.z;D=s.x-t.x;E=u.x-t.x;A=s.y-t.y;B=u.y-t.y;F=1/(D*B-E*A);R.set((B*v-A*y)*F,(B*G-A*w)*F,(B*K-A*x)*F);H.set((D*y-E*v)*F,(D*w-E*G)*F,(D*x-E*K)*F);k[a].add(R);k[b].add(R);k[c].add(R);n[a].add(H);n[b].add(H);n[c].add(H)}function b(a){ya.fromArray(e,\n3*a);P.copy(ya);Fa=k[a];la.copy(Fa);la.sub(ya.multiplyScalar(ya.dot(Fa))).normalize();ma.crossVectors(P,Fa);za=ma.dot(n[a]);Ga=0>za?-1:1;h[4*a]=la.x;h[4*a+1]=la.y;h[4*a+2]=la.z;h[4*a+3]=Ga}if(void 0===this.attributes.index||void 0===this.attributes.position||void 0===this.attributes.normal||void 0===this.attributes.uv)console.warn(\"Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()\");else{var c=this.attributes.index.array,d=this.attributes.position.array,\ne=this.attributes.normal.array,f=this.attributes.uv.array,g=d.length/3;void 0===this.attributes.tangent&&this.addAttribute(\"tangent\",new THREE.BufferAttribute(new Float32Array(4*g),4));for(var h=this.attributes.tangent.array,k=[],n=[],p=0;p<g;p++)k[p]=new THREE.Vector3,n[p]=new THREE.Vector3;var q=new THREE.Vector3,m=new THREE.Vector3,r=new THREE.Vector3,t=new THREE.Vector2,s=new THREE.Vector2,u=new THREE.Vector2,v,y,G,w,K,x,D,E,A,B,F,R=new THREE.Vector3,H=new THREE.Vector3,C,T,Q,O,S;0===this.drawcalls.length&&\nthis.addDrawCall(0,c.length,0);var X=this.drawcalls,p=0;for(T=X.length;p<T;++p){C=X[p].start;Q=X[p].count;var Y=X[p].index,g=C;for(C+=Q;g<C;g+=3)Q=Y+c[g],O=Y+c[g+1],S=Y+c[g+2],a(Q,O,S)}var la=new THREE.Vector3,ma=new THREE.Vector3,ya=new THREE.Vector3,P=new THREE.Vector3,Ga,Fa,za,p=0;for(T=X.length;p<T;++p)for(C=X[p].start,Q=X[p].count,Y=X[p].index,g=C,C+=Q;g<C;g+=3)Q=Y+c[g],O=Y+c[g+1],S=Y+c[g+2],b(Q),b(O),b(S)}},computeOffsets:function(a){var b=a;void 0===a&&(b=65535);Date.now();a=this.attributes.index.array;\nfor(var c=this.attributes.position.array,d=a.length/3,e=new Uint16Array(a.length),f=0,g=0,h=[{start:0,count:0,index:0}],k=h[0],n=0,p=0,q=new Int32Array(6),m=new Int32Array(c.length),r=new Int32Array(c.length),t=0;t<c.length;t++)m[t]=-1,r[t]=-1;for(c=0;c<d;c++){for(var s=p=0;3>s;s++)t=a[3*c+s],-1==m[t]?(q[2*s]=t,q[2*s+1]=-1,p++):m[t]<k.index?(q[2*s]=t,q[2*s+1]=-1,n++):(q[2*s]=t,q[2*s+1]=m[t]);if(g+p>k.index+b)for(k={start:f,count:0,index:g},h.push(k),p=0;6>p;p+=2)s=q[p+1],-1<s&&s<k.index&&(q[p+1]=\n-1);for(p=0;6>p;p+=2)t=q[p],s=q[p+1],-1===s&&(s=g++),m[t]=s,r[s]=t,e[f++]=s-k.index,k.count++}this.reorderBuffers(e,r,g);return this.offsets=h},merge:function(){console.log(\"BufferGeometry.merge(): TODO\")},normalizeNormals:function(){for(var a=this.attributes.normal.array,b,c,d,e=0,f=a.length;e<f;e+=3)b=a[e],c=a[e+1],d=a[e+2],b=1/Math.sqrt(b*b+c*c+d*d),a[e]*=b,a[e+1]*=b,a[e+2]*=b},reorderBuffers:function(a,b,c){var d={},e;for(e in this.attributes)\"index\"!=e&&(d[e]=new this.attributes[e].array.constructor(this.attributes[e].itemSize*\nc));for(var f=0;f<c;f++){var g=b[f];for(e in this.attributes)if(\"index\"!=e)for(var h=this.attributes[e].array,k=this.attributes[e].itemSize,n=d[e],p=0;p<k;p++)n[f*k+p]=h[g*k+p]}this.attributes.index.array=a;for(e in this.attributes)\"index\"!=e&&(this.attributes[e].array=d[e],this.attributes[e].numItems=this.attributes[e].itemSize*c)},toJSON:function(){var a={metadata:{version:4,type:\"BufferGeometry\",generator:\"BufferGeometryExporter\"},uuid:this.uuid,type:this.type,data:{attributes:{}}},b=this.attributes,\nc=this.offsets,d=this.boundingSphere,e;for(e in b){for(var f=b[e],g=[],h=f.array,k=0,n=h.length;k<n;k++)g[k]=h[k];a.data.attributes[e]={itemSize:f.itemSize,type:f.array.constructor.name,array:g}}0<c.length&&(a.data.offsets=JSON.parse(JSON.stringify(c)));null!==d&&(a.data.boundingSphere={center:d.center.toArray(),radius:d.radius});return a},clone:function(){var a=new THREE.BufferGeometry,b;for(b in this.attributes)a.addAttribute(b,this.attributes[b].clone());b=0;for(var c=this.offsets.length;b<c;b++){var d=\nthis.offsets[b];a.offsets.push({start:d.start,index:d.index,count:d.count})}return a},dispose:function(){this.dispatchEvent({type:\"dispose\"})}};THREE.EventDispatcher.prototype.apply(THREE.BufferGeometry.prototype);\nTHREE.Geometry=function(){Object.defineProperty(this,\"id\",{value:THREE.GeometryIdCount++});this.uuid=THREE.Math.generateUUID();this.name=\"\";this.type=\"Geometry\";this.vertices=[];this.colors=[];this.faces=[];this.faceVertexUvs=[[]];this.morphTargets=[];this.morphColors=[];this.morphNormals=[];this.skinWeights=[];this.skinIndices=[];this.lineDistances=[];this.boundingSphere=this.boundingBox=null;this.hasTangents=!1;this.dynamic=!0;this.groupsNeedUpdate=this.lineDistancesNeedUpdate=this.colorsNeedUpdate=\nthis.tangentsNeedUpdate=this.normalsNeedUpdate=this.uvsNeedUpdate=this.elementsNeedUpdate=this.verticesNeedUpdate=!1};\nTHREE.Geometry.prototype={constructor:THREE.Geometry,applyMatrix:function(a){for(var b=(new THREE.Matrix3).getNormalMatrix(a),c=0,d=this.vertices.length;c<d;c++)this.vertices[c].applyMatrix4(a);c=0;for(d=this.faces.length;c<d;c++){a=this.faces[c];a.normal.applyMatrix3(b).normalize();for(var e=0,f=a.vertexNormals.length;e<f;e++)a.vertexNormals[e].applyMatrix3(b).normalize()}this.boundingBox instanceof THREE.Box3&&this.computeBoundingBox();this.boundingSphere instanceof THREE.Sphere&&this.computeBoundingSphere()},\nfromBufferGeometry:function(a){for(var b=this,c=a.attributes,d=c.position.array,e=void 0!==c.index?c.index.array:void 0,f=void 0!==c.normal?c.normal.array:void 0,g=void 0!==c.color?c.color.array:void 0,h=void 0!==c.uv?c.uv.array:void 0,k=[],n=[],p=c=0;c<d.length;c+=3,p+=2)b.vertices.push(new THREE.Vector3(d[c],d[c+1],d[c+2])),void 0!==f&&k.push(new THREE.Vector3(f[c],f[c+1],f[c+2])),void 0!==g&&b.colors.push(new THREE.Color(g[c],g[c+1],g[c+2])),void 0!==h&&n.push(new THREE.Vector2(h[p],h[p+1]));h=\nfunction(a,c,d){var e=void 0!==f?[k[a].clone(),k[c].clone(),k[d].clone()]:[],h=void 0!==g?[b.colors[a].clone(),b.colors[c].clone(),b.colors[d].clone()]:[];b.faces.push(new THREE.Face3(a,c,d,e,h));b.faceVertexUvs[0].push([n[a],n[c],n[d]])};if(void 0!==e)for(c=0;c<e.length;c+=3)h(e[c],e[c+1],e[c+2]);else for(c=0;c<d.length/3;c+=3)h(c,c+1,c+2);this.computeFaceNormals();null!==a.boundingBox&&(this.boundingBox=a.boundingBox.clone());null!==a.boundingSphere&&(this.boundingSphere=a.boundingSphere.clone());\nreturn this},center:function(){this.computeBoundingBox();var a=new THREE.Vector3;a.addVectors(this.boundingBox.min,this.boundingBox.max);a.multiplyScalar(-.5);this.applyMatrix((new THREE.Matrix4).makeTranslation(a.x,a.y,a.z));this.computeBoundingBox();return a},computeFaceNormals:function(){for(var a=new THREE.Vector3,b=new THREE.Vector3,c=0,d=this.faces.length;c<d;c++){var e=this.faces[c],f=this.vertices[e.a],g=this.vertices[e.b];a.subVectors(this.vertices[e.c],g);b.subVectors(f,g);a.cross(b);a.normalize();\ne.normal.copy(a)}},computeVertexNormals:function(a){var b,c,d;d=Array(this.vertices.length);b=0;for(c=this.vertices.length;b<c;b++)d[b]=new THREE.Vector3;if(a){var e,f,g,h=new THREE.Vector3,k=new THREE.Vector3;new THREE.Vector3;new THREE.Vector3;new THREE.Vector3;a=0;for(b=this.faces.length;a<b;a++)c=this.faces[a],e=this.vertices[c.a],f=this.vertices[c.b],g=this.vertices[c.c],h.subVectors(g,f),k.subVectors(e,f),h.cross(k),d[c.a].add(h),d[c.b].add(h),d[c.c].add(h)}else for(a=0,b=this.faces.length;a<\nb;a++)c=this.faces[a],d[c.a].add(c.normal),d[c.b].add(c.normal),d[c.c].add(c.normal);b=0;for(c=this.vertices.length;b<c;b++)d[b].normalize();a=0;for(b=this.faces.length;a<b;a++)c=this.faces[a],c.vertexNormals[0]=d[c.a].clone(),c.vertexNormals[1]=d[c.b].clone(),c.vertexNormals[2]=d[c.c].clone()},computeMorphNormals:function(){var a,b,c,d,e;c=0;for(d=this.faces.length;c<d;c++)for(e=this.faces[c],e.__originalFaceNormal?e.__originalFaceNormal.copy(e.normal):e.__originalFaceNormal=e.normal.clone(),e.__originalVertexNormals||\n(e.__originalVertexNormals=[]),a=0,b=e.vertexNormals.length;a<b;a++)e.__originalVertexNormals[a]?e.__originalVertexNormals[a].copy(e.vertexNormals[a]):e.__originalVertexNormals[a]=e.vertexNormals[a].clone();var f=new THREE.Geometry;f.faces=this.faces;a=0;for(b=this.morphTargets.length;a<b;a++){if(!this.morphNormals[a]){this.morphNormals[a]={};this.morphNormals[a].faceNormals=[];this.morphNormals[a].vertexNormals=[];e=this.morphNormals[a].faceNormals;var g=this.morphNormals[a].vertexNormals,h,k;c=\n0;for(d=this.faces.length;c<d;c++)h=new THREE.Vector3,k={a:new THREE.Vector3,b:new THREE.Vector3,c:new THREE.Vector3},e.push(h),g.push(k)}g=this.morphNormals[a];f.vertices=this.morphTargets[a].vertices;f.computeFaceNormals();f.computeVertexNormals();c=0;for(d=this.faces.length;c<d;c++)e=this.faces[c],h=g.faceNormals[c],k=g.vertexNormals[c],h.copy(e.normal),k.a.copy(e.vertexNormals[0]),k.b.copy(e.vertexNormals[1]),k.c.copy(e.vertexNormals[2])}c=0;for(d=this.faces.length;c<d;c++)e=this.faces[c],e.normal=\ne.__originalFaceNormal,e.vertexNormals=e.__originalVertexNormals},computeTangents:function(){var a,b,c,d,e,f,g,h,k,n,p,q,m,r,t,s,u,v=[],y=[];c=new THREE.Vector3;var G=new THREE.Vector3,w=new THREE.Vector3,K=new THREE.Vector3,x=new THREE.Vector3;a=0;for(b=this.vertices.length;a<b;a++)v[a]=new THREE.Vector3,y[a]=new THREE.Vector3;a=0;for(b=this.faces.length;a<b;a++)e=this.faces[a],f=this.faceVertexUvs[0][a],d=e.a,u=e.b,e=e.c,g=this.vertices[d],h=this.vertices[u],k=this.vertices[e],n=f[0],p=f[1],q=f[2],\nf=h.x-g.x,m=k.x-g.x,r=h.y-g.y,t=k.y-g.y,h=h.z-g.z,g=k.z-g.z,k=p.x-n.x,s=q.x-n.x,p=p.y-n.y,n=q.y-n.y,q=1/(k*n-s*p),c.set((n*f-p*m)*q,(n*r-p*t)*q,(n*h-p*g)*q),G.set((k*m-s*f)*q,(k*t-s*r)*q,(k*g-s*h)*q),v[d].add(c),v[u].add(c),v[e].add(c),y[d].add(G),y[u].add(G),y[e].add(G);G=[\"a\",\"b\",\"c\",\"d\"];a=0;for(b=this.faces.length;a<b;a++)for(e=this.faces[a],c=0;c<Math.min(e.vertexNormals.length,3);c++)x.copy(e.vertexNormals[c]),d=e[G[c]],u=v[d],w.copy(u),w.sub(x.multiplyScalar(x.dot(u))).normalize(),K.crossVectors(e.vertexNormals[c],\nu),d=K.dot(y[d]),d=0>d?-1:1,e.vertexTangents[c]=new THREE.Vector4(w.x,w.y,w.z,d);this.hasTangents=!0},computeLineDistances:function(){for(var a=0,b=this.vertices,c=0,d=b.length;c<d;c++)0<c&&(a+=b[c].distanceTo(b[c-1])),this.lineDistances[c]=a},computeBoundingBox:function(){null===this.boundingBox&&(this.boundingBox=new THREE.Box3);this.boundingBox.setFromPoints(this.vertices)},computeBoundingSphere:function(){null===this.boundingSphere&&(this.boundingSphere=new THREE.Sphere);this.boundingSphere.setFromPoints(this.vertices)},\nmerge:function(a,b,c){if(!1===a instanceof THREE.Geometry)console.error(\"THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.\",a);else{var d,e=this.vertices.length,f=this.vertices,g=a.vertices,h=this.faces,k=a.faces,n=this.faceVertexUvs[0];a=a.faceVertexUvs[0];void 0===c&&(c=0);void 0!==b&&(d=(new THREE.Matrix3).getNormalMatrix(b));for(var p=0,q=g.length;p<q;p++){var m=g[p].clone();void 0!==b&&m.applyMatrix4(b);f.push(m)}p=0;for(q=k.length;p<q;p++){var g=k[p],r,t=g.vertexNormals,s=\ng.vertexColors,m=new THREE.Face3(g.a+e,g.b+e,g.c+e);m.normal.copy(g.normal);void 0!==d&&m.normal.applyMatrix3(d).normalize();b=0;for(f=t.length;b<f;b++)r=t[b].clone(),void 0!==d&&r.applyMatrix3(d).normalize(),m.vertexNormals.push(r);m.color.copy(g.color);b=0;for(f=s.length;b<f;b++)r=s[b],m.vertexColors.push(r.clone());m.materialIndex=g.materialIndex+c;h.push(m)}p=0;for(q=a.length;p<q;p++)if(c=a[p],d=[],void 0!==c){b=0;for(f=c.length;b<f;b++)d.push(new THREE.Vector2(c[b].x,c[b].y));n.push(d)}}},mergeVertices:function(){var a=\n{},b=[],c=[],d,e=Math.pow(10,4),f,g;f=0;for(g=this.vertices.length;f<g;f++)d=this.vertices[f],d=Math.round(d.x*e)+\"_\"+Math.round(d.y*e)+\"_\"+Math.round(d.z*e),void 0===a[d]?(a[d]=f,b.push(this.vertices[f]),c[f]=b.length-1):c[f]=c[a[d]];a=[];f=0;for(g=this.faces.length;f<g;f++)for(e=this.faces[f],e.a=c[e.a],e.b=c[e.b],e.c=c[e.c],e=[e.a,e.b,e.c],d=0;3>d;d++)if(e[d]==e[(d+1)%3]){a.push(f);break}for(f=a.length-1;0<=f;f--)for(e=a[f],this.faces.splice(e,1),c=0,g=this.faceVertexUvs.length;c<g;c++)this.faceVertexUvs[c].splice(e,\n1);f=this.vertices.length-b.length;this.vertices=b;return f},toJSON:function(){function a(a,b,c){return c?a|1<<b:a&~(1<<b)}function b(a){var b=a.x.toString()+a.y.toString()+a.z.toString();if(void 0!==n[b])return n[b];n[b]=k.length/3;k.push(a.x,a.y,a.z);return n[b]}function c(a){var b=a.r.toString()+a.g.toString()+a.b.toString();if(void 0!==q[b])return q[b];q[b]=p.length;p.push(a.getHex());return q[b]}function d(a){var b=a.x.toString()+a.y.toString();if(void 0!==r[b])return r[b];r[b]=m.length/2;m.push(a.x,\na.y);return r[b]}var e={metadata:{version:4,type:\"BufferGeometry\",generator:\"BufferGeometryExporter\"},uuid:this.uuid,type:this.type};\"\"!==this.name&&(e.name=this.name);if(void 0!==this.parameters){var f=this.parameters,g;for(g in f)void 0!==f[g]&&(e[g]=f[g]);return e}f=[];for(g=0;g<this.vertices.length;g++){var h=this.vertices[g];f.push(h.x,h.y,h.z)}var h=[],k=[],n={},p=[],q={},m=[],r={};for(g=0;g<this.faces.length;g++){var t=this.faces[g],s=void 0!==this.faceVertexUvs[0][g],u=0<t.normal.length(),\nv=0<t.vertexNormals.length,y=1!==t.color.r||1!==t.color.g||1!==t.color.b,G=0<t.vertexColors.length,w=0,w=a(w,0,0),w=a(w,1,!1),w=a(w,2,!1),w=a(w,3,s),w=a(w,4,u),w=a(w,5,v),w=a(w,6,y),w=a(w,7,G);h.push(w);h.push(t.a,t.b,t.c);s&&(s=this.faceVertexUvs[0][g],h.push(d(s[0]),d(s[1]),d(s[2])));u&&h.push(b(t.normal));v&&(u=t.vertexNormals,h.push(b(u[0]),b(u[1]),b(u[2])));y&&h.push(c(t.color));G&&(t=t.vertexColors,h.push(c(t[0]),c(t[1]),c(t[2])))}e.data={};e.data.vertices=f;e.data.normals=k;0<p.length&&(e.data.colors=\np);0<m.length&&(e.data.uvs=[m]);e.data.faces=h;return e},clone:function(){for(var a=new THREE.Geometry,b=this.vertices,c=0,d=b.length;c<d;c++)a.vertices.push(b[c].clone());b=this.faces;c=0;for(d=b.length;c<d;c++)a.faces.push(b[c].clone());b=this.faceVertexUvs[0];c=0;for(d=b.length;c<d;c++){for(var e=b[c],f=[],g=0,h=e.length;g<h;g++)f.push(new THREE.Vector2(e[g].x,e[g].y));a.faceVertexUvs[0].push(f)}return a},dispose:function(){this.dispatchEvent({type:\"dispose\"})}};THREE.EventDispatcher.prototype.apply(THREE.Geometry.prototype);\nTHREE.GeometryIdCount=0;THREE.Camera=function(){THREE.Object3D.call(this);this.type=\"Camera\";this.matrixWorldInverse=new THREE.Matrix4;this.projectionMatrix=new THREE.Matrix4};THREE.Camera.prototype=Object.create(THREE.Object3D.prototype);THREE.Camera.prototype.getWorldDirection=function(){var a=new THREE.Quaternion;return function(b){b=b||new THREE.Vector3;this.getWorldQuaternion(a);return b.set(0,0,-1).applyQuaternion(a)}}();\nTHREE.Camera.prototype.lookAt=function(){var a=new THREE.Matrix4;return function(b){a.lookAt(this.position,b,this.up);this.quaternion.setFromRotationMatrix(a)}}();THREE.Camera.prototype.clone=function(a){void 0===a&&(a=new THREE.Camera);THREE.Object3D.prototype.clone.call(this,a);a.matrixWorldInverse.copy(this.matrixWorldInverse);a.projectionMatrix.copy(this.projectionMatrix);return a};\nTHREE.CubeCamera=function(a,b,c){THREE.Object3D.call(this);this.type=\"CubeCamera\";var d=new THREE.PerspectiveCamera(90,1,a,b);d.up.set(0,-1,0);d.lookAt(new THREE.Vector3(1,0,0));this.add(d);var e=new THREE.PerspectiveCamera(90,1,a,b);e.up.set(0,-1,0);e.lookAt(new THREE.Vector3(-1,0,0));this.add(e);var f=new THREE.PerspectiveCamera(90,1,a,b);f.up.set(0,0,1);f.lookAt(new THREE.Vector3(0,1,0));this.add(f);var g=new THREE.PerspectiveCamera(90,1,a,b);g.up.set(0,0,-1);g.lookAt(new THREE.Vector3(0,-1,0));\nthis.add(g);var h=new THREE.PerspectiveCamera(90,1,a,b);h.up.set(0,-1,0);h.lookAt(new THREE.Vector3(0,0,1));this.add(h);var k=new THREE.PerspectiveCamera(90,1,a,b);k.up.set(0,-1,0);k.lookAt(new THREE.Vector3(0,0,-1));this.add(k);this.renderTarget=new THREE.WebGLRenderTargetCube(c,c,{format:THREE.RGBFormat,magFilter:THREE.LinearFilter,minFilter:THREE.LinearFilter});this.updateCubeMap=function(a,b){var c=this.renderTarget,m=c.generateMipmaps;c.generateMipmaps=!1;c.activeCubeFace=0;a.render(b,d,c);c.activeCubeFace=\n1;a.render(b,e,c);c.activeCubeFace=2;a.render(b,f,c);c.activeCubeFace=3;a.render(b,g,c);c.activeCubeFace=4;a.render(b,h,c);c.generateMipmaps=m;c.activeCubeFace=5;a.render(b,k,c)}};THREE.CubeCamera.prototype=Object.create(THREE.Object3D.prototype);THREE.OrthographicCamera=function(a,b,c,d,e,f){THREE.Camera.call(this);this.type=\"OrthographicCamera\";this.zoom=1;this.left=a;this.right=b;this.top=c;this.bottom=d;this.near=void 0!==e?e:.1;this.far=void 0!==f?f:2E3;this.updateProjectionMatrix()};\nTHREE.OrthographicCamera.prototype=Object.create(THREE.Camera.prototype);THREE.OrthographicCamera.prototype.updateProjectionMatrix=function(){var a=(this.right-this.left)/(2*this.zoom),b=(this.top-this.bottom)/(2*this.zoom),c=(this.right+this.left)/2,d=(this.top+this.bottom)/2;this.projectionMatrix.makeOrthographic(c-a,c+a,d+b,d-b,this.near,this.far)};\nTHREE.OrthographicCamera.prototype.clone=function(){var a=new THREE.OrthographicCamera;THREE.Camera.prototype.clone.call(this,a);a.zoom=this.zoom;a.left=this.left;a.right=this.right;a.top=this.top;a.bottom=this.bottom;a.near=this.near;a.far=this.far;a.projectionMatrix.copy(this.projectionMatrix);return a};\nTHREE.PerspectiveCamera=function(a,b,c,d){THREE.Camera.call(this);this.type=\"PerspectiveCamera\";this.zoom=1;this.fov=void 0!==a?a:50;this.aspect=void 0!==b?b:1;this.near=void 0!==c?c:.1;this.far=void 0!==d?d:2E3;this.updateProjectionMatrix()};THREE.PerspectiveCamera.prototype=Object.create(THREE.Camera.prototype);THREE.PerspectiveCamera.prototype.setLens=function(a,b){void 0===b&&(b=24);this.fov=2*THREE.Math.radToDeg(Math.atan(b/(2*a)));this.updateProjectionMatrix()};\nTHREE.PerspectiveCamera.prototype.setViewOffset=function(a,b,c,d,e,f){this.fullWidth=a;this.fullHeight=b;this.x=c;this.y=d;this.width=e;this.height=f;this.updateProjectionMatrix()};\nTHREE.PerspectiveCamera.prototype.updateProjectionMatrix=function(){var a=THREE.Math.radToDeg(2*Math.atan(Math.tan(.5*THREE.Math.degToRad(this.fov))/this.zoom));if(this.fullWidth){var b=this.fullWidth/this.fullHeight,a=Math.tan(THREE.Math.degToRad(.5*a))*this.near,c=-a,d=b*c,b=Math.abs(b*a-d),c=Math.abs(a-c);this.projectionMatrix.makeFrustum(d+this.x*b/this.fullWidth,d+(this.x+this.width)*b/this.fullWidth,a-(this.y+this.height)*c/this.fullHeight,a-this.y*c/this.fullHeight,this.near,this.far)}else this.projectionMatrix.makePerspective(a,\nthis.aspect,this.near,this.far)};THREE.PerspectiveCamera.prototype.clone=function(){var a=new THREE.PerspectiveCamera;THREE.Camera.prototype.clone.call(this,a);a.zoom=this.zoom;a.fov=this.fov;a.aspect=this.aspect;a.near=this.near;a.far=this.far;a.projectionMatrix.copy(this.projectionMatrix);return a};THREE.Light=function(a){THREE.Object3D.call(this);this.type=\"Light\";this.color=new THREE.Color(a)};THREE.Light.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Light.prototype.clone=function(a){void 0===a&&(a=new THREE.Light);THREE.Object3D.prototype.clone.call(this,a);a.color.copy(this.color);return a};THREE.AmbientLight=function(a){THREE.Light.call(this,a);this.type=\"AmbientLight\"};THREE.AmbientLight.prototype=Object.create(THREE.Light.prototype);THREE.AmbientLight.prototype.clone=function(){var a=new THREE.AmbientLight;THREE.Light.prototype.clone.call(this,a);return a};\nTHREE.AreaLight=function(a,b){THREE.Light.call(this,a);this.type=\"AreaLight\";this.normal=new THREE.Vector3(0,-1,0);this.right=new THREE.Vector3(1,0,0);this.intensity=void 0!==b?b:1;this.height=this.width=1;this.constantAttenuation=1.5;this.linearAttenuation=.5;this.quadraticAttenuation=.1};THREE.AreaLight.prototype=Object.create(THREE.Light.prototype);\nTHREE.DirectionalLight=function(a,b){THREE.Light.call(this,a);this.type=\"DirectionalLight\";this.position.set(0,1,0);this.target=new THREE.Object3D;this.intensity=void 0!==b?b:1;this.onlyShadow=this.castShadow=!1;this.shadowCameraNear=50;this.shadowCameraFar=5E3;this.shadowCameraLeft=-500;this.shadowCameraTop=this.shadowCameraRight=500;this.shadowCameraBottom=-500;this.shadowCameraVisible=!1;this.shadowBias=0;this.shadowDarkness=.5;this.shadowMapHeight=this.shadowMapWidth=512;this.shadowCascade=!1;\nthis.shadowCascadeOffset=new THREE.Vector3(0,0,-1E3);this.shadowCascadeCount=2;this.shadowCascadeBias=[0,0,0];this.shadowCascadeWidth=[512,512,512];this.shadowCascadeHeight=[512,512,512];this.shadowCascadeNearZ=[-1,.99,.998];this.shadowCascadeFarZ=[.99,.998,1];this.shadowCascadeArray=[];this.shadowMatrix=this.shadowCamera=this.shadowMapSize=this.shadowMap=null};THREE.DirectionalLight.prototype=Object.create(THREE.Light.prototype);\nTHREE.DirectionalLight.prototype.clone=function(){var a=new THREE.DirectionalLight;THREE.Light.prototype.clone.call(this,a);a.target=this.target.clone();a.intensity=this.intensity;a.castShadow=this.castShadow;a.onlyShadow=this.onlyShadow;a.shadowCameraNear=this.shadowCameraNear;a.shadowCameraFar=this.shadowCameraFar;a.shadowCameraLeft=this.shadowCameraLeft;a.shadowCameraRight=this.shadowCameraRight;a.shadowCameraTop=this.shadowCameraTop;a.shadowCameraBottom=this.shadowCameraBottom;a.shadowCameraVisible=\nthis.shadowCameraVisible;a.shadowBias=this.shadowBias;a.shadowDarkness=this.shadowDarkness;a.shadowMapWidth=this.shadowMapWidth;a.shadowMapHeight=this.shadowMapHeight;a.shadowCascade=this.shadowCascade;a.shadowCascadeOffset.copy(this.shadowCascadeOffset);a.shadowCascadeCount=this.shadowCascadeCount;a.shadowCascadeBias=this.shadowCascadeBias.slice(0);a.shadowCascadeWidth=this.shadowCascadeWidth.slice(0);a.shadowCascadeHeight=this.shadowCascadeHeight.slice(0);a.shadowCascadeNearZ=this.shadowCascadeNearZ.slice(0);\na.shadowCascadeFarZ=this.shadowCascadeFarZ.slice(0);return a};THREE.HemisphereLight=function(a,b,c){THREE.Light.call(this,a);this.type=\"HemisphereLight\";this.position.set(0,100,0);this.groundColor=new THREE.Color(b);this.intensity=void 0!==c?c:1};THREE.HemisphereLight.prototype=Object.create(THREE.Light.prototype);\nTHREE.HemisphereLight.prototype.clone=function(){var a=new THREE.HemisphereLight;THREE.Light.prototype.clone.call(this,a);a.groundColor.copy(this.groundColor);a.intensity=this.intensity;return a};THREE.PointLight=function(a,b,c){THREE.Light.call(this,a);this.type=\"PointLight\";this.intensity=void 0!==b?b:1;this.distance=void 0!==c?c:0};THREE.PointLight.prototype=Object.create(THREE.Light.prototype);\nTHREE.PointLight.prototype.clone=function(){var a=new THREE.PointLight;THREE.Light.prototype.clone.call(this,a);a.intensity=this.intensity;a.distance=this.distance;return a};\nTHREE.SpotLight=function(a,b,c,d,e){THREE.Light.call(this,a);this.type=\"SpotLight\";this.position.set(0,1,0);this.target=new THREE.Object3D;this.intensity=void 0!==b?b:1;this.distance=void 0!==c?c:0;this.angle=void 0!==d?d:Math.PI/3;this.exponent=void 0!==e?e:10;this.onlyShadow=this.castShadow=!1;this.shadowCameraNear=50;this.shadowCameraFar=5E3;this.shadowCameraFov=50;this.shadowCameraVisible=!1;this.shadowBias=0;this.shadowDarkness=.5;this.shadowMapHeight=this.shadowMapWidth=512;this.shadowMatrix=\nthis.shadowCamera=this.shadowMapSize=this.shadowMap=null};THREE.SpotLight.prototype=Object.create(THREE.Light.prototype);\nTHREE.SpotLight.prototype.clone=function(){var a=new THREE.SpotLight;THREE.Light.prototype.clone.call(this,a);a.target=this.target.clone();a.intensity=this.intensity;a.distance=this.distance;a.angle=this.angle;a.exponent=this.exponent;a.castShadow=this.castShadow;a.onlyShadow=this.onlyShadow;a.shadowCameraNear=this.shadowCameraNear;a.shadowCameraFar=this.shadowCameraFar;a.shadowCameraFov=this.shadowCameraFov;a.shadowCameraVisible=this.shadowCameraVisible;a.shadowBias=this.shadowBias;a.shadowDarkness=\nthis.shadowDarkness;a.shadowMapWidth=this.shadowMapWidth;a.shadowMapHeight=this.shadowMapHeight;return a};THREE.Cache=function(){this.files={}};THREE.Cache.prototype={constructor:THREE.Cache,add:function(a,b){this.files[a]=b},get:function(a){return this.files[a]},remove:function(a){delete this.files[a]},clear:function(){this.files={}}};\nTHREE.Loader=function(a){this.statusDomElement=(this.showStatus=a)?THREE.Loader.prototype.addStatusElement():null;this.imageLoader=new THREE.ImageLoader;this.onLoadStart=function(){};this.onLoadProgress=function(){};this.onLoadComplete=function(){}};\nTHREE.Loader.prototype={constructor:THREE.Loader,crossOrigin:void 0,addStatusElement:function(){var a=document.createElement(\"div\");a.style.position=\"absolute\";a.style.right=\"0px\";a.style.top=\"0px\";a.style.fontSize=\"0.8em\";a.style.textAlign=\"left\";a.style.background=\"rgba(0,0,0,0.25)\";a.style.color=\"#fff\";a.style.width=\"120px\";a.style.padding=\"0.5em 0.5em 0.5em 0.5em\";a.style.zIndex=1E3;a.innerHTML=\"Loading ...\";return a},updateProgress:function(a){var b=\"Loaded \",b=a.total?b+((100*a.loaded/a.total).toFixed(0)+\n\"%\"):b+((a.loaded/1024).toFixed(2)+\" KB\");this.statusDomElement.innerHTML=b},extractUrlBase:function(a){a=a.split(\"/\");if(1===a.length)return\"./\";a.pop();return a.join(\"/\")+\"/\"},initMaterials:function(a,b){for(var c=[],d=0;d<a.length;++d)c[d]=this.createMaterial(a[d],b);return c},needsTangents:function(a){for(var b=0,c=a.length;b<c;b++)if(a[b]instanceof THREE.ShaderMaterial)return!0;return!1},createMaterial:function(a,b){function c(a){a=Math.log(a)/Math.LN2;return Math.pow(2,Math.round(a))}function d(a,\nd,e,g,h,k,s){var u=b+e,v,y=THREE.Loader.Handlers.get(u);null!==y?v=y.load(u):(v=new THREE.Texture,y=f.imageLoader,y.crossOrigin=f.crossOrigin,y.load(u,function(a){if(!1===THREE.Math.isPowerOfTwo(a.width)||!1===THREE.Math.isPowerOfTwo(a.height)){var b=c(a.width),d=c(a.height),e=document.createElement(\"canvas\");e.width=b;e.height=d;e.getContext(\"2d\").drawImage(a,0,0,b,d);v.image=e}else v.image=a;v.needsUpdate=!0}));v.sourceFile=e;g&&(v.repeat.set(g[0],g[1]),1!==g[0]&&(v.wrapS=THREE.RepeatWrapping),\n1!==g[1]&&(v.wrapT=THREE.RepeatWrapping));h&&v.offset.set(h[0],h[1]);k&&(e={repeat:THREE.RepeatWrapping,mirror:THREE.MirroredRepeatWrapping},void 0!==e[k[0]]&&(v.wrapS=e[k[0]]),void 0!==e[k[1]]&&(v.wrapT=e[k[1]]));s&&(v.anisotropy=s);a[d]=v}function e(a){return(255*a[0]<<16)+(255*a[1]<<8)+255*a[2]}var f=this,g=\"MeshLambertMaterial\",h={color:15658734,opacity:1,map:null,lightMap:null,normalMap:null,bumpMap:null,wireframe:!1};if(a.shading){var k=a.shading.toLowerCase();\"phong\"===k?g=\"MeshPhongMaterial\":\n\"basic\"===k&&(g=\"MeshBasicMaterial\")}void 0!==a.blending&&void 0!==THREE[a.blending]&&(h.blending=THREE[a.blending]);if(void 0!==a.transparent||1>a.opacity)h.transparent=a.transparent;void 0!==a.depthTest&&(h.depthTest=a.depthTest);void 0!==a.depthWrite&&(h.depthWrite=a.depthWrite);void 0!==a.visible&&(h.visible=a.visible);void 0!==a.flipSided&&(h.side=THREE.BackSide);void 0!==a.doubleSided&&(h.side=THREE.DoubleSide);void 0!==a.wireframe&&(h.wireframe=a.wireframe);void 0!==a.vertexColors&&(\"face\"===\na.vertexColors?h.vertexColors=THREE.FaceColors:a.vertexColors&&(h.vertexColors=THREE.VertexColors));a.colorDiffuse?h.color=e(a.colorDiffuse):a.DbgColor&&(h.color=a.DbgColor);a.colorSpecular&&(h.specular=e(a.colorSpecular));a.colorAmbient&&(h.ambient=e(a.colorAmbient));a.colorEmissive&&(h.emissive=e(a.colorEmissive));a.transparency&&(h.opacity=a.transparency);a.specularCoef&&(h.shininess=a.specularCoef);a.mapDiffuse&&b&&d(h,\"map\",a.mapDiffuse,a.mapDiffuseRepeat,a.mapDiffuseOffset,a.mapDiffuseWrap,\na.mapDiffuseAnisotropy);a.mapLight&&b&&d(h,\"lightMap\",a.mapLight,a.mapLightRepeat,a.mapLightOffset,a.mapLightWrap,a.mapLightAnisotropy);a.mapBump&&b&&d(h,\"bumpMap\",a.mapBump,a.mapBumpRepeat,a.mapBumpOffset,a.mapBumpWrap,a.mapBumpAnisotropy);a.mapNormal&&b&&d(h,\"normalMap\",a.mapNormal,a.mapNormalRepeat,a.mapNormalOffset,a.mapNormalWrap,a.mapNormalAnisotropy);a.mapSpecular&&b&&d(h,\"specularMap\",a.mapSpecular,a.mapSpecularRepeat,a.mapSpecularOffset,a.mapSpecularWrap,a.mapSpecularAnisotropy);a.mapAlpha&&\nb&&d(h,\"alphaMap\",a.mapAlpha,a.mapAlphaRepeat,a.mapAlphaOffset,a.mapAlphaWrap,a.mapAlphaAnisotropy);a.mapBumpScale&&(h.bumpScale=a.mapBumpScale);a.mapNormal?(g=THREE.ShaderLib.normalmap,k=THREE.UniformsUtils.clone(g.uniforms),k.tNormal.value=h.normalMap,a.mapNormalFactor&&k.uNormalScale.value.set(a.mapNormalFactor,a.mapNormalFactor),h.map&&(k.tDiffuse.value=h.map,k.enableDiffuse.value=!0),h.specularMap&&(k.tSpecular.value=h.specularMap,k.enableSpecular.value=!0),h.lightMap&&(k.tAO.value=h.lightMap,\nk.enableAO.value=!0),k.diffuse.value.setHex(h.color),k.specular.value.setHex(h.specular),k.ambient.value.setHex(h.ambient),k.shininess.value=h.shininess,void 0!==h.opacity&&(k.opacity.value=h.opacity),g=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:k,lights:!0,fog:!0}),h.transparent&&(g.transparent=!0)):g=new THREE[g](h);void 0!==a.DbgName&&(g.name=a.DbgName);return g}};\nTHREE.Loader.Handlers={handlers:[],add:function(a,b){this.handlers.push(a,b)},get:function(a){for(var b=0,c=this.handlers.length;b<c;b+=2){var d=this.handlers[b+1];if(this.handlers[b].test(a))return d}return null}};THREE.XHRLoader=function(a){this.cache=new THREE.Cache;this.manager=void 0!==a?a:THREE.DefaultLoadingManager};\nTHREE.XHRLoader.prototype={constructor:THREE.XHRLoader,load:function(a,b,c,d){var e=this,f=e.cache.get(a);void 0!==f?b&&b(f):(f=new XMLHttpRequest,f.open(\"GET\",a,!0),f.addEventListener(\"load\",function(c){e.cache.add(a,this.response);b&&b(this.response);e.manager.itemEnd(a)},!1),void 0!==c&&f.addEventListener(\"progress\",function(a){c(a)},!1),void 0!==d&&f.addEventListener(\"error\",function(a){d(a)},!1),void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin),void 0!==this.responseType&&(f.responseType=\nthis.responseType),f.send(null),e.manager.itemStart(a))},setResponseType:function(a){this.responseType=a},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.ImageLoader=function(a){this.cache=new THREE.Cache;this.manager=void 0!==a?a:THREE.DefaultLoadingManager};\nTHREE.ImageLoader.prototype={constructor:THREE.ImageLoader,load:function(a,b,c,d){var e=this,f=e.cache.get(a);if(void 0!==f)b(f);else return f=document.createElement(\"img\"),void 0!==b&&f.addEventListener(\"load\",function(c){e.cache.add(a,this);b(this);e.manager.itemEnd(a)},!1),void 0!==c&&f.addEventListener(\"progress\",function(a){c(a)},!1),void 0!==d&&f.addEventListener(\"error\",function(a){d(a)},!1),void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin),f.src=a,e.manager.itemStart(a),f},setCrossOrigin:function(a){this.crossOrigin=\na}};THREE.JSONLoader=function(a){THREE.Loader.call(this,a);this.withCredentials=!1};THREE.JSONLoader.prototype=Object.create(THREE.Loader.prototype);THREE.JSONLoader.prototype.load=function(a,b,c){c=c&&\"string\"===typeof c?c:this.extractUrlBase(a);this.onLoadStart();this.loadAjaxJSON(this,a,b,c)};\nTHREE.JSONLoader.prototype.loadAjaxJSON=function(a,b,c,d,e){var f=new XMLHttpRequest,g=0;f.onreadystatechange=function(){if(f.readyState===f.DONE)if(200===f.status||0===f.status){if(f.responseText){var h=JSON.parse(f.responseText);if(void 0!==h.metadata&&\"scene\"===h.metadata.type){console.error('THREE.JSONLoader: \"'+b+'\" seems to be a Scene. Use THREE.SceneLoader instead.');return}h=a.parse(h,d);c(h.geometry,h.materials)}else console.error('THREE.JSONLoader: \"'+b+'\" seems to be unreachable or the file is empty.');\na.onLoadComplete()}else console.error(\"THREE.JSONLoader: Couldn't load \\\"\"+b+'\" ('+f.status+\")\");else f.readyState===f.LOADING?e&&(0===g&&(g=f.getResponseHeader(\"Content-Length\")),e({total:g,loaded:f.responseText.length})):f.readyState===f.HEADERS_RECEIVED&&void 0!==e&&(g=f.getResponseHeader(\"Content-Length\"))};f.open(\"GET\",b,!0);f.withCredentials=this.withCredentials;f.send(null)};\nTHREE.JSONLoader.prototype.parse=function(a,b){var c=new THREE.Geometry,d=void 0!==a.scale?1/a.scale:1;(function(b){var d,g,h,k,n,p,q,m,r,t,s,u,v,y=a.faces;p=a.vertices;var G=a.normals,w=a.colors,K=0;if(void 0!==a.uvs){for(d=0;d<a.uvs.length;d++)a.uvs[d].length&&K++;for(d=0;d<K;d++)c.faceVertexUvs[d]=[]}k=0;for(n=p.length;k<n;)d=new THREE.Vector3,d.x=p[k++]*b,d.y=p[k++]*b,d.z=p[k++]*b,c.vertices.push(d);k=0;for(n=y.length;k<n;)if(b=y[k++],r=b&1,h=b&2,d=b&8,q=b&16,t=b&32,p=b&64,b&=128,r){r=new THREE.Face3;\nr.a=y[k];r.b=y[k+1];r.c=y[k+3];s=new THREE.Face3;s.a=y[k+1];s.b=y[k+2];s.c=y[k+3];k+=4;h&&(h=y[k++],r.materialIndex=h,s.materialIndex=h);h=c.faces.length;if(d)for(d=0;d<K;d++)for(u=a.uvs[d],c.faceVertexUvs[d][h]=[],c.faceVertexUvs[d][h+1]=[],g=0;4>g;g++)m=y[k++],v=u[2*m],m=u[2*m+1],v=new THREE.Vector2(v,m),2!==g&&c.faceVertexUvs[d][h].push(v),0!==g&&c.faceVertexUvs[d][h+1].push(v);q&&(q=3*y[k++],r.normal.set(G[q++],G[q++],G[q]),s.normal.copy(r.normal));if(t)for(d=0;4>d;d++)q=3*y[k++],t=new THREE.Vector3(G[q++],\nG[q++],G[q]),2!==d&&r.vertexNormals.push(t),0!==d&&s.vertexNormals.push(t);p&&(p=y[k++],p=w[p],r.color.setHex(p),s.color.setHex(p));if(b)for(d=0;4>d;d++)p=y[k++],p=w[p],2!==d&&r.vertexColors.push(new THREE.Color(p)),0!==d&&s.vertexColors.push(new THREE.Color(p));c.faces.push(r);c.faces.push(s)}else{r=new THREE.Face3;r.a=y[k++];r.b=y[k++];r.c=y[k++];h&&(h=y[k++],r.materialIndex=h);h=c.faces.length;if(d)for(d=0;d<K;d++)for(u=a.uvs[d],c.faceVertexUvs[d][h]=[],g=0;3>g;g++)m=y[k++],v=u[2*m],m=u[2*m+1],\nv=new THREE.Vector2(v,m),c.faceVertexUvs[d][h].push(v);q&&(q=3*y[k++],r.normal.set(G[q++],G[q++],G[q]));if(t)for(d=0;3>d;d++)q=3*y[k++],t=new THREE.Vector3(G[q++],G[q++],G[q]),r.vertexNormals.push(t);p&&(p=y[k++],r.color.setHex(w[p]));if(b)for(d=0;3>d;d++)p=y[k++],r.vertexColors.push(new THREE.Color(w[p]));c.faces.push(r)}})(d);(function(){var b=void 0!==a.influencesPerVertex?a.influencesPerVertex:2;if(a.skinWeights)for(var d=0,g=a.skinWeights.length;d<g;d+=b)c.skinWeights.push(new THREE.Vector4(a.skinWeights[d],\n1<b?a.skinWeights[d+1]:0,2<b?a.skinWeights[d+2]:0,3<b?a.skinWeights[d+3]:0));if(a.skinIndices)for(d=0,g=a.skinIndices.length;d<g;d+=b)c.skinIndices.push(new THREE.Vector4(a.skinIndices[d],1<b?a.skinIndices[d+1]:0,2<b?a.skinIndices[d+2]:0,3<b?a.skinIndices[d+3]:0));c.bones=a.bones;c.bones&&0<c.bones.length&&(c.skinWeights.length!==c.skinIndices.length||c.skinIndices.length!==c.vertices.length)&&console.warn(\"When skinning, number of vertices (\"+c.vertices.length+\"), skinIndices (\"+c.skinIndices.length+\n\"), and skinWeights (\"+c.skinWeights.length+\") should match.\");c.animation=a.animation;c.animations=a.animations})();(function(b){if(void 0!==a.morphTargets){var d,g,h,k,n,p;d=0;for(g=a.morphTargets.length;d<g;d++)for(c.morphTargets[d]={},c.morphTargets[d].name=a.morphTargets[d].name,c.morphTargets[d].vertices=[],n=c.morphTargets[d].vertices,p=a.morphTargets[d].vertices,h=0,k=p.length;h<k;h+=3){var q=new THREE.Vector3;q.x=p[h]*b;q.y=p[h+1]*b;q.z=p[h+2]*b;n.push(q)}}if(void 0!==a.morphColors)for(d=\n0,g=a.morphColors.length;d<g;d++)for(c.morphColors[d]={},c.morphColors[d].name=a.morphColors[d].name,c.morphColors[d].colors=[],k=c.morphColors[d].colors,n=a.morphColors[d].colors,b=0,h=n.length;b<h;b+=3)p=new THREE.Color(16755200),p.setRGB(n[b],n[b+1],n[b+2]),k.push(p)})(d);c.computeFaceNormals();c.computeBoundingSphere();if(void 0===a.materials||0===a.materials.length)return{geometry:c};d=this.initMaterials(a.materials,b);this.needsTangents(d)&&c.computeTangents();return{geometry:c,materials:d}};\nTHREE.LoadingManager=function(a,b,c){var d=this,e=0,f=0;this.onLoad=a;this.onProgress=b;this.onError=c;this.itemStart=function(a){f++};this.itemEnd=function(a){e++;if(void 0!==d.onProgress)d.onProgress(a,e,f);if(e===f&&void 0!==d.onLoad)d.onLoad()}};THREE.DefaultLoadingManager=new THREE.LoadingManager;THREE.BufferGeometryLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};\nTHREE.BufferGeometryLoader.prototype={constructor:THREE.BufferGeometryLoader,load:function(a,b,c,d){var e=this,f=new THREE.XHRLoader;f.setCrossOrigin(this.crossOrigin);f.load(a,function(a){b(e.parse(JSON.parse(a)))},c,d)},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(a){var b=new THREE.BufferGeometry,c=a.attributes,d;for(d in c){var e=c[d],f=new self[e.type](e.array);b.addAttribute(d,new THREE.BufferAttribute(f,e.itemSize))}c=a.offsets;void 0!==c&&(b.offsets=JSON.parse(JSON.stringify(c)));\na=a.boundingSphere;void 0!==a&&(c=new THREE.Vector3,void 0!==a.center&&c.fromArray(a.center),b.boundingSphere=new THREE.Sphere(c,a.radius));return b}};THREE.MaterialLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};\nTHREE.MaterialLoader.prototype={constructor:THREE.MaterialLoader,load:function(a,b,c,d){var e=this,f=new THREE.XHRLoader;f.setCrossOrigin(this.crossOrigin);f.load(a,function(a){b(e.parse(JSON.parse(a)))},c,d)},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(a){var b=new THREE[a.type];void 0!==a.color&&b.color.setHex(a.color);void 0!==a.ambient&&b.ambient.setHex(a.ambient);void 0!==a.emissive&&b.emissive.setHex(a.emissive);void 0!==a.specular&&b.specular.setHex(a.specular);void 0!==a.shininess&&\n(b.shininess=a.shininess);void 0!==a.uniforms&&(b.uniforms=a.uniforms);void 0!==a.vertexShader&&(b.vertexShader=a.vertexShader);void 0!==a.fragmentShader&&(b.fragmentShader=a.fragmentShader);void 0!==a.vertexColors&&(b.vertexColors=a.vertexColors);void 0!==a.shading&&(b.shading=a.shading);void 0!==a.blending&&(b.blending=a.blending);void 0!==a.side&&(b.side=a.side);void 0!==a.opacity&&(b.opacity=a.opacity);void 0!==a.transparent&&(b.transparent=a.transparent);void 0!==a.wireframe&&(b.wireframe=a.wireframe);\nif(void 0!==a.materials)for(var c=0,d=a.materials.length;c<d;c++)b.materials.push(this.parse(a.materials[c]));return b}};THREE.ObjectLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};\nTHREE.ObjectLoader.prototype={constructor:THREE.ObjectLoader,load:function(a,b,c,d){var e=this,f=new THREE.XHRLoader(e.manager);f.setCrossOrigin(this.crossOrigin);f.load(a,function(a){b(e.parse(JSON.parse(a)))},c,d)},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(a){var b=this.parseGeometries(a.geometries),c=this.parseMaterials(a.materials);return this.parseObject(a.object,b,c)},parseGeometries:function(a){var b={};if(void 0!==a)for(var c=new THREE.JSONLoader,d=new THREE.BufferGeometryLoader,\ne=0,f=a.length;e<f;e++){var g,h=a[e];switch(h.type){case \"PlaneGeometry\":g=new THREE.PlaneGeometry(h.width,h.height,h.widthSegments,h.heightSegments);break;case \"BoxGeometry\":case \"CubeGeometry\":g=new THREE.BoxGeometry(h.width,h.height,h.depth,h.widthSegments,h.heightSegments,h.depthSegments);break;case \"CircleGeometry\":g=new THREE.CircleGeometry(h.radius,h.segments);break;case \"CylinderGeometry\":g=new THREE.CylinderGeometry(h.radiusTop,h.radiusBottom,h.height,h.radialSegments,h.heightSegments,h.openEnded);\nbreak;case \"SphereGeometry\":g=new THREE.SphereGeometry(h.radius,h.widthSegments,h.heightSegments,h.phiStart,h.phiLength,h.thetaStart,h.thetaLength);break;case \"IcosahedronGeometry\":g=new THREE.IcosahedronGeometry(h.radius,h.detail);break;case \"TorusGeometry\":g=new THREE.TorusGeometry(h.radius,h.tube,h.radialSegments,h.tubularSegments,h.arc);break;case \"TorusKnotGeometry\":g=new THREE.TorusKnotGeometry(h.radius,h.tube,h.radialSegments,h.tubularSegments,h.p,h.q,h.heightScale);break;case \"BufferGeometry\":g=\nd.parse(h.data);break;case \"Geometry\":g=c.parse(h.data).geometry}g.uuid=h.uuid;void 0!==h.name&&(g.name=h.name);b[h.uuid]=g}return b},parseMaterials:function(a){var b={};if(void 0!==a)for(var c=new THREE.MaterialLoader,d=0,e=a.length;d<e;d++){var f=a[d],g=c.parse(f);g.uuid=f.uuid;void 0!==f.name&&(g.name=f.name);b[f.uuid]=g}return b},parseObject:function(){var a=new THREE.Matrix4;return function(b,c,d){var e;switch(b.type){case \"Scene\":e=new THREE.Scene;break;case \"PerspectiveCamera\":e=new THREE.PerspectiveCamera(b.fov,\nb.aspect,b.near,b.far);break;case \"OrthographicCamera\":e=new THREE.OrthographicCamera(b.left,b.right,b.top,b.bottom,b.near,b.far);break;case \"AmbientLight\":e=new THREE.AmbientLight(b.color);break;case \"DirectionalLight\":e=new THREE.DirectionalLight(b.color,b.intensity);break;case \"PointLight\":e=new THREE.PointLight(b.color,b.intensity,b.distance);break;case \"SpotLight\":e=new THREE.SpotLight(b.color,b.intensity,b.distance,b.angle,b.exponent);break;case \"HemisphereLight\":e=new THREE.HemisphereLight(b.color,\nb.groundColor,b.intensity);break;case \"Mesh\":e=c[b.geometry];var f=d[b.material];void 0===e&&console.warn(\"THREE.ObjectLoader: Undefined geometry\",b.geometry);void 0===f&&console.warn(\"THREE.ObjectLoader: Undefined material\",b.material);e=new THREE.Mesh(e,f);break;case \"Line\":e=c[b.geometry];f=d[b.material];void 0===e&&console.warn(\"THREE.ObjectLoader: Undefined geometry\",b.geometry);void 0===f&&console.warn(\"THREE.ObjectLoader: Undefined material\",b.material);e=new THREE.Line(e,f);break;case \"Sprite\":f=\nd[b.material];void 0===f&&console.warn(\"THREE.ObjectLoader: Undefined material\",b.material);e=new THREE.Sprite(f);break;case \"Group\":e=new THREE.Group;break;default:e=new THREE.Object3D}e.uuid=b.uuid;void 0!==b.name&&(e.name=b.name);void 0!==b.matrix?(a.fromArray(b.matrix),a.decompose(e.position,e.quaternion,e.scale)):(void 0!==b.position&&e.position.fromArray(b.position),void 0!==b.rotation&&e.rotation.fromArray(b.rotation),void 0!==b.scale&&e.scale.fromArray(b.scale));void 0!==b.visible&&(e.visible=\nb.visible);void 0!==b.userData&&(e.userData=b.userData);if(void 0!==b.children)for(var g in b.children)e.add(this.parseObject(b.children[g],c,d));return e}}()};THREE.TextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};\nTHREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a,b,c,d){var e=new THREE.ImageLoader(this.manager);e.setCrossOrigin(this.crossOrigin);e.load(a,function(a){a=new THREE.Texture(a);a.needsUpdate=!0;void 0!==b&&b(a)},c,d)},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.CompressedTextureLoader=function(){this._parser=null};\nTHREE.CompressedTextureLoader.prototype={constructor:THREE.CompressedTextureLoader,load:function(a,b,c){var d=this,e=[],f=new THREE.CompressedTexture;f.image=e;var g=new THREE.XHRLoader;g.setResponseType(\"arraybuffer\");if(a instanceof Array){var h=0;c=function(c){g.load(a[c],function(a){a=d._parser(a,!0);e[c]={width:a.width,height:a.height,format:a.format,mipmaps:a.mipmaps};h+=1;6===h&&(1==a.mipmapCount&&(f.minFilter=THREE.LinearFilter),f.format=a.format,f.needsUpdate=!0,b&&b(f))})};for(var k=0,n=\na.length;k<n;++k)c(k)}else g.load(a,function(a){a=d._parser(a,!0);if(a.isCubemap)for(var c=a.mipmaps.length/a.mipmapCount,g=0;g<c;g++){e[g]={mipmaps:[]};for(var h=0;h<a.mipmapCount;h++)e[g].mipmaps.push(a.mipmaps[g*a.mipmapCount+h]),e[g].format=a.format,e[g].width=a.width,e[g].height=a.height}else f.image.width=a.width,f.image.height=a.height,f.mipmaps=a.mipmaps;1===a.mipmapCount&&(f.minFilter=THREE.LinearFilter);f.format=a.format;f.needsUpdate=!0;b&&b(f)});return f}};\nTHREE.Material=function(){Object.defineProperty(this,\"id\",{value:THREE.MaterialIdCount++});this.uuid=THREE.Math.generateUUID();this.name=\"\";this.type=\"Material\";this.side=THREE.FrontSide;this.opacity=1;this.transparent=!1;this.blending=THREE.NormalBlending;this.blendSrc=THREE.SrcAlphaFactor;this.blendDst=THREE.OneMinusSrcAlphaFactor;this.blendEquation=THREE.AddEquation;this.depthWrite=this.depthTest=!0;this.polygonOffset=!1;this.overdraw=this.alphaTest=this.polygonOffsetUnits=this.polygonOffsetFactor=\n0;this.needsUpdate=this.visible=!0};\nTHREE.Material.prototype={constructor:THREE.Material,setValues:function(a){if(void 0!==a)for(var b in a){var c=a[b];if(void 0===c)console.warn(\"THREE.Material: '\"+b+\"' parameter is undefined.\");else if(b in this){var d=this[b];d instanceof THREE.Color?d.set(c):d instanceof THREE.Vector3&&c instanceof THREE.Vector3?d.copy(c):this[b]=\"overdraw\"==b?Number(c):c}}},toJSON:function(){var a={metadata:{version:4.2,type:\"material\",generator:\"MaterialExporter\"},uuid:this.uuid,type:this.type};\"\"!==this.name&&\n(a.name=this.name);this instanceof THREE.MeshBasicMaterial?(a.color=this.color.getHex(),this.vertexColors!==THREE.NoColors&&(a.vertexColors=this.vertexColors),this.blending!==THREE.NormalBlending&&(a.blending=this.blending),this.side!==THREE.FrontSide&&(a.side=this.side)):this instanceof THREE.MeshLambertMaterial?(a.color=this.color.getHex(),a.ambient=this.ambient.getHex(),a.emissive=this.emissive.getHex(),this.vertexColors!==THREE.NoColors&&(a.vertexColors=this.vertexColors),this.blending!==THREE.NormalBlending&&\n(a.blending=this.blending),this.side!==THREE.FrontSide&&(a.side=this.side)):this instanceof THREE.MeshPhongMaterial?(a.color=this.color.getHex(),a.ambient=this.ambient.getHex(),a.emissive=this.emissive.getHex(),a.specular=this.specular.getHex(),a.shininess=this.shininess,this.vertexColors!==THREE.NoColors&&(a.vertexColors=this.vertexColors),this.blending!==THREE.NormalBlending&&(a.blending=this.blending),this.side!==THREE.FrontSide&&(a.side=this.side)):this instanceof THREE.MeshNormalMaterial?(this.shading!==\nTHREE.FlatShading&&(a.shading=this.shading),this.blending!==THREE.NormalBlending&&(a.blending=this.blending),this.side!==THREE.FrontSide&&(a.side=this.side)):this instanceof THREE.MeshDepthMaterial?(this.blending!==THREE.NormalBlending&&(a.blending=this.blending),this.side!==THREE.FrontSide&&(a.side=this.side)):this instanceof THREE.ShaderMaterial?(a.uniforms=this.uniforms,a.vertexShader=this.vertexShader,a.fragmentShader=this.fragmentShader):this instanceof THREE.SpriteMaterial&&(a.color=this.color.getHex());\n1>this.opacity&&(a.opacity=this.opacity);!1!==this.transparent&&(a.transparent=this.transparent);!1!==this.wireframe&&(a.wireframe=this.wireframe);return a},clone:function(a){void 0===a&&(a=new THREE.Material);a.name=this.name;a.side=this.side;a.opacity=this.opacity;a.transparent=this.transparent;a.blending=this.blending;a.blendSrc=this.blendSrc;a.blendDst=this.blendDst;a.blendEquation=this.blendEquation;a.depthTest=this.depthTest;a.depthWrite=this.depthWrite;a.polygonOffset=this.polygonOffset;a.polygonOffsetFactor=\nthis.polygonOffsetFactor;a.polygonOffsetUnits=this.polygonOffsetUnits;a.alphaTest=this.alphaTest;a.overdraw=this.overdraw;a.visible=this.visible;return a},dispose:function(){this.dispatchEvent({type:\"dispose\"})}};THREE.EventDispatcher.prototype.apply(THREE.Material.prototype);THREE.MaterialIdCount=0;\nTHREE.LineBasicMaterial=function(a){THREE.Material.call(this);this.type=\"LineBasicMaterial\";this.color=new THREE.Color(16777215);this.linewidth=1;this.linejoin=this.linecap=\"round\";this.vertexColors=THREE.NoColors;this.fog=!0;this.setValues(a)};THREE.LineBasicMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.LineBasicMaterial.prototype.clone=function(){var a=new THREE.LineBasicMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.linewidth=this.linewidth;a.linecap=this.linecap;a.linejoin=this.linejoin;a.vertexColors=this.vertexColors;a.fog=this.fog;return a};\nTHREE.LineDashedMaterial=function(a){THREE.Material.call(this);this.type=\"LineDashedMaterial\";this.color=new THREE.Color(16777215);this.scale=this.linewidth=1;this.dashSize=3;this.gapSize=1;this.vertexColors=!1;this.fog=!0;this.setValues(a)};THREE.LineDashedMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.LineDashedMaterial.prototype.clone=function(){var a=new THREE.LineDashedMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.linewidth=this.linewidth;a.scale=this.scale;a.dashSize=this.dashSize;a.gapSize=this.gapSize;a.vertexColors=this.vertexColors;a.fog=this.fog;return a};\nTHREE.MeshBasicMaterial=function(a){THREE.Material.call(this);this.type=\"MeshBasicMaterial\";this.color=new THREE.Color(16777215);this.envMap=this.alphaMap=this.specularMap=this.lightMap=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap=\"round\";this.vertexColors=THREE.NoColors;this.morphTargets=this.skinning=!1;this.setValues(a)};\nTHREE.MeshBasicMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.MeshBasicMaterial.prototype.clone=function(){var a=new THREE.MeshBasicMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.lightMap=this.lightMap;a.specularMap=this.specularMap;a.alphaMap=this.alphaMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;\na.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;return a};\nTHREE.MeshLambertMaterial=function(a){THREE.Material.call(this);this.type=\"MeshLambertMaterial\";this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(16777215);this.emissive=new THREE.Color(0);this.wrapAround=!1;this.wrapRGB=new THREE.Vector3(1,1,1);this.envMap=this.alphaMap=this.specularMap=this.lightMap=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=\n1;this.wireframeLinejoin=this.wireframeLinecap=\"round\";this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.setValues(a)};THREE.MeshLambertMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.MeshLambertMaterial.prototype.clone=function(){var a=new THREE.MeshLambertMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.ambient.copy(this.ambient);a.emissive.copy(this.emissive);a.wrapAround=this.wrapAround;a.wrapRGB.copy(this.wrapRGB);a.map=this.map;a.lightMap=this.lightMap;a.specularMap=this.specularMap;a.alphaMap=this.alphaMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;\na.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;a.morphNormals=this.morphNormals;return a};\nTHREE.MeshPhongMaterial=function(a){THREE.Material.call(this);this.type=\"MeshPhongMaterial\";this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(16777215);this.emissive=new THREE.Color(0);this.specular=new THREE.Color(1118481);this.shininess=30;this.wrapAround=this.metal=!1;this.wrapRGB=new THREE.Vector3(1,1,1);this.bumpMap=this.lightMap=this.map=null;this.bumpScale=1;this.normalMap=null;this.normalScale=new THREE.Vector2(1,1);this.envMap=this.alphaMap=this.specularMap=null;this.combine=\nTHREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap=\"round\";this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.setValues(a)};THREE.MeshPhongMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.MeshPhongMaterial.prototype.clone=function(){var a=new THREE.MeshPhongMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.ambient.copy(this.ambient);a.emissive.copy(this.emissive);a.specular.copy(this.specular);a.shininess=this.shininess;a.metal=this.metal;a.wrapAround=this.wrapAround;a.wrapRGB.copy(this.wrapRGB);a.map=this.map;a.lightMap=this.lightMap;a.bumpMap=this.bumpMap;a.bumpScale=this.bumpScale;a.normalMap=this.normalMap;a.normalScale.copy(this.normalScale);\na.specularMap=this.specularMap;a.alphaMap=this.alphaMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;a.morphNormals=this.morphNormals;return a};\nTHREE.MeshDepthMaterial=function(a){THREE.Material.call(this);this.type=\"MeshDepthMaterial\";this.wireframe=this.morphTargets=!1;this.wireframeLinewidth=1;this.setValues(a)};THREE.MeshDepthMaterial.prototype=Object.create(THREE.Material.prototype);THREE.MeshDepthMaterial.prototype.clone=function(){var a=new THREE.MeshDepthMaterial;THREE.Material.prototype.clone.call(this,a);a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;return a};\nTHREE.MeshNormalMaterial=function(a){THREE.Material.call(this,a);this.type=\"MeshNormalMaterial\";this.shading=THREE.FlatShading;this.wireframe=!1;this.wireframeLinewidth=1;this.morphTargets=!1;this.setValues(a)};THREE.MeshNormalMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.MeshNormalMaterial.prototype.clone=function(){var a=new THREE.MeshNormalMaterial;THREE.Material.prototype.clone.call(this,a);a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;return a};THREE.MeshFaceMaterial=function(a){this.uuid=THREE.Math.generateUUID();this.type=\"MeshFaceMaterial\";this.materials=a instanceof Array?a:[]};\nTHREE.MeshFaceMaterial.prototype={constructor:THREE.MeshFaceMaterial,toJSON:function(){for(var a={metadata:{version:4.2,type:\"material\",generator:\"MaterialExporter\"},uuid:this.uuid,type:this.type,materials:[]},b=0,c=this.materials.length;b<c;b++)a.materials.push(this.materials[b].toJSON());return a},clone:function(){for(var a=new THREE.MeshFaceMaterial,b=0;b<this.materials.length;b++)a.materials.push(this.materials[b].clone());return a}};\nTHREE.PointCloudMaterial=function(a){THREE.Material.call(this);this.type=\"PointCloudMaterial\";this.color=new THREE.Color(16777215);this.map=null;this.size=1;this.sizeAttenuation=!0;this.vertexColors=THREE.NoColors;this.fog=!0;this.setValues(a)};THREE.PointCloudMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.PointCloudMaterial.prototype.clone=function(){var a=new THREE.PointCloudMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.size=this.size;a.sizeAttenuation=this.sizeAttenuation;a.vertexColors=this.vertexColors;a.fog=this.fog;return a};THREE.ParticleBasicMaterial=function(a){console.warn(\"THREE.ParticleBasicMaterial has been renamed to THREE.PointCloudMaterial.\");return new THREE.PointCloudMaterial(a)};\nTHREE.ParticleSystemMaterial=function(a){console.warn(\"THREE.ParticleSystemMaterial has been renamed to THREE.PointCloudMaterial.\");return new THREE.PointCloudMaterial(a)};\nTHREE.ShaderMaterial=function(a){THREE.Material.call(this);this.type=\"ShaderMaterial\";this.defines={};this.uniforms={};this.attributes=null;this.vertexShader=\"void main() {\\n\\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\\n}\";this.fragmentShader=\"void main() {\\n\\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\\n}\";this.shading=THREE.SmoothShading;this.linewidth=1;this.wireframe=!1;this.wireframeLinewidth=1;this.lights=this.fog=!1;this.vertexColors=THREE.NoColors;this.morphNormals=\nthis.morphTargets=this.skinning=!1;this.defaultAttributeValues={color:[1,1,1],uv:[0,0],uv2:[0,0]};this.index0AttributeName=void 0;this.setValues(a)};THREE.ShaderMaterial.prototype=Object.create(THREE.Material.prototype);\nTHREE.ShaderMaterial.prototype.clone=function(){var a=new THREE.ShaderMaterial;THREE.Material.prototype.clone.call(this,a);a.fragmentShader=this.fragmentShader;a.vertexShader=this.vertexShader;a.uniforms=THREE.UniformsUtils.clone(this.uniforms);a.attributes=this.attributes;a.defines=this.defines;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.fog=this.fog;a.lights=this.lights;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=\nthis.morphTargets;a.morphNormals=this.morphNormals;return a};THREE.RawShaderMaterial=function(a){THREE.ShaderMaterial.call(this,a);this.type=\"RawShaderMaterial\"};THREE.RawShaderMaterial.prototype=Object.create(THREE.ShaderMaterial.prototype);THREE.RawShaderMaterial.prototype.clone=function(){var a=new THREE.RawShaderMaterial;THREE.ShaderMaterial.prototype.clone.call(this,a);return a};\nTHREE.SpriteMaterial=function(a){THREE.Material.call(this);this.type=\"SpriteMaterial\";this.color=new THREE.Color(16777215);this.map=null;this.rotation=0;this.fog=!1;this.setValues(a)};THREE.SpriteMaterial.prototype=Object.create(THREE.Material.prototype);THREE.SpriteMaterial.prototype.clone=function(){var a=new THREE.SpriteMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.rotation=this.rotation;a.fog=this.fog;return a};\nTHREE.Texture=function(a,b,c,d,e,f,g,h,k){Object.defineProperty(this,\"id\",{value:THREE.TextureIdCount++});this.uuid=THREE.Math.generateUUID();this.name=\"\";this.image=void 0!==a?a:THREE.Texture.DEFAULT_IMAGE;this.mipmaps=[];this.mapping=void 0!==b?b:THREE.Texture.DEFAULT_MAPPING;this.wrapS=void 0!==c?c:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==d?d:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==e?e:THREE.LinearFilter;this.minFilter=void 0!==f?f:THREE.LinearMipMapLinearFilter;this.anisotropy=\nvoid 0!==k?k:1;this.format=void 0!==g?g:THREE.RGBAFormat;this.type=void 0!==h?h:THREE.UnsignedByteType;this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1);this.generateMipmaps=!0;this.premultiplyAlpha=!1;this.flipY=!0;this.unpackAlignment=4;this._needsUpdate=!1;this.onUpdate=null};THREE.Texture.DEFAULT_IMAGE=void 0;THREE.Texture.DEFAULT_MAPPING=new THREE.UVMapping;\nTHREE.Texture.prototype={constructor:THREE.Texture,get needsUpdate(){return this._needsUpdate},set needsUpdate(a){!0===a&&this.update();this._needsUpdate=a},clone:function(a){void 0===a&&(a=new THREE.Texture);a.image=this.image;a.mipmaps=this.mipmaps.slice(0);a.mapping=this.mapping;a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.format=this.format;a.type=this.type;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.generateMipmaps=\nthis.generateMipmaps;a.premultiplyAlpha=this.premultiplyAlpha;a.flipY=this.flipY;a.unpackAlignment=this.unpackAlignment;return a},update:function(){this.dispatchEvent({type:\"update\"})},dispose:function(){this.dispatchEvent({type:\"dispose\"})}};THREE.EventDispatcher.prototype.apply(THREE.Texture.prototype);THREE.TextureIdCount=0;THREE.CubeTexture=function(a,b,c,d,e,f,g,h,k){THREE.Texture.call(this,a,b,c,d,e,f,g,h,k);this.images=a};THREE.CubeTexture.prototype=Object.create(THREE.Texture.prototype);\nTHREE.CubeTexture.clone=function(a){void 0===a&&(a=new THREE.CubeTexture);THREE.Texture.prototype.clone.call(this,a);a.images=this.images;return a};THREE.CompressedTexture=function(a,b,c,d,e,f,g,h,k,n,p){THREE.Texture.call(this,null,f,g,h,k,n,d,e,p);this.image={width:b,height:c};this.mipmaps=a;this.generateMipmaps=this.flipY=!1};THREE.CompressedTexture.prototype=Object.create(THREE.Texture.prototype);\nTHREE.CompressedTexture.prototype.clone=function(){var a=new THREE.CompressedTexture;THREE.Texture.prototype.clone.call(this,a);return a};THREE.DataTexture=function(a,b,c,d,e,f,g,h,k,n,p){THREE.Texture.call(this,null,f,g,h,k,n,d,e,p);this.image={data:a,width:b,height:c}};THREE.DataTexture.prototype=Object.create(THREE.Texture.prototype);THREE.DataTexture.prototype.clone=function(){var a=new THREE.DataTexture;THREE.Texture.prototype.clone.call(this,a);return a};\nTHREE.VideoTexture=function(a,b,c,d,e,f,g,h,k){THREE.Texture.call(this,a,b,c,d,e,f,g,h,k);this.generateMipmaps=!1;var n=this,p=function(){requestAnimationFrame(p);a.readyState===a.HAVE_ENOUGH_DATA&&(n.needsUpdate=!0)};p()};THREE.VideoTexture.prototype=Object.create(THREE.Texture.prototype);THREE.Group=function(){THREE.Object3D.call(this);this.type=\"Group\"};THREE.Group.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.PointCloud=function(a,b){THREE.Object3D.call(this);this.type=\"PointCloud\";this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.PointCloudMaterial({color:16777215*Math.random()});this.sortParticles=!1};THREE.PointCloud.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.PointCloud.prototype.raycast=function(){var a=new THREE.Matrix4,b=new THREE.Ray;return function(c,d){var e=this,f=e.geometry,g=c.params.PointCloud.threshold;a.getInverse(this.matrixWorld);b.copy(c.ray).applyMatrix4(a);if(null===f.boundingBox||!1!==b.isIntersectionBox(f.boundingBox)){var h=g/((this.scale.x+this.scale.y+this.scale.z)/3),k=new THREE.Vector3,g=function(a,f){var g=b.distanceToPoint(a);if(g<h){var k=b.closestPointToPoint(a);k.applyMatrix4(e.matrixWorld);var m=c.ray.origin.distanceTo(k);\nd.push({distance:m,distanceToRay:g,point:k.clone(),index:f,face:null,object:e})}};if(f instanceof THREE.BufferGeometry){var n=f.attributes,p=n.position.array;if(void 0!==n.index){var n=n.index.array,q=f.offsets;0===q.length&&(q=[{start:0,count:n.length,index:0}]);for(var m=0,r=q.length;m<r;++m)for(var t=q[m].start,s=q[m].index,f=t,t=t+q[m].count;f<t;f++){var u=s+n[f];k.fromArray(p,3*u);g(k,u)}}else for(n=p.length/3,f=0;f<n;f++)k.set(p[3*f],p[3*f+1],p[3*f+2]),g(k,f)}else for(k=this.geometry.vertices,\nf=0;f<k.length;f++)g(k[f],f)}}}();THREE.PointCloud.prototype.clone=function(a){void 0===a&&(a=new THREE.PointCloud(this.geometry,this.material));a.sortParticles=this.sortParticles;THREE.Object3D.prototype.clone.call(this,a);return a};THREE.ParticleSystem=function(a,b){console.warn(\"THREE.ParticleSystem has been renamed to THREE.PointCloud.\");return new THREE.PointCloud(a,b)};\nTHREE.Line=function(a,b,c){THREE.Object3D.call(this);this.type=\"Line\";this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.LineBasicMaterial({color:16777215*Math.random()});this.mode=void 0!==c?c:THREE.LineStrip};THREE.LineStrip=0;THREE.LinePieces=1;THREE.Line.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Line.prototype.raycast=function(){var a=new THREE.Matrix4,b=new THREE.Ray,c=new THREE.Sphere;return function(d,e){var f=d.linePrecision,f=f*f,g=this.geometry;null===g.boundingSphere&&g.computeBoundingSphere();c.copy(g.boundingSphere);c.applyMatrix4(this.matrixWorld);if(!1!==d.ray.isIntersectionSphere(c)&&(a.getInverse(this.matrixWorld),b.copy(d.ray).applyMatrix4(a),g instanceof THREE.Geometry))for(var g=g.vertices,h=g.length,k=new THREE.Vector3,n=new THREE.Vector3,p=this.mode===THREE.LineStrip?\n1:2,q=0;q<h-1;q+=p)if(!(b.distanceSqToSegment(g[q],g[q+1],n,k)>f)){var m=b.origin.distanceTo(n);m<d.near||m>d.far||e.push({distance:m,point:k.clone().applyMatrix4(this.matrixWorld),face:null,faceIndex:null,object:this})}}}();THREE.Line.prototype.clone=function(a){void 0===a&&(a=new THREE.Line(this.geometry,this.material,this.mode));THREE.Object3D.prototype.clone.call(this,a);return a};\nTHREE.Mesh=function(a,b){THREE.Object3D.call(this);this.type=\"Mesh\";this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.MeshBasicMaterial({color:16777215*Math.random()});this.updateMorphTargets()};THREE.Mesh.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Mesh.prototype.updateMorphTargets=function(){if(void 0!==this.geometry.morphTargets&&0<this.geometry.morphTargets.length){this.morphTargetBase=-1;this.morphTargetForcedOrder=[];this.morphTargetInfluences=[];this.morphTargetDictionary={};for(var a=0,b=this.geometry.morphTargets.length;a<b;a++)this.morphTargetInfluences.push(0),this.morphTargetDictionary[this.geometry.morphTargets[a].name]=a}};\nTHREE.Mesh.prototype.getMorphTargetIndexByName=function(a){if(void 0!==this.morphTargetDictionary[a])return this.morphTargetDictionary[a];console.log(\"THREE.Mesh.getMorphTargetIndexByName: morph target \"+a+\" does not exist. Returning 0.\");return 0};\nTHREE.Mesh.prototype.raycast=function(){var a=new THREE.Matrix4,b=new THREE.Ray,c=new THREE.Sphere,d=new THREE.Vector3,e=new THREE.Vector3,f=new THREE.Vector3;return function(g,h){var k=this.geometry;null===k.boundingSphere&&k.computeBoundingSphere();c.copy(k.boundingSphere);c.applyMatrix4(this.matrixWorld);if(!1!==g.ray.isIntersectionSphere(c)&&(a.getInverse(this.matrixWorld),b.copy(g.ray).applyMatrix4(a),null===k.boundingBox||!1!==b.isIntersectionBox(k.boundingBox)))if(k instanceof THREE.BufferGeometry){var n=\nthis.material;if(void 0!==n){var p=k.attributes,q,m,r=g.precision;if(void 0!==p.index){var t=p.index.array,s=p.position.array,u=k.offsets;0===u.length&&(u=[{start:0,count:t.length,index:0}]);for(var v=0,y=u.length;v<y;++v)for(var p=u[v].start,G=u[v].index,k=p,w=p+u[v].count;k<w;k+=3){p=G+t[k];q=G+t[k+1];m=G+t[k+2];d.fromArray(s,3*p);e.fromArray(s,3*q);f.fromArray(s,3*m);var K=n.side===THREE.BackSide?b.intersectTriangle(f,e,d,!0):b.intersectTriangle(d,e,f,n.side!==THREE.DoubleSide);if(null!==K){K.applyMatrix4(this.matrixWorld);\nvar x=g.ray.origin.distanceTo(K);x<r||x<g.near||x>g.far||h.push({distance:x,point:K,face:new THREE.Face3(p,q,m,THREE.Triangle.normal(d,e,f)),faceIndex:null,object:this})}}}else for(s=p.position.array,t=k=0,w=s.length;k<w;k+=3,t+=9)p=k,q=k+1,m=k+2,d.fromArray(s,t),e.fromArray(s,t+3),f.fromArray(s,t+6),K=n.side===THREE.BackSide?b.intersectTriangle(f,e,d,!0):b.intersectTriangle(d,e,f,n.side!==THREE.DoubleSide),null!==K&&(K.applyMatrix4(this.matrixWorld),x=g.ray.origin.distanceTo(K),x<r||x<g.near||x>\ng.far||h.push({distance:x,point:K,face:new THREE.Face3(p,q,m,THREE.Triangle.normal(d,e,f)),faceIndex:null,object:this}))}}else if(k instanceof THREE.Geometry)for(t=this.material instanceof THREE.MeshFaceMaterial,s=!0===t?this.material.materials:null,r=g.precision,u=k.vertices,v=0,y=k.faces.length;v<y;v++)if(G=k.faces[v],n=!0===t?s[G.materialIndex]:this.material,void 0!==n){p=u[G.a];q=u[G.b];m=u[G.c];if(!0===n.morphTargets){K=k.morphTargets;x=this.morphTargetInfluences;d.set(0,0,0);e.set(0,0,0);f.set(0,\n0,0);for(var w=0,D=K.length;w<D;w++){var E=x[w];if(0!==E){var A=K[w].vertices;d.x+=(A[G.a].x-p.x)*E;d.y+=(A[G.a].y-p.y)*E;d.z+=(A[G.a].z-p.z)*E;e.x+=(A[G.b].x-q.x)*E;e.y+=(A[G.b].y-q.y)*E;e.z+=(A[G.b].z-q.z)*E;f.x+=(A[G.c].x-m.x)*E;f.y+=(A[G.c].y-m.y)*E;f.z+=(A[G.c].z-m.z)*E}}d.add(p);e.add(q);f.add(m);p=d;q=e;m=f}K=n.side===THREE.BackSide?b.intersectTriangle(m,q,p,!0):b.intersectTriangle(p,q,m,n.side!==THREE.DoubleSide);null!==K&&(K.applyMatrix4(this.matrixWorld),x=g.ray.origin.distanceTo(K),x<r||\nx<g.near||x>g.far||h.push({distance:x,point:K,face:G,faceIndex:v,object:this}))}}}();THREE.Mesh.prototype.clone=function(a,b){void 0===a&&(a=new THREE.Mesh(this.geometry,this.material));THREE.Object3D.prototype.clone.call(this,a,b);return a};THREE.Bone=function(a){THREE.Object3D.call(this);this.skin=a};THREE.Bone.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Skeleton=function(a,b,c){this.useVertexTexture=void 0!==c?c:!0;this.identityMatrix=new THREE.Matrix4;a=a||[];this.bones=a.slice(0);this.useVertexTexture?(this.boneTextureHeight=this.boneTextureWidth=a=256<this.bones.length?64:64<this.bones.length?32:16<this.bones.length?16:8,this.boneMatrices=new Float32Array(this.boneTextureWidth*this.boneTextureHeight*4),this.boneTexture=new THREE.DataTexture(this.boneMatrices,this.boneTextureWidth,this.boneTextureHeight,THREE.RGBAFormat,THREE.FloatType),\nthis.boneTexture.minFilter=THREE.NearestFilter,this.boneTexture.magFilter=THREE.NearestFilter,this.boneTexture.generateMipmaps=!1,this.boneTexture.flipY=!1):this.boneMatrices=new Float32Array(16*this.bones.length);if(void 0===b)this.calculateInverses();else if(this.bones.length===b.length)this.boneInverses=b.slice(0);else for(console.warn(\"THREE.Skeleton bonInverses is the wrong length.\"),this.boneInverses=[],b=0,a=this.bones.length;b<a;b++)this.boneInverses.push(new THREE.Matrix4)};\nTHREE.Skeleton.prototype.calculateInverses=function(){this.boneInverses=[];for(var a=0,b=this.bones.length;a<b;a++){var c=new THREE.Matrix4;this.bones[a]&&c.getInverse(this.bones[a].matrixWorld);this.boneInverses.push(c)}};\nTHREE.Skeleton.prototype.pose=function(){for(var a,b=0,c=this.bones.length;b<c;b++)(a=this.bones[b])&&a.matrixWorld.getInverse(this.boneInverses[b]);b=0;for(c=this.bones.length;b<c;b++)if(a=this.bones[b])a.parent?(a.matrix.getInverse(a.parent.matrixWorld),a.matrix.multiply(a.matrixWorld)):a.matrix.copy(a.matrixWorld),a.matrix.decompose(a.position,a.quaternion,a.scale)};\nTHREE.Skeleton.prototype.update=function(){var a=new THREE.Matrix4;return function(){for(var b=0,c=this.bones.length;b<c;b++)a.multiplyMatrices(this.bones[b]?this.bones[b].matrixWorld:this.identityMatrix,this.boneInverses[b]),a.flattenToArrayOffset(this.boneMatrices,16*b);this.useVertexTexture&&(this.boneTexture.needsUpdate=!0)}}();\nTHREE.SkinnedMesh=function(a,b,c){THREE.Mesh.call(this,a,b);this.type=\"SkinnedMesh\";this.bindMode=\"attached\";this.bindMatrix=new THREE.Matrix4;this.bindMatrixInverse=new THREE.Matrix4;a=[];if(this.geometry&&void 0!==this.geometry.bones){for(var d,e,f,g,h=0,k=this.geometry.bones.length;h<k;++h)d=this.geometry.bones[h],e=d.pos,f=d.rotq,g=d.scl,b=new THREE.Bone(this),a.push(b),b.name=d.name,b.position.set(e[0],e[1],e[2]),b.quaternion.set(f[0],f[1],f[2],f[3]),void 0!==g?b.scale.set(g[0],g[1],g[2]):b.scale.set(1,\n1,1);h=0;for(k=this.geometry.bones.length;h<k;++h)d=this.geometry.bones[h],-1!==d.parent?a[d.parent].add(a[h]):this.add(a[h])}this.normalizeSkinWeights();this.updateMatrixWorld(!0);this.bind(new THREE.Skeleton(a,void 0,c))};THREE.SkinnedMesh.prototype=Object.create(THREE.Mesh.prototype);THREE.SkinnedMesh.prototype.bind=function(a,b){this.skeleton=a;void 0===b&&(this.updateMatrixWorld(!0),b=this.matrixWorld);this.bindMatrix.copy(b);this.bindMatrixInverse.getInverse(b)};\nTHREE.SkinnedMesh.prototype.pose=function(){this.skeleton.pose()};THREE.SkinnedMesh.prototype.normalizeSkinWeights=function(){if(this.geometry instanceof THREE.Geometry)for(var a=0;a<this.geometry.skinIndices.length;a++){var b=this.geometry.skinWeights[a],c=1/b.lengthManhattan();Infinity!==c?b.multiplyScalar(c):b.set(1)}};\nTHREE.SkinnedMesh.prototype.updateMatrixWorld=function(a){THREE.Mesh.prototype.updateMatrixWorld.call(this,!0);\"attached\"===this.bindMode?this.bindMatrixInverse.getInverse(this.matrixWorld):\"detached\"===this.bindMode?this.bindMatrixInverse.getInverse(this.bindMatrix):console.warn(\"THREE.SkinnedMesh unreckognized bindMode: \"+this.bindMode)};\nTHREE.SkinnedMesh.prototype.clone=function(a){void 0===a&&(a=new THREE.SkinnedMesh(this.geometry,this.material,this.useVertexTexture));THREE.Mesh.prototype.clone.call(this,a);return a};THREE.MorphAnimMesh=function(a,b){THREE.Mesh.call(this,a,b);this.type=\"MorphAnimMesh\";this.duration=1E3;this.mirroredLoop=!1;this.currentKeyframe=this.lastKeyframe=this.time=0;this.direction=1;this.directionBackwards=!1;this.setFrameRange(0,this.geometry.morphTargets.length-1)};THREE.MorphAnimMesh.prototype=Object.create(THREE.Mesh.prototype);\nTHREE.MorphAnimMesh.prototype.setFrameRange=function(a,b){this.startKeyframe=a;this.endKeyframe=b;this.length=this.endKeyframe-this.startKeyframe+1};THREE.MorphAnimMesh.prototype.setDirectionForward=function(){this.direction=1;this.directionBackwards=!1};THREE.MorphAnimMesh.prototype.setDirectionBackward=function(){this.direction=-1;this.directionBackwards=!0};\nTHREE.MorphAnimMesh.prototype.parseAnimations=function(){var a=this.geometry;a.animations||(a.animations={});for(var b,c=a.animations,d=/([a-z]+)_?(\\d+)/,e=0,f=a.morphTargets.length;e<f;e++){var g=a.morphTargets[e].name.match(d);if(g&&1<g.length){g=g[1];c[g]||(c[g]={start:Infinity,end:-Infinity});var h=c[g];e<h.start&&(h.start=e);e>h.end&&(h.end=e);b||(b=g)}}a.firstAnimation=b};\nTHREE.MorphAnimMesh.prototype.setAnimationLabel=function(a,b,c){this.geometry.animations||(this.geometry.animations={});this.geometry.animations[a]={start:b,end:c}};THREE.MorphAnimMesh.prototype.playAnimation=function(a,b){var c=this.geometry.animations[a];c?(this.setFrameRange(c.start,c.end),this.duration=(c.end-c.start)/b*1E3,this.time=0):console.warn(\"animation[\"+a+\"] undefined\")};\nTHREE.MorphAnimMesh.prototype.updateAnimation=function(a){var b=this.duration/this.length;this.time+=this.direction*a;if(this.mirroredLoop){if(this.time>this.duration||0>this.time)this.direction*=-1,this.time>this.duration&&(this.time=this.duration,this.directionBackwards=!0),0>this.time&&(this.time=0,this.directionBackwards=!1)}else this.time%=this.duration,0>this.time&&(this.time+=this.duration);a=this.startKeyframe+THREE.Math.clamp(Math.floor(this.time/b),0,this.length-1);a!==this.currentKeyframe&&\n(this.morphTargetInfluences[this.lastKeyframe]=0,this.morphTargetInfluences[this.currentKeyframe]=1,this.morphTargetInfluences[a]=0,this.lastKeyframe=this.currentKeyframe,this.currentKeyframe=a);b=this.time%b/b;this.directionBackwards&&(b=1-b);this.morphTargetInfluences[this.currentKeyframe]=b;this.morphTargetInfluences[this.lastKeyframe]=1-b};\nTHREE.MorphAnimMesh.prototype.interpolateTargets=function(a,b,c){for(var d=this.morphTargetInfluences,e=0,f=d.length;e<f;e++)d[e]=0;-1<a&&(d[a]=1-c);-1<b&&(d[b]=c)};\nTHREE.MorphAnimMesh.prototype.clone=function(a){void 0===a&&(a=new THREE.MorphAnimMesh(this.geometry,this.material));a.duration=this.duration;a.mirroredLoop=this.mirroredLoop;a.time=this.time;a.lastKeyframe=this.lastKeyframe;a.currentKeyframe=this.currentKeyframe;a.direction=this.direction;a.directionBackwards=this.directionBackwards;THREE.Mesh.prototype.clone.call(this,a);return a};THREE.LOD=function(){THREE.Object3D.call(this);this.objects=[]};THREE.LOD.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.LOD.prototype.addLevel=function(a,b){void 0===b&&(b=0);b=Math.abs(b);for(var c=0;c<this.objects.length&&!(b<this.objects[c].distance);c++);this.objects.splice(c,0,{distance:b,object:a});this.add(a)};THREE.LOD.prototype.getObjectForDistance=function(a){for(var b=1,c=this.objects.length;b<c&&!(a<this.objects[b].distance);b++);return this.objects[b-1].object};\nTHREE.LOD.prototype.raycast=function(){var a=new THREE.Vector3;return function(b,c){a.setFromMatrixPosition(this.matrixWorld);var d=b.ray.origin.distanceTo(a);this.getObjectForDistance(d).raycast(b,c)}}();\nTHREE.LOD.prototype.update=function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c){if(1<this.objects.length){a.setFromMatrixPosition(c.matrixWorld);b.setFromMatrixPosition(this.matrixWorld);c=a.distanceTo(b);this.objects[0].object.visible=!0;for(var d=1,e=this.objects.length;d<e;d++)if(c>=this.objects[d].distance)this.objects[d-1].object.visible=!1,this.objects[d].object.visible=!0;else break;for(;d<e;d++)this.objects[d].object.visible=!1}}}();\nTHREE.LOD.prototype.clone=function(a){void 0===a&&(a=new THREE.LOD);THREE.Object3D.prototype.clone.call(this,a);for(var b=0,c=this.objects.length;b<c;b++){var d=this.objects[b].object.clone();d.visible=0===b;a.addLevel(d,this.objects[b].distance)}return a};\nTHREE.Sprite=function(){var a=new Uint16Array([0,1,2,0,2,3]),b=new Float32Array([-.5,-.5,0,.5,-.5,0,.5,.5,0,-.5,.5,0]),c=new Float32Array([0,0,1,0,1,1,0,1]),d=new THREE.BufferGeometry;d.addAttribute(\"index\",new THREE.BufferAttribute(a,1));d.addAttribute(\"position\",new THREE.BufferAttribute(b,3));d.addAttribute(\"uv\",new THREE.BufferAttribute(c,2));return function(a){THREE.Object3D.call(this);this.type=\"Sprite\";this.geometry=d;this.material=void 0!==a?a:new THREE.SpriteMaterial}}();\nTHREE.Sprite.prototype=Object.create(THREE.Object3D.prototype);THREE.Sprite.prototype.raycast=function(){var a=new THREE.Vector3;return function(b,c){a.setFromMatrixPosition(this.matrixWorld);var d=b.ray.distanceToPoint(a);d>this.scale.x||c.push({distance:d,point:this.position,face:null,object:this})}}();THREE.Sprite.prototype.clone=function(a){void 0===a&&(a=new THREE.Sprite(this.material));THREE.Object3D.prototype.clone.call(this,a);return a};THREE.Particle=THREE.Sprite;\nTHREE.LensFlare=function(a,b,c,d,e){THREE.Object3D.call(this);this.lensFlares=[];this.positionScreen=new THREE.Vector3;this.customUpdateCallback=void 0;void 0!==a&&this.add(a,b,c,d,e)};THREE.LensFlare.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.LensFlare.prototype.add=function(a,b,c,d,e,f){void 0===b&&(b=-1);void 0===c&&(c=0);void 0===f&&(f=1);void 0===e&&(e=new THREE.Color(16777215));void 0===d&&(d=THREE.NormalBlending);c=Math.min(c,Math.max(0,c));this.lensFlares.push({texture:a,size:b,distance:c,x:0,y:0,z:0,scale:1,rotation:1,opacity:f,color:e,blending:d})};\nTHREE.LensFlare.prototype.updateLensFlares=function(){var a,b=this.lensFlares.length,c,d=2*-this.positionScreen.x,e=2*-this.positionScreen.y;for(a=0;a<b;a++)c=this.lensFlares[a],c.x=this.positionScreen.x+d*c.distance,c.y=this.positionScreen.y+e*c.distance,c.wantedRotation=c.x*Math.PI*.25,c.rotation+=.25*(c.wantedRotation-c.rotation)};THREE.Scene=function(){THREE.Object3D.call(this);this.type=\"Scene\";this.overrideMaterial=this.fog=null;this.autoUpdate=!0};THREE.Scene.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Scene.prototype.clone=function(a){void 0===a&&(a=new THREE.Scene);THREE.Object3D.prototype.clone.call(this,a);null!==this.fog&&(a.fog=this.fog.clone());null!==this.overrideMaterial&&(a.overrideMaterial=this.overrideMaterial.clone());a.autoUpdate=this.autoUpdate;a.matrixAutoUpdate=this.matrixAutoUpdate;return a};THREE.Fog=function(a,b,c){this.name=\"\";this.color=new THREE.Color(a);this.near=void 0!==b?b:1;this.far=void 0!==c?c:1E3};\nTHREE.Fog.prototype.clone=function(){return new THREE.Fog(this.color.getHex(),this.near,this.far)};THREE.FogExp2=function(a,b){this.name=\"\";this.color=new THREE.Color(a);this.density=void 0!==b?b:2.5E-4};THREE.FogExp2.prototype.clone=function(){return new THREE.FogExp2(this.color.getHex(),this.density)};THREE.ShaderChunk={};THREE.ShaderChunk.alphatest_fragment=\"#ifdef ALPHATEST\\n\\n\\tif ( gl_FragColor.a < ALPHATEST ) discard;\\n\\n#endif\\n\";THREE.ShaderChunk.lights_lambert_vertex=\"vLightFront = vec3( 0.0 );\\n\\n#ifdef DOUBLE_SIDED\\n\\n\\tvLightBack = vec3( 0.0 );\\n\\n#endif\\n\\ntransformedNormal = normalize( transformedNormal );\\n\\n#if MAX_DIR_LIGHTS > 0\\n\\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\\n\\n\\tvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\\n\\tvec3 dirVector = normalize( lDirection.xyz );\\n\\n\\tfloat dotProduct = dot( transformedNormal, dirVector );\\n\\tvec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );\\n\\n\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\tvec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\\n\\n\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\tvec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\\n\\n\\t\\t#endif\\n\\n\\t#endif\\n\\n\\t#ifdef WRAP_AROUND\\n\\n\\t\\tvec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\\n\\t\\tdirectionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );\\n\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\tdirectionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );\\n\\n\\t\\t#endif\\n\\n\\t#endif\\n\\n\\tvLightFront += directionalLightColor[ i ] * directionalLightWeighting;\\n\\n\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\tvLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;\\n\\n\\t#endif\\n\\n}\\n\\n#endif\\n\\n#if MAX_POINT_LIGHTS > 0\\n\\n\\tfor( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\\n\\t\\tvec3 lVector = lPosition.xyz - mvPosition.xyz;\\n\\n\\t\\tfloat lDistance = 1.0;\\n\\t\\tif ( pointLightDistance[ i ] > 0.0 )\\n\\t\\t\\tlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\\n\\n\\t\\tlVector = normalize( lVector );\\n\\t\\tfloat dotProduct = dot( transformedNormal, lVector );\\n\\n\\t\\tvec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );\\n\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\tvec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\\n\\n\\t\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\t\\tvec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t#endif\\n\\n\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\tvec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\\n\\t\\t\\tpointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );\\n\\n\\t\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\t\\tpointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t#endif\\n\\n\\t\\tvLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;\\n\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\tvLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;\\n\\n\\t\\t#endif\\n\\n\\t}\\n\\n#endif\\n\\n#if MAX_SPOT_LIGHTS > 0\\n\\n\\tfor( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\\n\\t\\tvec3 lVector = lPosition.xyz - mvPosition.xyz;\\n\\n\\t\\tfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );\\n\\n\\t\\tif ( spotEffect > spotLightAngleCos[ i ] ) {\\n\\n\\t\\t\\tspotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );\\n\\n\\t\\t\\tfloat lDistance = 1.0;\\n\\t\\t\\tif ( spotLightDistance[ i ] > 0.0 )\\n\\t\\t\\t\\tlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\\n\\n\\t\\t\\tlVector = normalize( lVector );\\n\\n\\t\\t\\tfloat dotProduct = dot( transformedNormal, lVector );\\n\\t\\t\\tvec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );\\n\\n\\t\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\t\\tvec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\\n\\n\\t\\t\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\t\\t\\tvec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\\n\\n\\t\\t\\t\\t#endif\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\t\\tvec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\\n\\t\\t\\t\\tspotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );\\n\\n\\t\\t\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\t\\t\\tspotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );\\n\\n\\t\\t\\t\\t#endif\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t\\tvLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;\\n\\n\\t\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\t\\tvLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t}\\n\\n\\t}\\n\\n#endif\\n\\n#if MAX_HEMI_LIGHTS > 0\\n\\n\\tfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\\n\\t\\tvec3 lVector = normalize( lDirection.xyz );\\n\\n\\t\\tfloat dotProduct = dot( transformedNormal, lVector );\\n\\n\\t\\tfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\\n\\t\\tfloat hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;\\n\\n\\t\\tvLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\\n\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\t\\tvLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\\n\\n\\t\\t#endif\\n\\n\\t}\\n\\n#endif\\n\\nvLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;\\n\\n#ifdef DOUBLE_SIDED\\n\\n\\tvLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;\\n\\n#endif\";\nTHREE.ShaderChunk.map_particle_pars_fragment=\"#ifdef USE_MAP\\n\\n\\tuniform sampler2D map;\\n\\n#endif\";THREE.ShaderChunk.default_vertex=\"vec4 mvPosition;\\n\\n#ifdef USE_SKINNING\\n\\n\\tmvPosition = modelViewMatrix * skinned;\\n\\n#endif\\n\\n#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )\\n\\n\\tmvPosition = modelViewMatrix * vec4( morphed, 1.0 );\\n\\n#endif\\n\\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )\\n\\n\\tmvPosition = modelViewMatrix * vec4( position, 1.0 );\\n\\n#endif\\n\\ngl_Position = projectionMatrix * mvPosition;\";\nTHREE.ShaderChunk.map_pars_fragment=\"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\\n\\n\\tvarying vec2 vUv;\\n\\n#endif\\n\\n#ifdef USE_MAP\\n\\n\\tuniform sampler2D map;\\n\\n#endif\";THREE.ShaderChunk.skinnormal_vertex=\"#ifdef USE_SKINNING\\n\\n\\tmat4 skinMatrix = mat4( 0.0 );\\n\\tskinMatrix += skinWeight.x * boneMatX;\\n\\tskinMatrix += skinWeight.y * boneMatY;\\n\\tskinMatrix += skinWeight.z * boneMatZ;\\n\\tskinMatrix += skinWeight.w * boneMatW;\\n\\tskinMatrix  = bindMatrixInverse * skinMatrix * bindMatrix;\\n\\n\\t#ifdef USE_MORPHNORMALS\\n\\n\\tvec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );\\n\\n\\t#else\\n\\n\\tvec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );\\n\\n\\t#endif\\n\\n#endif\\n\";\nTHREE.ShaderChunk.logdepthbuf_pars_vertex=\"#ifdef USE_LOGDEPTHBUF\\n\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\n\\t\\tvarying float vFragDepth;\\n\\n\\t#endif\\n\\n\\tuniform float logDepthBufFC;\\n\\n#endif\";THREE.ShaderChunk.lightmap_pars_vertex=\"#ifdef USE_LIGHTMAP\\n\\n\\tvarying vec2 vUv2;\\n\\n#endif\";THREE.ShaderChunk.lights_phong_fragment=\"vec3 normal = normalize( vNormal );\\nvec3 viewPosition = normalize( vViewPosition );\\n\\n#ifdef DOUBLE_SIDED\\n\\n\\tnormal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\\n\\n#endif\\n\\n#ifdef USE_NORMALMAP\\n\\n\\tnormal = perturbNormal2Arb( -vViewPosition, normal );\\n\\n#elif defined( USE_BUMPMAP )\\n\\n\\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\\n\\n#endif\\n\\n#if MAX_POINT_LIGHTS > 0\\n\\n\\tvec3 pointDiffuse = vec3( 0.0 );\\n\\tvec3 pointSpecular = vec3( 0.0 );\\n\\n\\tfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\\n\\t\\tvec3 lVector = lPosition.xyz + vViewPosition.xyz;\\n\\n\\t\\tfloat lDistance = 1.0;\\n\\t\\tif ( pointLightDistance[ i ] > 0.0 )\\n\\t\\t\\tlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\\n\\n\\t\\tlVector = normalize( lVector );\\n\\n\\t\\t\\t\\t// diffuse\\n\\n\\t\\tfloat dotProduct = dot( normal, lVector );\\n\\n\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\tfloat pointDiffuseWeightFull = max( dotProduct, 0.0 );\\n\\t\\t\\tfloat pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\\n\\n\\t\\t\\tvec3 pointDiffuseWeight = mix( vec3( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\\n\\n\\t\\t#else\\n\\n\\t\\t\\tfloat pointDiffuseWeight = max( dotProduct, 0.0 );\\n\\n\\t\\t#endif\\n\\n\\t\\tpointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;\\n\\n\\t\\t\\t\\t// specular\\n\\n\\t\\tvec3 pointHalfVector = normalize( lVector + viewPosition );\\n\\t\\tfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\\n\\t\\tfloat pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );\\n\\n\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\n\\t\\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, pointHalfVector ), 0.0 ), 5.0 );\\n\\t\\tpointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;\\n\\n\\t}\\n\\n#endif\\n\\n#if MAX_SPOT_LIGHTS > 0\\n\\n\\tvec3 spotDiffuse = vec3( 0.0 );\\n\\tvec3 spotSpecular = vec3( 0.0 );\\n\\n\\tfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\\n\\t\\tvec3 lVector = lPosition.xyz + vViewPosition.xyz;\\n\\n\\t\\tfloat lDistance = 1.0;\\n\\t\\tif ( spotLightDistance[ i ] > 0.0 )\\n\\t\\t\\tlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\\n\\n\\t\\tlVector = normalize( lVector );\\n\\n\\t\\tfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\\n\\n\\t\\tif ( spotEffect > spotLightAngleCos[ i ] ) {\\n\\n\\t\\t\\tspotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );\\n\\n\\t\\t\\t\\t\\t// diffuse\\n\\n\\t\\t\\tfloat dotProduct = dot( normal, lVector );\\n\\n\\t\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\t\\tfloat spotDiffuseWeightFull = max( dotProduct, 0.0 );\\n\\t\\t\\t\\tfloat spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\\n\\n\\t\\t\\t\\tvec3 spotDiffuseWeight = mix( vec3( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\\n\\n\\t\\t\\t#else\\n\\n\\t\\t\\t\\tfloat spotDiffuseWeight = max( dotProduct, 0.0 );\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t\\tspotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;\\n\\n\\t\\t\\t\\t\\t// specular\\n\\n\\t\\t\\tvec3 spotHalfVector = normalize( lVector + viewPosition );\\n\\t\\t\\tfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\\n\\t\\t\\tfloat spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );\\n\\n\\t\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\n\\t\\t\\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, spotHalfVector ), 0.0 ), 5.0 );\\n\\t\\t\\tspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;\\n\\n\\t\\t}\\n\\n\\t}\\n\\n#endif\\n\\n#if MAX_DIR_LIGHTS > 0\\n\\n\\tvec3 dirDiffuse = vec3( 0.0 );\\n\\tvec3 dirSpecular = vec3( 0.0 );\\n\\n\\tfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\\n\\t\\tvec3 dirVector = normalize( lDirection.xyz );\\n\\n\\t\\t\\t\\t// diffuse\\n\\n\\t\\tfloat dotProduct = dot( normal, dirVector );\\n\\n\\t\\t#ifdef WRAP_AROUND\\n\\n\\t\\t\\tfloat dirDiffuseWeightFull = max( dotProduct, 0.0 );\\n\\t\\t\\tfloat dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\\n\\n\\t\\t\\tvec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );\\n\\n\\t\\t#else\\n\\n\\t\\t\\tfloat dirDiffuseWeight = max( dotProduct, 0.0 );\\n\\n\\t\\t#endif\\n\\n\\t\\tdirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;\\n\\n\\t\\t// specular\\n\\n\\t\\tvec3 dirHalfVector = normalize( dirVector + viewPosition );\\n\\t\\tfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\\n\\t\\tfloat dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );\\n\\n\\t\\t/*\\n\\t\\t// fresnel term from skin shader\\n\\t\\tconst float F0 = 0.128;\\n\\n\\t\\tfloat base = 1.0 - dot( viewPosition, dirHalfVector );\\n\\t\\tfloat exponential = pow( base, 5.0 );\\n\\n\\t\\tfloat fresnel = exponential + F0 * ( 1.0 - exponential );\\n\\t\\t*/\\n\\n\\t\\t/*\\n\\t\\t// fresnel term from fresnel shader\\n\\t\\tconst float mFresnelBias = 0.08;\\n\\t\\tconst float mFresnelScale = 0.3;\\n\\t\\tconst float mFresnelPower = 5.0;\\n\\n\\t\\tfloat fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );\\n\\t\\t*/\\n\\n\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\n\\t\\t// \\t\\tdirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;\\n\\n\\t\\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );\\n\\t\\tdirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\\n\\n\\n\\t}\\n\\n#endif\\n\\n#if MAX_HEMI_LIGHTS > 0\\n\\n\\tvec3 hemiDiffuse = vec3( 0.0 );\\n\\tvec3 hemiSpecular = vec3( 0.0 );\\n\\n\\tfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\\n\\n\\t\\tvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\\n\\t\\tvec3 lVector = normalize( lDirection.xyz );\\n\\n\\t\\t// diffuse\\n\\n\\t\\tfloat dotProduct = dot( normal, lVector );\\n\\t\\tfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\\n\\n\\t\\tvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\\n\\n\\t\\themiDiffuse += diffuse * hemiColor;\\n\\n\\t\\t// specular (sky light)\\n\\n\\t\\tvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\\n\\t\\tfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\\n\\t\\tfloat hemiSpecularWeightSky = specularStrength * max( pow( max( hemiDotNormalHalfSky, 0.0 ), shininess ), 0.0 );\\n\\n\\t\\t// specular (ground light)\\n\\n\\t\\tvec3 lVectorGround = -lVector;\\n\\n\\t\\tvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\\n\\t\\tfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\\n\\t\\tfloat hemiSpecularWeightGround = specularStrength * max( pow( max( hemiDotNormalHalfGround, 0.0 ), shininess ), 0.0 );\\n\\n\\t\\tfloat dotProductGround = dot( normal, lVectorGround );\\n\\n\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\n\\t\\tvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );\\n\\t\\tvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );\\n\\t\\themiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\\n\\n\\t}\\n\\n#endif\\n\\nvec3 totalDiffuse = vec3( 0.0 );\\nvec3 totalSpecular = vec3( 0.0 );\\n\\n#if MAX_DIR_LIGHTS > 0\\n\\n\\ttotalDiffuse += dirDiffuse;\\n\\ttotalSpecular += dirSpecular;\\n\\n#endif\\n\\n#if MAX_HEMI_LIGHTS > 0\\n\\n\\ttotalDiffuse += hemiDiffuse;\\n\\ttotalSpecular += hemiSpecular;\\n\\n#endif\\n\\n#if MAX_POINT_LIGHTS > 0\\n\\n\\ttotalDiffuse += pointDiffuse;\\n\\ttotalSpecular += pointSpecular;\\n\\n#endif\\n\\n#if MAX_SPOT_LIGHTS > 0\\n\\n\\ttotalDiffuse += spotDiffuse;\\n\\ttotalSpecular += spotSpecular;\\n\\n#endif\\n\\n#ifdef METAL\\n\\n\\tgl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );\\n\\n#else\\n\\n\\tgl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\\n\\n#endif\";\nTHREE.ShaderChunk.fog_pars_fragment=\"#ifdef USE_FOG\\n\\n\\tuniform vec3 fogColor;\\n\\n\\t#ifdef FOG_EXP2\\n\\n\\t\\tuniform float fogDensity;\\n\\n\\t#else\\n\\n\\t\\tuniform float fogNear;\\n\\t\\tuniform float fogFar;\\n\\t#endif\\n\\n#endif\";THREE.ShaderChunk.morphnormal_vertex=\"#ifdef USE_MORPHNORMALS\\n\\n\\tvec3 morphedNormal = vec3( 0.0 );\\n\\n\\tmorphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\\n\\tmorphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\\n\\tmorphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\\n\\tmorphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\\n\\n\\tmorphedNormal += normal;\\n\\n#endif\";\nTHREE.ShaderChunk.envmap_pars_fragment=\"#ifdef USE_ENVMAP\\n\\n\\tuniform float reflectivity;\\n\\tuniform samplerCube envMap;\\n\\tuniform float flipEnvMap;\\n\\tuniform int combine;\\n\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\n\\t\\tuniform bool useRefract;\\n\\t\\tuniform float refractionRatio;\\n\\n\\t#else\\n\\n\\t\\tvarying vec3 vReflect;\\n\\n\\t#endif\\n\\n#endif\";THREE.ShaderChunk.logdepthbuf_fragment=\"#if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT)\\n\\n\\tgl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5;\\n\\n#endif\";\nTHREE.ShaderChunk.normalmap_pars_fragment=\"#ifdef USE_NORMALMAP\\n\\n\\tuniform sampler2D normalMap;\\n\\tuniform vec2 normalScale;\\n\\n\\t\\t\\t// Per-Pixel Tangent Space Normal Mapping\\n\\t\\t\\t// http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html\\n\\n\\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\\n\\n\\t\\tvec3 q0 = dFdx( eye_pos.xyz );\\n\\t\\tvec3 q1 = dFdy( eye_pos.xyz );\\n\\t\\tvec2 st0 = dFdx( vUv.st );\\n\\t\\tvec2 st1 = dFdy( vUv.st );\\n\\n\\t\\tvec3 S = normalize( q0 * st1.t - q1 * st0.t );\\n\\t\\tvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\\n\\t\\tvec3 N = normalize( surf_norm );\\n\\n\\t\\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\\n\\t\\tmapN.xy = normalScale * mapN.xy;\\n\\t\\tmat3 tsn = mat3( S, T, N );\\n\\t\\treturn normalize( tsn * mapN );\\n\\n\\t}\\n\\n#endif\\n\";\nTHREE.ShaderChunk.lights_phong_pars_vertex=\"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\\n\\n\\tvarying vec3 vWorldPosition;\\n\\n#endif\\n\";THREE.ShaderChunk.lightmap_pars_fragment=\"#ifdef USE_LIGHTMAP\\n\\n\\tvarying vec2 vUv2;\\n\\tuniform sampler2D lightMap;\\n\\n#endif\";THREE.ShaderChunk.shadowmap_vertex=\"#ifdef USE_SHADOWMAP\\n\\n\\tfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\\n\\n\\t\\tvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\\n\\n\\t}\\n\\n#endif\";\nTHREE.ShaderChunk.lights_phong_vertex=\"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\\n\\n\\tvWorldPosition = worldPosition.xyz;\\n\\n#endif\";THREE.ShaderChunk.map_fragment=\"#ifdef USE_MAP\\n\\n\\tvec4 texelColor = texture2D( map, vUv );\\n\\n\\t#ifdef GAMMA_INPUT\\n\\n\\t\\ttexelColor.xyz *= texelColor.xyz;\\n\\n\\t#endif\\n\\n\\tgl_FragColor = gl_FragColor * texelColor;\\n\\n#endif\";THREE.ShaderChunk.lightmap_vertex=\"#ifdef USE_LIGHTMAP\\n\\n\\tvUv2 = uv2;\\n\\n#endif\";\nTHREE.ShaderChunk.map_particle_fragment=\"#ifdef USE_MAP\\n\\n\\tgl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );\\n\\n#endif\";THREE.ShaderChunk.color_pars_fragment=\"#ifdef USE_COLOR\\n\\n\\tvarying vec3 vColor;\\n\\n#endif\\n\";THREE.ShaderChunk.color_vertex=\"#ifdef USE_COLOR\\n\\n\\t#ifdef GAMMA_INPUT\\n\\n\\t\\tvColor = color * color;\\n\\n\\t#else\\n\\n\\t\\tvColor = color;\\n\\n\\t#endif\\n\\n#endif\";THREE.ShaderChunk.skinning_vertex=\"#ifdef USE_SKINNING\\n\\n\\t#ifdef USE_MORPHTARGETS\\n\\n\\tvec4 skinVertex = bindMatrix * vec4( morphed, 1.0 );\\n\\n\\t#else\\n\\n\\tvec4 skinVertex = bindMatrix * vec4( position, 1.0 );\\n\\n\\t#endif\\n\\n\\tvec4 skinned = vec4( 0.0 );\\n\\tskinned += boneMatX * skinVertex * skinWeight.x;\\n\\tskinned += boneMatY * skinVertex * skinWeight.y;\\n\\tskinned += boneMatZ * skinVertex * skinWeight.z;\\n\\tskinned += boneMatW * skinVertex * skinWeight.w;\\n\\tskinned  = bindMatrixInverse * skinned;\\n\\n#endif\\n\";\nTHREE.ShaderChunk.envmap_pars_vertex=\"#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP ) && ! defined( PHONG )\\n\\n\\tvarying vec3 vReflect;\\n\\n\\tuniform float refractionRatio;\\n\\tuniform bool useRefract;\\n\\n#endif\\n\";THREE.ShaderChunk.linear_to_gamma_fragment=\"#ifdef GAMMA_OUTPUT\\n\\n\\tgl_FragColor.xyz = sqrt( gl_FragColor.xyz );\\n\\n#endif\";THREE.ShaderChunk.color_pars_vertex=\"#ifdef USE_COLOR\\n\\n\\tvarying vec3 vColor;\\n\\n#endif\";\nTHREE.ShaderChunk.lights_lambert_pars_vertex=\"uniform vec3 ambient;\\nuniform vec3 diffuse;\\nuniform vec3 emissive;\\n\\nuniform vec3 ambientLightColor;\\n\\n#if MAX_DIR_LIGHTS > 0\\n\\n\\tuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\\n\\tuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_HEMI_LIGHTS > 0\\n\\n\\tuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\\n\\tuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\\n\\tuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_POINT_LIGHTS > 0\\n\\n\\tuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\\n\\tuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\\n\\tuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_SPOT_LIGHTS > 0\\n\\n\\tuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\\n\\tuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\\n\\tuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\\n\\n#endif\\n\\n#ifdef WRAP_AROUND\\n\\n\\tuniform vec3 wrapRGB;\\n\\n#endif\\n\";\nTHREE.ShaderChunk.map_pars_vertex=\"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\\n\\n\\tvarying vec2 vUv;\\n\\tuniform vec4 offsetRepeat;\\n\\n#endif\\n\";THREE.ShaderChunk.envmap_fragment=\"#ifdef USE_ENVMAP\\n\\n\\tvec3 reflectVec;\\n\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\n\\t\\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\\n\\n\\t\\t// http://en.wikibooks.org/wiki/GLSL_Programming/Applying_Matrix_Transformations\\n\\t\\t// Transforming Normal Vectors with the Inverse Transformation\\n\\n\\t\\tvec3 worldNormal = normalize( vec3( vec4( normal, 0.0 ) * viewMatrix ) );\\n\\n\\t\\tif ( useRefract ) {\\n\\n\\t\\t\\treflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\\n\\n\\t\\t} else { \\n\\n\\t\\t\\treflectVec = reflect( cameraToVertex, worldNormal );\\n\\n\\t\\t}\\n\\n\\t#else\\n\\n\\t\\treflectVec = vReflect;\\n\\n\\t#endif\\n\\n\\t#ifdef DOUBLE_SIDED\\n\\n\\t\\tfloat flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );\\n\\t\\tvec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\\n\\n\\t#else\\n\\n\\t\\tvec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\\n\\n\\t#endif\\n\\n\\t#ifdef GAMMA_INPUT\\n\\n\\t\\tcubeColor.xyz *= cubeColor.xyz;\\n\\n\\t#endif\\n\\n\\tif ( combine == 1 ) {\\n\\n\\t\\tgl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );\\n\\n\\t} else if ( combine == 2 ) {\\n\\n\\t\\tgl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;\\n\\n\\t} else {\\n\\n\\t\\tgl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );\\n\\n\\t}\\n\\n#endif\";\nTHREE.ShaderChunk.specularmap_pars_fragment=\"#ifdef USE_SPECULARMAP\\n\\n\\tuniform sampler2D specularMap;\\n\\n#endif\";THREE.ShaderChunk.logdepthbuf_vertex=\"#ifdef USE_LOGDEPTHBUF\\n\\n\\tgl_Position.z = log2(max(1e-6, gl_Position.w + 1.0)) * logDepthBufFC;\\n\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\n\\t\\tvFragDepth = 1.0 + gl_Position.w;\\n\\n#else\\n\\n\\t\\tgl_Position.z = (gl_Position.z - 1.0) * gl_Position.w;\\n\\n\\t#endif\\n\\n#endif\";THREE.ShaderChunk.morphtarget_pars_vertex=\"#ifdef USE_MORPHTARGETS\\n\\n\\t#ifndef USE_MORPHNORMALS\\n\\n\\tuniform float morphTargetInfluences[ 8 ];\\n\\n\\t#else\\n\\n\\tuniform float morphTargetInfluences[ 4 ];\\n\\n\\t#endif\\n\\n#endif\";\nTHREE.ShaderChunk.specularmap_fragment=\"float specularStrength;\\n\\n#ifdef USE_SPECULARMAP\\n\\n\\tvec4 texelSpecular = texture2D( specularMap, vUv );\\n\\tspecularStrength = texelSpecular.r;\\n\\n#else\\n\\n\\tspecularStrength = 1.0;\\n\\n#endif\";THREE.ShaderChunk.fog_fragment=\"#ifdef USE_FOG\\n\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\n\\t\\tfloat depth = gl_FragDepthEXT / gl_FragCoord.w;\\n\\n\\t#else\\n\\n\\t\\tfloat depth = gl_FragCoord.z / gl_FragCoord.w;\\n\\n\\t#endif\\n\\n\\t#ifdef FOG_EXP2\\n\\n\\t\\tconst float LOG2 = 1.442695;\\n\\t\\tfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\\n\\t\\tfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\\n\\n\\t#else\\n\\n\\t\\tfloat fogFactor = smoothstep( fogNear, fogFar, depth );\\n\\n\\t#endif\\n\\t\\n\\tgl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\\n\\n#endif\";\nTHREE.ShaderChunk.bumpmap_pars_fragment=\"#ifdef USE_BUMPMAP\\n\\n\\tuniform sampler2D bumpMap;\\n\\tuniform float bumpScale;\\n\\n\\t\\t\\t// Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen\\n\\t\\t\\t//\\thttp://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html\\n\\n\\t\\t\\t// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)\\n\\n\\tvec2 dHdxy_fwd() {\\n\\n\\t\\tvec2 dSTdx = dFdx( vUv );\\n\\t\\tvec2 dSTdy = dFdy( vUv );\\n\\n\\t\\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\\n\\t\\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\\n\\t\\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\\n\\n\\t\\treturn vec2( dBx, dBy );\\n\\n\\t}\\n\\n\\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\\n\\n\\t\\tvec3 vSigmaX = dFdx( surf_pos );\\n\\t\\tvec3 vSigmaY = dFdy( surf_pos );\\n\\t\\tvec3 vN = surf_norm;\\t\\t// normalized\\n\\n\\t\\tvec3 R1 = cross( vSigmaY, vN );\\n\\t\\tvec3 R2 = cross( vN, vSigmaX );\\n\\n\\t\\tfloat fDet = dot( vSigmaX, R1 );\\n\\n\\t\\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\\n\\t\\treturn normalize( abs( fDet ) * surf_norm - vGrad );\\n\\n\\t}\\n\\n#endif\";\nTHREE.ShaderChunk.defaultnormal_vertex=\"vec3 objectNormal;\\n\\n#ifdef USE_SKINNING\\n\\n\\tobjectNormal = skinnedNormal.xyz;\\n\\n#endif\\n\\n#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )\\n\\n\\tobjectNormal = morphedNormal;\\n\\n#endif\\n\\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )\\n\\n\\tobjectNormal = normal;\\n\\n#endif\\n\\n#ifdef FLIP_SIDED\\n\\n\\tobjectNormal = -objectNormal;\\n\\n#endif\\n\\nvec3 transformedNormal = normalMatrix * objectNormal;\";\nTHREE.ShaderChunk.lights_phong_pars_fragment=\"uniform vec3 ambientLightColor;\\n\\n#if MAX_DIR_LIGHTS > 0\\n\\n\\tuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\\n\\tuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_HEMI_LIGHTS > 0\\n\\n\\tuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\\n\\tuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\\n\\tuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_POINT_LIGHTS > 0\\n\\n\\tuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\\n\\n\\tuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\\n\\tuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_SPOT_LIGHTS > 0\\n\\n\\tuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\\n\\tuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\\n\\tuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\\n\\n\\tuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\\n\\n#endif\\n\\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\\n\\n\\tvarying vec3 vWorldPosition;\\n\\n#endif\\n\\n#ifdef WRAP_AROUND\\n\\n\\tuniform vec3 wrapRGB;\\n\\n#endif\\n\\nvarying vec3 vViewPosition;\\nvarying vec3 vNormal;\";\nTHREE.ShaderChunk.skinbase_vertex=\"#ifdef USE_SKINNING\\n\\n\\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\\n\\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\\n\\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\\n\\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\\n\\n#endif\";THREE.ShaderChunk.map_vertex=\"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\\n\\n\\tvUv = uv * offsetRepeat.zw + offsetRepeat.xy;\\n\\n#endif\";\nTHREE.ShaderChunk.lightmap_fragment=\"#ifdef USE_LIGHTMAP\\n\\n\\tgl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );\\n\\n#endif\";THREE.ShaderChunk.shadowmap_pars_vertex=\"#ifdef USE_SHADOWMAP\\n\\n\\tvarying vec4 vShadowCoord[ MAX_SHADOWS ];\\n\\tuniform mat4 shadowMatrix[ MAX_SHADOWS ];\\n\\n#endif\";THREE.ShaderChunk.color_fragment=\"#ifdef USE_COLOR\\n\\n\\tgl_FragColor = gl_FragColor * vec4( vColor, 1.0 );\\n\\n#endif\";THREE.ShaderChunk.morphtarget_vertex=\"#ifdef USE_MORPHTARGETS\\n\\n\\tvec3 morphed = vec3( 0.0 );\\n\\tmorphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\\n\\tmorphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\\n\\tmorphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\\n\\tmorphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\\n\\n\\t#ifndef USE_MORPHNORMALS\\n\\n\\tmorphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\\n\\tmorphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\\n\\tmorphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\\n\\tmorphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\\n\\n\\t#endif\\n\\n\\tmorphed += position;\\n\\n#endif\";\nTHREE.ShaderChunk.envmap_vertex=\"#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP ) && ! defined( PHONG )\\n\\n\\tvec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;\\n\\tworldNormal = normalize( worldNormal );\\n\\n\\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\\n\\n\\tif ( useRefract ) {\\n\\n\\t\\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\\n\\n\\t} else {\\n\\n\\t\\tvReflect = reflect( cameraToVertex, worldNormal );\\n\\n\\t}\\n\\n#endif\";\nTHREE.ShaderChunk.shadowmap_fragment=\"#ifdef USE_SHADOWMAP\\n\\n\\t#ifdef SHADOWMAP_DEBUG\\n\\n\\t\\tvec3 frustumColors[3];\\n\\t\\tfrustumColors[0] = vec3( 1.0, 0.5, 0.0 );\\n\\t\\tfrustumColors[1] = vec3( 0.0, 1.0, 0.8 );\\n\\t\\tfrustumColors[2] = vec3( 0.0, 0.5, 1.0 );\\n\\n\\t#endif\\n\\n\\t#ifdef SHADOWMAP_CASCADE\\n\\n\\t\\tint inFrustumCount = 0;\\n\\n\\t#endif\\n\\n\\tfloat fDepth;\\n\\tvec3 shadowColor = vec3( 1.0 );\\n\\n\\tfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\\n\\n\\t\\tvec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\\n\\n\\t\\t\\t\\t// if ( something && something ) breaks ATI OpenGL shader compiler\\n\\t\\t\\t\\t// if ( all( something, something ) ) using this instead\\n\\n\\t\\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\\n\\t\\tbool inFrustum = all( inFrustumVec );\\n\\n\\t\\t\\t\\t// don't shadow pixels outside of light frustum\\n\\t\\t\\t\\t// use just first frustum (for cascades)\\n\\t\\t\\t\\t// don't shadow pixels behind far plane of light frustum\\n\\n\\t\\t#ifdef SHADOWMAP_CASCADE\\n\\n\\t\\t\\tinFrustumCount += int( inFrustum );\\n\\t\\t\\tbvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );\\n\\n\\t\\t#else\\n\\n\\t\\t\\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\\n\\n\\t\\t#endif\\n\\n\\t\\tbool frustumTest = all( frustumTestVec );\\n\\n\\t\\tif ( frustumTest ) {\\n\\n\\t\\t\\tshadowCoord.z += shadowBias[ i ];\\n\\n\\t\\t\\t#if defined( SHADOWMAP_TYPE_PCF )\\n\\n\\t\\t\\t\\t\\t\\t// Percentage-close filtering\\n\\t\\t\\t\\t\\t\\t// (9 pixel kernel)\\n\\t\\t\\t\\t\\t\\t// http://fabiensanglard.net/shadowmappingPCF/\\n\\n\\t\\t\\t\\tfloat shadow = 0.0;\\n\\n\\t\\t/*\\n\\t\\t\\t\\t\\t\\t// nested loops breaks shader compiler / validator on some ATI cards when using OpenGL\\n\\t\\t\\t\\t\\t\\t// must enroll loop manually\\n\\n\\t\\t\\t\\tfor ( float y = -1.25; y <= 1.25; y += 1.25 )\\n\\t\\t\\t\\t\\tfor ( float x = -1.25; x <= 1.25; x += 1.25 ) {\\n\\n\\t\\t\\t\\t\\t\\tvec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );\\n\\n\\t\\t\\t\\t\\t\\t\\t\\t// doesn't seem to produce any noticeable visual difference compared to simple texture2D lookup\\n\\t\\t\\t\\t\\t\\t\\t\\t//vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );\\n\\n\\t\\t\\t\\t\\t\\tfloat fDepth = unpackDepth( rgbaDepth );\\n\\n\\t\\t\\t\\t\\t\\tif ( fDepth < shadowCoord.z )\\n\\t\\t\\t\\t\\t\\t\\tshadow += 1.0;\\n\\n\\t\\t\\t\\t}\\n\\n\\t\\t\\t\\tshadow /= 9.0;\\n\\n\\t\\t*/\\n\\n\\t\\t\\t\\tconst float shadowDelta = 1.0 / 9.0;\\n\\n\\t\\t\\t\\tfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\\n\\t\\t\\t\\tfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\\n\\n\\t\\t\\t\\tfloat dx0 = -1.25 * xPixelOffset;\\n\\t\\t\\t\\tfloat dy0 = -1.25 * yPixelOffset;\\n\\t\\t\\t\\tfloat dx1 = 1.25 * xPixelOffset;\\n\\t\\t\\t\\tfloat dy1 = 1.25 * yPixelOffset;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\\n\\n\\t\\t\\t\\tshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\\n\\n\\t\\t\\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\\n\\n\\t\\t\\t\\t\\t\\t// Percentage-close filtering\\n\\t\\t\\t\\t\\t\\t// (9 pixel kernel)\\n\\t\\t\\t\\t\\t\\t// http://fabiensanglard.net/shadowmappingPCF/\\n\\n\\t\\t\\t\\tfloat shadow = 0.0;\\n\\n\\t\\t\\t\\tfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\\n\\t\\t\\t\\tfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\\n\\n\\t\\t\\t\\tfloat dx0 = -1.0 * xPixelOffset;\\n\\t\\t\\t\\tfloat dy0 = -1.0 * yPixelOffset;\\n\\t\\t\\t\\tfloat dx1 = 1.0 * xPixelOffset;\\n\\t\\t\\t\\tfloat dy1 = 1.0 * yPixelOffset;\\n\\n\\t\\t\\t\\tmat3 shadowKernel;\\n\\t\\t\\t\\tmat3 depthKernel;\\n\\n\\t\\t\\t\\tdepthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\\n\\t\\t\\t\\tdepthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\\n\\t\\t\\t\\tdepthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\\n\\t\\t\\t\\tdepthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\\n\\t\\t\\t\\tdepthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\\n\\t\\t\\t\\tdepthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\\n\\t\\t\\t\\tdepthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\\n\\t\\t\\t\\tdepthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\\n\\t\\t\\t\\tdepthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\\n\\n\\t\\t\\t\\tvec3 shadowZ = vec3( shadowCoord.z );\\n\\t\\t\\t\\tshadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));\\n\\t\\t\\t\\tshadowKernel[0] *= vec3(0.25);\\n\\n\\t\\t\\t\\tshadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));\\n\\t\\t\\t\\tshadowKernel[1] *= vec3(0.25);\\n\\n\\t\\t\\t\\tshadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));\\n\\t\\t\\t\\tshadowKernel[2] *= vec3(0.25);\\n\\n\\t\\t\\t\\tvec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\\n\\n\\t\\t\\t\\tshadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\\n\\t\\t\\t\\tshadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\\n\\n\\t\\t\\t\\tvec4 shadowValues;\\n\\t\\t\\t\\tshadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\\n\\t\\t\\t\\tshadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\\n\\t\\t\\t\\tshadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\\n\\t\\t\\t\\tshadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\\n\\n\\t\\t\\t\\tshadow = dot( shadowValues, vec4( 1.0 ) );\\n\\n\\t\\t\\t\\tshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\\n\\n\\t\\t\\t#else\\n\\n\\t\\t\\t\\tvec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\\n\\t\\t\\t\\tfloat fDepth = unpackDepth( rgbaDepth );\\n\\n\\t\\t\\t\\tif ( fDepth < shadowCoord.z )\\n\\n\\t\\t// spot with multiple shadows is darker\\n\\n\\t\\t\\t\\t\\tshadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );\\n\\n\\t\\t// spot with multiple shadows has the same color as single shadow spot\\n\\n\\t\\t// \\t\\t\\t\\t\\tshadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t}\\n\\n\\n\\t\\t#ifdef SHADOWMAP_DEBUG\\n\\n\\t\\t\\t#ifdef SHADOWMAP_CASCADE\\n\\n\\t\\t\\t\\tif ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];\\n\\n\\t\\t\\t#else\\n\\n\\t\\t\\t\\tif ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];\\n\\n\\t\\t\\t#endif\\n\\n\\t\\t#endif\\n\\n\\t}\\n\\n\\t#ifdef GAMMA_OUTPUT\\n\\n\\t\\tshadowColor *= shadowColor;\\n\\n\\t#endif\\n\\n\\tgl_FragColor.xyz = gl_FragColor.xyz * shadowColor;\\n\\n#endif\\n\";\nTHREE.ShaderChunk.worldpos_vertex=\"#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )\\n\\n\\t#ifdef USE_SKINNING\\n\\n\\t\\tvec4 worldPosition = modelMatrix * skinned;\\n\\n\\t#endif\\n\\n\\t#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )\\n\\n\\t\\tvec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );\\n\\n\\t#endif\\n\\n\\t#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )\\n\\n\\t\\tvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\\n\\n\\t#endif\\n\\n#endif\";\nTHREE.ShaderChunk.shadowmap_pars_fragment=\"#ifdef USE_SHADOWMAP\\n\\n\\tuniform sampler2D shadowMap[ MAX_SHADOWS ];\\n\\tuniform vec2 shadowMapSize[ MAX_SHADOWS ];\\n\\n\\tuniform float shadowDarkness[ MAX_SHADOWS ];\\n\\tuniform float shadowBias[ MAX_SHADOWS ];\\n\\n\\tvarying vec4 vShadowCoord[ MAX_SHADOWS ];\\n\\n\\tfloat unpackDepth( const in vec4 rgba_depth ) {\\n\\n\\t\\tconst vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );\\n\\t\\tfloat depth = dot( rgba_depth, bit_shift );\\n\\t\\treturn depth;\\n\\n\\t}\\n\\n#endif\";\nTHREE.ShaderChunk.skinning_pars_vertex=\"#ifdef USE_SKINNING\\n\\n\\tuniform mat4 bindMatrix;\\n\\tuniform mat4 bindMatrixInverse;\\n\\n\\t#ifdef BONE_TEXTURE\\n\\n\\t\\tuniform sampler2D boneTexture;\\n\\t\\tuniform int boneTextureWidth;\\n\\t\\tuniform int boneTextureHeight;\\n\\n\\t\\tmat4 getBoneMatrix( const in float i ) {\\n\\n\\t\\t\\tfloat j = i * 4.0;\\n\\t\\t\\tfloat x = mod( j, float( boneTextureWidth ) );\\n\\t\\t\\tfloat y = floor( j / float( boneTextureWidth ) );\\n\\n\\t\\t\\tfloat dx = 1.0 / float( boneTextureWidth );\\n\\t\\t\\tfloat dy = 1.0 / float( boneTextureHeight );\\n\\n\\t\\t\\ty = dy * ( y + 0.5 );\\n\\n\\t\\t\\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\\n\\t\\t\\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\\n\\t\\t\\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\\n\\t\\t\\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\\n\\n\\t\\t\\tmat4 bone = mat4( v1, v2, v3, v4 );\\n\\n\\t\\t\\treturn bone;\\n\\n\\t\\t}\\n\\n\\t#else\\n\\n\\t\\tuniform mat4 boneGlobalMatrices[ MAX_BONES ];\\n\\n\\t\\tmat4 getBoneMatrix( const in float i ) {\\n\\n\\t\\t\\tmat4 bone = boneGlobalMatrices[ int(i) ];\\n\\t\\t\\treturn bone;\\n\\n\\t\\t}\\n\\n\\t#endif\\n\\n#endif\\n\";\nTHREE.ShaderChunk.logdepthbuf_pars_fragment=\"#ifdef USE_LOGDEPTHBUF\\n\\n\\tuniform float logDepthBufFC;\\n\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\n\\t\\t#extension GL_EXT_frag_depth : enable\\n\\t\\tvarying float vFragDepth;\\n\\n\\t#endif\\n\\n#endif\";THREE.ShaderChunk.alphamap_fragment=\"#ifdef USE_ALPHAMAP\\n\\n\\tgl_FragColor.a *= texture2D( alphaMap, vUv ).g;\\n\\n#endif\\n\";THREE.ShaderChunk.alphamap_pars_fragment=\"#ifdef USE_ALPHAMAP\\n\\n\\tuniform sampler2D alphaMap;\\n\\n#endif\\n\";\nTHREE.UniformsUtils={merge:function(a){for(var b={},c=0;c<a.length;c++){var d=this.clone(a[c]),e;for(e in d)b[e]=d[e]}return b},clone:function(a){var b={},c;for(c in a){b[c]={};for(var d in a[c]){var e=a[c][d];b[c][d]=e instanceof THREE.Color||e instanceof THREE.Vector2||e instanceof THREE.Vector3||e instanceof THREE.Vector4||e instanceof THREE.Matrix4||e instanceof THREE.Texture?e.clone():e instanceof Array?e.slice():e}}return b}};\nTHREE.UniformsLib={common:{diffuse:{type:\"c\",value:new THREE.Color(15658734)},opacity:{type:\"f\",value:1},map:{type:\"t\",value:null},offsetRepeat:{type:\"v4\",value:new THREE.Vector4(0,0,1,1)},lightMap:{type:\"t\",value:null},specularMap:{type:\"t\",value:null},alphaMap:{type:\"t\",value:null},envMap:{type:\"t\",value:null},flipEnvMap:{type:\"f\",value:-1},useRefract:{type:\"i\",value:0},reflectivity:{type:\"f\",value:1},refractionRatio:{type:\"f\",value:.98},combine:{type:\"i\",value:0},morphTargetInfluences:{type:\"f\",\nvalue:0}},bump:{bumpMap:{type:\"t\",value:null},bumpScale:{type:\"f\",value:1}},normalmap:{normalMap:{type:\"t\",value:null},normalScale:{type:\"v2\",value:new THREE.Vector2(1,1)}},fog:{fogDensity:{type:\"f\",value:2.5E-4},fogNear:{type:\"f\",value:1},fogFar:{type:\"f\",value:2E3},fogColor:{type:\"c\",value:new THREE.Color(16777215)}},lights:{ambientLightColor:{type:\"fv\",value:[]},directionalLightDirection:{type:\"fv\",value:[]},directionalLightColor:{type:\"fv\",value:[]},hemisphereLightDirection:{type:\"fv\",value:[]},\nhemisphereLightSkyColor:{type:\"fv\",value:[]},hemisphereLightGroundColor:{type:\"fv\",value:[]},pointLightColor:{type:\"fv\",value:[]},pointLightPosition:{type:\"fv\",value:[]},pointLightDistance:{type:\"fv1\",value:[]},spotLightColor:{type:\"fv\",value:[]},spotLightPosition:{type:\"fv\",value:[]},spotLightDirection:{type:\"fv\",value:[]},spotLightDistance:{type:\"fv1\",value:[]},spotLightAngleCos:{type:\"fv1\",value:[]},spotLightExponent:{type:\"fv1\",value:[]}},particle:{psColor:{type:\"c\",value:new THREE.Color(15658734)},\nopacity:{type:\"f\",value:1},size:{type:\"f\",value:1},scale:{type:\"f\",value:1},map:{type:\"t\",value:null},fogDensity:{type:\"f\",value:2.5E-4},fogNear:{type:\"f\",value:1},fogFar:{type:\"f\",value:2E3},fogColor:{type:\"c\",value:new THREE.Color(16777215)}},shadowmap:{shadowMap:{type:\"tv\",value:[]},shadowMapSize:{type:\"v2v\",value:[]},shadowBias:{type:\"fv1\",value:[]},shadowDarkness:{type:\"fv1\",value:[]},shadowMatrix:{type:\"m4v\",value:[]}}};\nTHREE.ShaderLib={basic:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.common,THREE.UniformsLib.fog,THREE.UniformsLib.shadowmap]),vertexShader:[THREE.ShaderChunk.map_pars_vertex,THREE.ShaderChunk.lightmap_pars_vertex,THREE.ShaderChunk.envmap_pars_vertex,THREE.ShaderChunk.color_pars_vertex,THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.map_vertex,\nTHREE.ShaderChunk.lightmap_vertex,THREE.ShaderChunk.color_vertex,THREE.ShaderChunk.skinbase_vertex,\"\\t#ifdef USE_ENVMAP\",THREE.ShaderChunk.morphnormal_vertex,THREE.ShaderChunk.skinnormal_vertex,THREE.ShaderChunk.defaultnormal_vertex,\"\\t#endif\",THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.worldpos_vertex,THREE.ShaderChunk.envmap_vertex,THREE.ShaderChunk.shadowmap_vertex,\"}\"].join(\"\\n\"),\nfragmentShader:[\"uniform vec3 diffuse;\\nuniform float opacity;\",THREE.ShaderChunk.color_pars_fragment,THREE.ShaderChunk.map_pars_fragment,THREE.ShaderChunk.alphamap_pars_fragment,THREE.ShaderChunk.lightmap_pars_fragment,THREE.ShaderChunk.envmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.specularmap_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tgl_FragColor = vec4( diffuse, opacity );\",THREE.ShaderChunk.logdepthbuf_fragment,\nTHREE.ShaderChunk.map_fragment,THREE.ShaderChunk.alphamap_fragment,THREE.ShaderChunk.alphatest_fragment,THREE.ShaderChunk.specularmap_fragment,THREE.ShaderChunk.lightmap_fragment,THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.envmap_fragment,THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,\"}\"].join(\"\\n\")},lambert:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.common,THREE.UniformsLib.fog,THREE.UniformsLib.lights,THREE.UniformsLib.shadowmap,\n{ambient:{type:\"c\",value:new THREE.Color(16777215)},emissive:{type:\"c\",value:new THREE.Color(0)},wrapRGB:{type:\"v3\",value:new THREE.Vector3(1,1,1)}}]),vertexShader:[\"#define LAMBERT\\nvarying vec3 vLightFront;\\n#ifdef DOUBLE_SIDED\\n\\tvarying vec3 vLightBack;\\n#endif\",THREE.ShaderChunk.map_pars_vertex,THREE.ShaderChunk.lightmap_pars_vertex,THREE.ShaderChunk.envmap_pars_vertex,THREE.ShaderChunk.lights_lambert_pars_vertex,THREE.ShaderChunk.color_pars_vertex,THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,\nTHREE.ShaderChunk.shadowmap_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.map_vertex,THREE.ShaderChunk.lightmap_vertex,THREE.ShaderChunk.color_vertex,THREE.ShaderChunk.morphnormal_vertex,THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,THREE.ShaderChunk.defaultnormal_vertex,THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.worldpos_vertex,\nTHREE.ShaderChunk.envmap_vertex,THREE.ShaderChunk.lights_lambert_vertex,THREE.ShaderChunk.shadowmap_vertex,\"}\"].join(\"\\n\"),fragmentShader:[\"uniform float opacity;\\nvarying vec3 vLightFront;\\n#ifdef DOUBLE_SIDED\\n\\tvarying vec3 vLightBack;\\n#endif\",THREE.ShaderChunk.color_pars_fragment,THREE.ShaderChunk.map_pars_fragment,THREE.ShaderChunk.alphamap_pars_fragment,THREE.ShaderChunk.lightmap_pars_fragment,THREE.ShaderChunk.envmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.shadowmap_pars_fragment,\nTHREE.ShaderChunk.specularmap_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tgl_FragColor = vec4( vec3( 1.0 ), opacity );\",THREE.ShaderChunk.logdepthbuf_fragment,THREE.ShaderChunk.map_fragment,THREE.ShaderChunk.alphamap_fragment,THREE.ShaderChunk.alphatest_fragment,THREE.ShaderChunk.specularmap_fragment,\"\\t#ifdef DOUBLE_SIDED\\n\\t\\tif ( gl_FrontFacing )\\n\\t\\t\\tgl_FragColor.xyz *= vLightFront;\\n\\t\\telse\\n\\t\\t\\tgl_FragColor.xyz *= vLightBack;\\n\\t#else\\n\\t\\tgl_FragColor.xyz *= vLightFront;\\n\\t#endif\",\nTHREE.ShaderChunk.lightmap_fragment,THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.envmap_fragment,THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,\"}\"].join(\"\\n\")},phong:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.common,THREE.UniformsLib.bump,THREE.UniformsLib.normalmap,THREE.UniformsLib.fog,THREE.UniformsLib.lights,THREE.UniformsLib.shadowmap,{ambient:{type:\"c\",value:new THREE.Color(16777215)},emissive:{type:\"c\",value:new THREE.Color(0)},\nspecular:{type:\"c\",value:new THREE.Color(1118481)},shininess:{type:\"f\",value:30},wrapRGB:{type:\"v3\",value:new THREE.Vector3(1,1,1)}}]),vertexShader:[\"#define PHONG\\nvarying vec3 vViewPosition;\\nvarying vec3 vNormal;\",THREE.ShaderChunk.map_pars_vertex,THREE.ShaderChunk.lightmap_pars_vertex,THREE.ShaderChunk.envmap_pars_vertex,THREE.ShaderChunk.lights_phong_pars_vertex,THREE.ShaderChunk.color_pars_vertex,THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,\nTHREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.map_vertex,THREE.ShaderChunk.lightmap_vertex,THREE.ShaderChunk.color_vertex,THREE.ShaderChunk.morphnormal_vertex,THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,THREE.ShaderChunk.defaultnormal_vertex,\"\\tvNormal = normalize( transformedNormal );\",THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,\"\\tvViewPosition = -mvPosition.xyz;\",\nTHREE.ShaderChunk.worldpos_vertex,THREE.ShaderChunk.envmap_vertex,THREE.ShaderChunk.lights_phong_vertex,THREE.ShaderChunk.shadowmap_vertex,\"}\"].join(\"\\n\"),fragmentShader:[\"#define PHONG\\nuniform vec3 diffuse;\\nuniform float opacity;\\nuniform vec3 ambient;\\nuniform vec3 emissive;\\nuniform vec3 specular;\\nuniform float shininess;\",THREE.ShaderChunk.color_pars_fragment,THREE.ShaderChunk.map_pars_fragment,THREE.ShaderChunk.alphamap_pars_fragment,THREE.ShaderChunk.lightmap_pars_fragment,THREE.ShaderChunk.envmap_pars_fragment,\nTHREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.lights_phong_pars_fragment,THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.bumpmap_pars_fragment,THREE.ShaderChunk.normalmap_pars_fragment,THREE.ShaderChunk.specularmap_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tgl_FragColor = vec4( vec3( 1.0 ), opacity );\",THREE.ShaderChunk.logdepthbuf_fragment,THREE.ShaderChunk.map_fragment,THREE.ShaderChunk.alphamap_fragment,THREE.ShaderChunk.alphatest_fragment,THREE.ShaderChunk.specularmap_fragment,\nTHREE.ShaderChunk.lights_phong_fragment,THREE.ShaderChunk.lightmap_fragment,THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.envmap_fragment,THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,\"}\"].join(\"\\n\")},particle_basic:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.particle,THREE.UniformsLib.shadowmap]),vertexShader:[\"uniform float size;\\nuniform float scale;\",THREE.ShaderChunk.color_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,\nTHREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.color_vertex,\"\\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\\n\\t#ifdef USE_SIZEATTENUATION\\n\\t\\tgl_PointSize = size * ( scale / length( mvPosition.xyz ) );\\n\\t#else\\n\\t\\tgl_PointSize = size;\\n\\t#endif\\n\\tgl_Position = projectionMatrix * mvPosition;\",THREE.ShaderChunk.logdepthbuf_vertex,THREE.ShaderChunk.worldpos_vertex,THREE.ShaderChunk.shadowmap_vertex,\"}\"].join(\"\\n\"),fragmentShader:[\"uniform vec3 psColor;\\nuniform float opacity;\",\nTHREE.ShaderChunk.color_pars_fragment,THREE.ShaderChunk.map_particle_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tgl_FragColor = vec4( psColor, opacity );\",THREE.ShaderChunk.logdepthbuf_fragment,THREE.ShaderChunk.map_particle_fragment,THREE.ShaderChunk.alphatest_fragment,THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.fog_fragment,\"}\"].join(\"\\n\")},dashed:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.common,\nTHREE.UniformsLib.fog,{scale:{type:\"f\",value:1},dashSize:{type:\"f\",value:1},totalSize:{type:\"f\",value:2}}]),vertexShader:[\"uniform float scale;\\nattribute float lineDistance;\\nvarying float vLineDistance;\",THREE.ShaderChunk.color_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.color_vertex,\"\\tvLineDistance = scale * lineDistance;\\n\\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\\n\\tgl_Position = projectionMatrix * mvPosition;\",THREE.ShaderChunk.logdepthbuf_vertex,\n\"}\"].join(\"\\n\"),fragmentShader:[\"uniform vec3 diffuse;\\nuniform float opacity;\\nuniform float dashSize;\\nuniform float totalSize;\\nvarying float vLineDistance;\",THREE.ShaderChunk.color_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\\n\\t\\tdiscard;\\n\\t}\\n\\tgl_FragColor = vec4( diffuse, opacity );\",THREE.ShaderChunk.logdepthbuf_fragment,THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.fog_fragment,\n\"}\"].join(\"\\n\")},depth:{uniforms:{mNear:{type:\"f\",value:1},mFar:{type:\"f\",value:2E3},opacity:{type:\"f\",value:1}},vertexShader:[THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,\"}\"].join(\"\\n\"),fragmentShader:[\"uniform float mNear;\\nuniform float mFar;\\nuniform float opacity;\",THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\",THREE.ShaderChunk.logdepthbuf_fragment,\n\"\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tfloat depth = gl_FragDepthEXT / gl_FragCoord.w;\\n\\t#else\\n\\t\\tfloat depth = gl_FragCoord.z / gl_FragCoord.w;\\n\\t#endif\\n\\tfloat color = 1.0 - smoothstep( mNear, mFar, depth );\\n\\tgl_FragColor = vec4( vec3( color ), opacity );\\n}\"].join(\"\\n\")},normal:{uniforms:{opacity:{type:\"f\",value:1}},vertexShader:[\"varying vec3 vNormal;\",THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\\n\\tvNormal = normalize( normalMatrix * normal );\",\nTHREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,\"}\"].join(\"\\n\"),fragmentShader:[\"uniform float opacity;\\nvarying vec3 vNormal;\",THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tgl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );\",THREE.ShaderChunk.logdepthbuf_fragment,\"}\"].join(\"\\n\")},normalmap:{uniforms:THREE.UniformsUtils.merge([THREE.UniformsLib.fog,THREE.UniformsLib.lights,THREE.UniformsLib.shadowmap,{enableAO:{type:\"i\",\nvalue:0},enableDiffuse:{type:\"i\",value:0},enableSpecular:{type:\"i\",value:0},enableReflection:{type:\"i\",value:0},enableDisplacement:{type:\"i\",value:0},tDisplacement:{type:\"t\",value:null},tDiffuse:{type:\"t\",value:null},tCube:{type:\"t\",value:null},tNormal:{type:\"t\",value:null},tSpecular:{type:\"t\",value:null},tAO:{type:\"t\",value:null},uNormalScale:{type:\"v2\",value:new THREE.Vector2(1,1)},uDisplacementBias:{type:\"f\",value:0},uDisplacementScale:{type:\"f\",value:1},diffuse:{type:\"c\",value:new THREE.Color(16777215)},\nspecular:{type:\"c\",value:new THREE.Color(1118481)},ambient:{type:\"c\",value:new THREE.Color(16777215)},shininess:{type:\"f\",value:30},opacity:{type:\"f\",value:1},useRefract:{type:\"i\",value:0},refractionRatio:{type:\"f\",value:.98},reflectivity:{type:\"f\",value:.5},uOffset:{type:\"v2\",value:new THREE.Vector2(0,0)},uRepeat:{type:\"v2\",value:new THREE.Vector2(1,1)},wrapRGB:{type:\"v3\",value:new THREE.Vector3(1,1,1)}}]),fragmentShader:[\"uniform vec3 ambient;\\nuniform vec3 diffuse;\\nuniform vec3 specular;\\nuniform float shininess;\\nuniform float opacity;\\nuniform bool enableDiffuse;\\nuniform bool enableSpecular;\\nuniform bool enableAO;\\nuniform bool enableReflection;\\nuniform sampler2D tDiffuse;\\nuniform sampler2D tNormal;\\nuniform sampler2D tSpecular;\\nuniform sampler2D tAO;\\nuniform samplerCube tCube;\\nuniform vec2 uNormalScale;\\nuniform bool useRefract;\\nuniform float refractionRatio;\\nuniform float reflectivity;\\nvarying vec3 vTangent;\\nvarying vec3 vBinormal;\\nvarying vec3 vNormal;\\nvarying vec2 vUv;\\nuniform vec3 ambientLightColor;\\n#if MAX_DIR_LIGHTS > 0\\n\\tuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\\n\\tuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\\n#endif\\n#if MAX_HEMI_LIGHTS > 0\\n\\tuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\\n\\tuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\\n\\tuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\\n#endif\\n#if MAX_POINT_LIGHTS > 0\\n\\tuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\\n\\tuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\\n\\tuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\\n#endif\\n#if MAX_SPOT_LIGHTS > 0\\n\\tuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\\n\\tuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\\n\\tuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\\n\\tuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\\n#endif\\n#ifdef WRAP_AROUND\\n\\tuniform vec3 wrapRGB;\\n#endif\\nvarying vec3 vWorldPosition;\\nvarying vec3 vViewPosition;\",\nTHREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\",THREE.ShaderChunk.logdepthbuf_fragment,\"\\tgl_FragColor = vec4( vec3( 1.0 ), opacity );\\n\\tvec3 specularTex = vec3( 1.0 );\\n\\tvec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;\\n\\tnormalTex.xy *= uNormalScale;\\n\\tnormalTex = normalize( normalTex );\\n\\tif( enableDiffuse ) {\\n\\t\\t#ifdef GAMMA_INPUT\\n\\t\\t\\tvec4 texelColor = texture2D( tDiffuse, vUv );\\n\\t\\t\\ttexelColor.xyz *= texelColor.xyz;\\n\\t\\t\\tgl_FragColor = gl_FragColor * texelColor;\\n\\t\\t#else\\n\\t\\t\\tgl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );\\n\\t\\t#endif\\n\\t}\\n\\tif( enableAO ) {\\n\\t\\t#ifdef GAMMA_INPUT\\n\\t\\t\\tvec4 aoColor = texture2D( tAO, vUv );\\n\\t\\t\\taoColor.xyz *= aoColor.xyz;\\n\\t\\t\\tgl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;\\n\\t\\t#else\\n\\t\\t\\tgl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;\\n\\t\\t#endif\\n\\t}\",\nTHREE.ShaderChunk.alphatest_fragment,\"\\tif( enableSpecular )\\n\\t\\tspecularTex = texture2D( tSpecular, vUv ).xyz;\\n\\tmat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );\\n\\tvec3 finalNormal = tsb * normalTex;\\n\\t#ifdef FLIP_SIDED\\n\\t\\tfinalNormal = -finalNormal;\\n\\t#endif\\n\\tvec3 normal = normalize( finalNormal );\\n\\tvec3 viewPosition = normalize( vViewPosition );\\n\\t#if MAX_POINT_LIGHTS > 0\\n\\t\\tvec3 pointDiffuse = vec3( 0.0 );\\n\\t\\tvec3 pointSpecular = vec3( 0.0 );\\n\\t\\tfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\\n\\t\\t\\tvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\\n\\t\\t\\tvec3 pointVector = lPosition.xyz + vViewPosition.xyz;\\n\\t\\t\\tfloat pointDistance = 1.0;\\n\\t\\t\\tif ( pointLightDistance[ i ] > 0.0 )\\n\\t\\t\\t\\tpointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );\\n\\t\\t\\tpointVector = normalize( pointVector );\\n\\t\\t\\t#ifdef WRAP_AROUND\\n\\t\\t\\t\\tfloat pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );\\n\\t\\t\\t\\tfloat pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );\\n\\t\\t\\t\\tvec3 pointDiffuseWeight = mix( vec3( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tfloat pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );\\n\\t\\t\\t#endif\\n\\t\\t\\tpointDiffuse += pointDistance * pointLightColor[ i ] * diffuse * pointDiffuseWeight;\\n\\t\\t\\tvec3 pointHalfVector = normalize( pointVector + viewPosition );\\n\\t\\t\\tfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\\n\\t\\t\\tfloat pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );\\n\\t\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\t\\t\\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( pointVector, pointHalfVector ), 0.0 ), 5.0 );\\n\\t\\t\\tpointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;\\n\\t\\t}\\n\\t#endif\\n\\t#if MAX_SPOT_LIGHTS > 0\\n\\t\\tvec3 spotDiffuse = vec3( 0.0 );\\n\\t\\tvec3 spotSpecular = vec3( 0.0 );\\n\\t\\tfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\\n\\t\\t\\tvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\\n\\t\\t\\tvec3 spotVector = lPosition.xyz + vViewPosition.xyz;\\n\\t\\t\\tfloat spotDistance = 1.0;\\n\\t\\t\\tif ( spotLightDistance[ i ] > 0.0 )\\n\\t\\t\\t\\tspotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );\\n\\t\\t\\tspotVector = normalize( spotVector );\\n\\t\\t\\tfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\\n\\t\\t\\tif ( spotEffect > spotLightAngleCos[ i ] ) {\\n\\t\\t\\t\\tspotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );\\n\\t\\t\\t\\t#ifdef WRAP_AROUND\\n\\t\\t\\t\\t\\tfloat spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );\\n\\t\\t\\t\\t\\tfloat spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );\\n\\t\\t\\t\\t\\tvec3 spotDiffuseWeight = mix( vec3( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\\n\\t\\t\\t\\t#else\\n\\t\\t\\t\\t\\tfloat spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );\\n\\t\\t\\t\\t#endif\\n\\t\\t\\t\\tspotDiffuse += spotDistance * spotLightColor[ i ] * diffuse * spotDiffuseWeight * spotEffect;\\n\\t\\t\\t\\tvec3 spotHalfVector = normalize( spotVector + viewPosition );\\n\\t\\t\\t\\tfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\\n\\t\\t\\t\\tfloat spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );\\n\\t\\t\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\t\\t\\t\\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( spotVector, spotHalfVector ), 0.0 ), 5.0 );\\n\\t\\t\\t\\tspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t#endif\\n\\t#if MAX_DIR_LIGHTS > 0\\n\\t\\tvec3 dirDiffuse = vec3( 0.0 );\\n\\t\\tvec3 dirSpecular = vec3( 0.0 );\\n\\t\\tfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\\n\\t\\t\\tvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\\n\\t\\t\\tvec3 dirVector = normalize( lDirection.xyz );\\n\\t\\t\\t#ifdef WRAP_AROUND\\n\\t\\t\\t\\tfloat directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );\\n\\t\\t\\t\\tfloat directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );\\n\\t\\t\\t\\tvec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tfloat dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );\\n\\t\\t\\t#endif\\n\\t\\t\\tdirDiffuse += directionalLightColor[ i ] * diffuse * dirDiffuseWeight;\\n\\t\\t\\tvec3 dirHalfVector = normalize( dirVector + viewPosition );\\n\\t\\t\\tfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\\n\\t\\t\\tfloat dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );\\n\\t\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\t\\t\\tvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );\\n\\t\\t\\tdirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\\n\\t\\t}\\n\\t#endif\\n\\t#if MAX_HEMI_LIGHTS > 0\\n\\t\\tvec3 hemiDiffuse = vec3( 0.0 );\\n\\t\\tvec3 hemiSpecular = vec3( 0.0 );\\n\\t\\tfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\\n\\t\\t\\tvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\\n\\t\\t\\tvec3 lVector = normalize( lDirection.xyz );\\n\\t\\t\\tfloat dotProduct = dot( normal, lVector );\\n\\t\\t\\tfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\\n\\t\\t\\tvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\\n\\t\\t\\themiDiffuse += diffuse * hemiColor;\\n\\t\\t\\tvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\\n\\t\\t\\tfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\\n\\t\\t\\tfloat hemiSpecularWeightSky = specularTex.r * max( pow( max( hemiDotNormalHalfSky, 0.0 ), shininess ), 0.0 );\\n\\t\\t\\tvec3 lVectorGround = -lVector;\\n\\t\\t\\tvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\\n\\t\\t\\tfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\\n\\t\\t\\tfloat hemiSpecularWeightGround = specularTex.r * max( pow( max( hemiDotNormalHalfGround, 0.0 ), shininess ), 0.0 );\\n\\t\\t\\tfloat dotProductGround = dot( normal, lVectorGround );\\n\\t\\t\\tfloat specularNormalization = ( shininess + 2.0 ) / 8.0;\\n\\t\\t\\tvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );\\n\\t\\t\\tvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );\\n\\t\\t\\themiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\\n\\t\\t}\\n\\t#endif\\n\\tvec3 totalDiffuse = vec3( 0.0 );\\n\\tvec3 totalSpecular = vec3( 0.0 );\\n\\t#if MAX_DIR_LIGHTS > 0\\n\\t\\ttotalDiffuse += dirDiffuse;\\n\\t\\ttotalSpecular += dirSpecular;\\n\\t#endif\\n\\t#if MAX_HEMI_LIGHTS > 0\\n\\t\\ttotalDiffuse += hemiDiffuse;\\n\\t\\ttotalSpecular += hemiSpecular;\\n\\t#endif\\n\\t#if MAX_POINT_LIGHTS > 0\\n\\t\\ttotalDiffuse += pointDiffuse;\\n\\t\\ttotalSpecular += pointSpecular;\\n\\t#endif\\n\\t#if MAX_SPOT_LIGHTS > 0\\n\\t\\ttotalDiffuse += spotDiffuse;\\n\\t\\ttotalSpecular += spotSpecular;\\n\\t#endif\\n\\t#ifdef METAL\\n\\t\\tgl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient + totalSpecular );\\n\\t#else\\n\\t\\tgl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\\n\\t#endif\\n\\tif ( enableReflection ) {\\n\\t\\tvec3 vReflect;\\n\\t\\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\\n\\t\\tif ( useRefract ) {\\n\\t\\t\\tvReflect = refract( cameraToVertex, normal, refractionRatio );\\n\\t\\t} else {\\n\\t\\t\\tvReflect = reflect( cameraToVertex, normal );\\n\\t\\t}\\n\\t\\tvec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );\\n\\t\\t#ifdef GAMMA_INPUT\\n\\t\\t\\tcubeColor.xyz *= cubeColor.xyz;\\n\\t\\t#endif\\n\\t\\tgl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * reflectivity );\\n\\t}\",\nTHREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,\"}\"].join(\"\\n\"),vertexShader:[\"attribute vec4 tangent;\\nuniform vec2 uOffset;\\nuniform vec2 uRepeat;\\nuniform bool enableDisplacement;\\n#ifdef VERTEX_TEXTURES\\n\\tuniform sampler2D tDisplacement;\\n\\tuniform float uDisplacementScale;\\n\\tuniform float uDisplacementBias;\\n#endif\\nvarying vec3 vTangent;\\nvarying vec3 vBinormal;\\nvarying vec3 vNormal;\\nvarying vec2 vUv;\\nvarying vec3 vWorldPosition;\\nvarying vec3 vViewPosition;\",\nTHREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,\"\\t#ifdef USE_SKINNING\\n\\t\\tvNormal = normalize( normalMatrix * skinnedNormal.xyz );\\n\\t\\tvec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );\\n\\t\\tvTangent = normalize( normalMatrix * skinnedTangent.xyz );\\n\\t#else\\n\\t\\tvNormal = normalize( normalMatrix * normal );\\n\\t\\tvTangent = normalize( normalMatrix * tangent.xyz );\\n\\t#endif\\n\\tvBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );\\n\\tvUv = uv * uRepeat + uOffset;\\n\\tvec3 displacedPosition;\\n\\t#ifdef VERTEX_TEXTURES\\n\\t\\tif ( enableDisplacement ) {\\n\\t\\t\\tvec3 dv = texture2D( tDisplacement, uv ).xyz;\\n\\t\\t\\tfloat df = uDisplacementScale * dv.x + uDisplacementBias;\\n\\t\\t\\tdisplacedPosition = position + normalize( normal ) * df;\\n\\t\\t} else {\\n\\t\\t\\t#ifdef USE_SKINNING\\n\\t\\t\\t\\tvec4 skinVertex = bindMatrix * vec4( position, 1.0 );\\n\\t\\t\\t\\tvec4 skinned = vec4( 0.0 );\\n\\t\\t\\t\\tskinned += boneMatX * skinVertex * skinWeight.x;\\n\\t\\t\\t\\tskinned += boneMatY * skinVertex * skinWeight.y;\\n\\t\\t\\t\\tskinned += boneMatZ * skinVertex * skinWeight.z;\\n\\t\\t\\t\\tskinned += boneMatW * skinVertex * skinWeight.w;\\n\\t\\t\\t\\tskinned  = bindMatrixInverse * skinned;\\n\\t\\t\\t\\tdisplacedPosition = skinned.xyz;\\n\\t\\t\\t#else\\n\\t\\t\\t\\tdisplacedPosition = position;\\n\\t\\t\\t#endif\\n\\t\\t}\\n\\t#else\\n\\t\\t#ifdef USE_SKINNING\\n\\t\\t\\tvec4 skinVertex = bindMatrix * vec4( position, 1.0 );\\n\\t\\t\\tvec4 skinned = vec4( 0.0 );\\n\\t\\t\\tskinned += boneMatX * skinVertex * skinWeight.x;\\n\\t\\t\\tskinned += boneMatY * skinVertex * skinWeight.y;\\n\\t\\t\\tskinned += boneMatZ * skinVertex * skinWeight.z;\\n\\t\\t\\tskinned += boneMatW * skinVertex * skinWeight.w;\\n\\t\\t\\tskinned  = bindMatrixInverse * skinned;\\n\\t\\t\\tdisplacedPosition = skinned.xyz;\\n\\t\\t#else\\n\\t\\t\\tdisplacedPosition = position;\\n\\t\\t#endif\\n\\t#endif\\n\\tvec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );\\n\\tvec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );\\n\\tgl_Position = projectionMatrix * mvPosition;\",\nTHREE.ShaderChunk.logdepthbuf_vertex,\"\\tvWorldPosition = worldPosition.xyz;\\n\\tvViewPosition = -mvPosition.xyz;\\n\\t#ifdef USE_SHADOWMAP\\n\\t\\tfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\\n\\t\\t\\tvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\\n\\t\\t}\\n\\t#endif\\n}\"].join(\"\\n\")},cube:{uniforms:{tCube:{type:\"t\",value:null},tFlip:{type:\"f\",value:-1}},vertexShader:[\"varying vec3 vWorldPosition;\",THREE.ShaderChunk.logdepthbuf_pars_vertex,\"void main() {\\n\\tvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\\n\\tvWorldPosition = worldPosition.xyz;\\n\\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\",\nTHREE.ShaderChunk.logdepthbuf_vertex,\"}\"].join(\"\\n\"),fragmentShader:[\"uniform samplerCube tCube;\\nuniform float tFlip;\\nvarying vec3 vWorldPosition;\",THREE.ShaderChunk.logdepthbuf_pars_fragment,\"void main() {\\n\\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\",THREE.ShaderChunk.logdepthbuf_fragment,\"}\"].join(\"\\n\")},depthRGBA:{uniforms:{},vertexShader:[THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.logdepthbuf_pars_vertex,\n\"void main() {\",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,THREE.ShaderChunk.logdepthbuf_vertex,\"}\"].join(\"\\n\"),fragmentShader:[THREE.ShaderChunk.logdepthbuf_pars_fragment,\"vec4 pack_depth( const in float depth ) {\\n\\tconst vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );\\n\\tconst vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );\\n\\tvec4 res = mod( depth * bit_shift * vec4( 255 ), vec4( 256 ) ) / vec4( 255 );\\n\\tres -= res.xxyz * bit_mask;\\n\\treturn res;\\n}\\nvoid main() {\",\nTHREE.ShaderChunk.logdepthbuf_fragment,\"\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tgl_FragData[ 0 ] = pack_depth( gl_FragDepthEXT );\\n\\t#else\\n\\t\\tgl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );\\n\\t#endif\\n}\"].join(\"\\n\")}};\nTHREE.WebGLRenderer=function(a){function b(a){var b=a.geometry;a=a.material;var c=b.vertices.length;if(a.attributes){void 0===b.__webglCustomAttributesList&&(b.__webglCustomAttributesList=[]);for(var d in a.attributes){var e=a.attributes[d];if(!e.__webglInitialized||e.createUniqueBuffers){e.__webglInitialized=!0;var f=1;\"v2\"===e.type?f=2:\"v3\"===e.type?f=3:\"v4\"===e.type?f=4:\"c\"===e.type&&(f=3);e.size=f;e.array=new Float32Array(c*f);e.buffer=l.createBuffer();e.buffer.belongsToAttribute=d;e.needsUpdate=\n!0}b.__webglCustomAttributesList.push(e)}}}function c(a,b){var c=b.geometry,e=a.faces3,f=3*e.length,g=1*e.length,h=3*e.length,e=d(b,a);a.__vertexArray=new Float32Array(3*f);a.__normalArray=new Float32Array(3*f);a.__colorArray=new Float32Array(3*f);a.__uvArray=new Float32Array(2*f);1<c.faceVertexUvs.length&&(a.__uv2Array=new Float32Array(2*f));c.hasTangents&&(a.__tangentArray=new Float32Array(4*f));b.geometry.skinWeights.length&&b.geometry.skinIndices.length&&(a.__skinIndexArray=new Float32Array(4*\nf),a.__skinWeightArray=new Float32Array(4*f));c=null!==pa.get(\"OES_element_index_uint\")&&21845<g?Uint32Array:Uint16Array;a.__typeArray=c;a.__faceArray=new c(3*g);a.__lineArray=new c(2*h);var k;if(a.numMorphTargets)for(a.__morphTargetsArrays=[],c=0,k=a.numMorphTargets;c<k;c++)a.__morphTargetsArrays.push(new Float32Array(3*f));if(a.numMorphNormals)for(a.__morphNormalsArrays=[],c=0,k=a.numMorphNormals;c<k;c++)a.__morphNormalsArrays.push(new Float32Array(3*f));a.__webglFaceCount=3*g;a.__webglLineCount=\n2*h;if(e.attributes){void 0===a.__webglCustomAttributesList&&(a.__webglCustomAttributesList=[]);for(var m in e.attributes){var g=e.attributes[m],h={},n;for(n in g)h[n]=g[n];if(!h.__webglInitialized||h.createUniqueBuffers)h.__webglInitialized=!0,c=1,\"v2\"===h.type?c=2:\"v3\"===h.type?c=3:\"v4\"===h.type?c=4:\"c\"===h.type&&(c=3),h.size=c,h.array=new Float32Array(f*c),h.buffer=l.createBuffer(),h.buffer.belongsToAttribute=m,g.needsUpdate=!0,h.__original=g;a.__webglCustomAttributesList.push(h)}}a.__inittedArrays=\n!0}function d(a,b){return a.material instanceof THREE.MeshFaceMaterial?a.material.materials[b.materialIndex]:a.material}function e(a,b,c,d){c=c.attributes;var e=b.attributes;b=b.attributesKeys;for(var f=0,k=b.length;f<k;f++){var m=b[f],n=e[m];if(0<=n){var p=c[m];void 0!==p?(m=p.itemSize,l.bindBuffer(l.ARRAY_BUFFER,p.buffer),g(n),l.vertexAttribPointer(n,m,l.FLOAT,!1,0,d*m*4)):void 0!==a.defaultAttributeValues&&(2===a.defaultAttributeValues[m].length?l.vertexAttrib2fv(n,a.defaultAttributeValues[m]):\n3===a.defaultAttributeValues[m].length&&l.vertexAttrib3fv(n,a.defaultAttributeValues[m]))}}h()}function f(){for(var a=0,b=wb.length;a<b;a++)wb[a]=0}function g(a){wb[a]=1;0===ib[a]&&(l.enableVertexAttribArray(a),ib[a]=1)}function h(){for(var a=0,b=ib.length;a<b;a++)ib[a]!==wb[a]&&(l.disableVertexAttribArray(a),ib[a]=0)}function k(a,b){return a.material.id!==b.material.id?b.material.id-a.material.id:a.z!==b.z?b.z-a.z:a.id-b.id}function n(a,b){return a.z!==b.z?a.z-b.z:a.id-b.id}function p(a,b){return b[0]-\na[0]}function q(a,e){if(!1!==e.visible){if(!(e instanceof THREE.Scene||e instanceof THREE.Group)){void 0===e.__webglInit&&(e.__webglInit=!0,e._modelViewMatrix=new THREE.Matrix4,e._normalMatrix=new THREE.Matrix3,e.addEventListener(\"removed\",Hc));var f=e.geometry;if(void 0!==f&&void 0===f.__webglInit&&(f.__webglInit=!0,f.addEventListener(\"dispose\",Ic),!(f instanceof THREE.BufferGeometry)))if(e instanceof THREE.Mesh)s(a,e,f);else if(e instanceof THREE.Line){if(void 0===f.__webglVertexBuffer){f.__webglVertexBuffer=\nl.createBuffer();f.__webglColorBuffer=l.createBuffer();f.__webglLineDistanceBuffer=l.createBuffer();J.info.memory.geometries++;var g=f.vertices.length;f.__vertexArray=new Float32Array(3*g);f.__colorArray=new Float32Array(3*g);f.__lineDistanceArray=new Float32Array(1*g);f.__webglLineCount=g;b(e);f.verticesNeedUpdate=!0;f.colorsNeedUpdate=!0;f.lineDistancesNeedUpdate=!0}}else if(e instanceof THREE.PointCloud&&void 0===f.__webglVertexBuffer){f.__webglVertexBuffer=l.createBuffer();f.__webglColorBuffer=\nl.createBuffer();J.info.memory.geometries++;var h=f.vertices.length;f.__vertexArray=new Float32Array(3*h);f.__colorArray=new Float32Array(3*h);f.__sortArray=[];f.__webglParticleCount=h;b(e);f.verticesNeedUpdate=!0;f.colorsNeedUpdate=!0}if(void 0===e.__webglActive)if(e.__webglActive=!0,e instanceof THREE.Mesh)if(f instanceof THREE.BufferGeometry)u(ob,f,e);else{if(f instanceof THREE.Geometry)for(var k=xb[f.id],m=0,n=k.length;m<n;m++)u(ob,k[m],e)}else e instanceof THREE.Line||e instanceof THREE.PointCloud?\nu(ob,f,e):(e instanceof THREE.ImmediateRenderObject||e.immediateRenderCallback)&&jb.push({id:null,object:e,opaque:null,transparent:null,z:0});if(e instanceof THREE.Light)cb.push(e);else if(e instanceof THREE.Sprite)yb.push(e);else if(e instanceof THREE.LensFlare)Ra.push(e);else{var t=ob[e.id];if(t&&(!1===e.frustumCulled||!0===Ec.intersectsObject(e))){var r=e.geometry,w,G;if(r instanceof THREE.BufferGeometry)for(var x=r.attributes,D=r.attributesKeys,E=0,B=D.length;E<B;E++){var A=D[E],K=x[A];void 0===\nK.buffer&&(K.buffer=l.createBuffer(),K.needsUpdate=!0);if(!0===K.needsUpdate){var F=\"index\"===A?l.ELEMENT_ARRAY_BUFFER:l.ARRAY_BUFFER;l.bindBuffer(F,K.buffer);l.bufferData(F,K.array,l.STATIC_DRAW);K.needsUpdate=!1}}else if(e instanceof THREE.Mesh){!0===r.groupsNeedUpdate&&s(a,e,r);for(var H=xb[r.id],O=0,Q=H.length;O<Q;O++){var R=H[O];G=d(e,R);!0===r.groupsNeedUpdate&&c(R,e);w=G.attributes&&v(G);if(r.verticesNeedUpdate||r.morphTargetsNeedUpdate||r.elementsNeedUpdate||r.uvsNeedUpdate||r.normalsNeedUpdate||\nr.colorsNeedUpdate||r.tangentsNeedUpdate||w){var C=R,P=e,S=l.DYNAMIC_DRAW,T=!r.dynamic,X=G;if(C.__inittedArrays){var bb=X&&void 0!==X.shading&&X.shading===THREE.SmoothShading,M=void 0,ea=void 0,Y=void 0,ca=void 0,ma=void 0,pa=void 0,sa=void 0,Fa=void 0,la=void 0,hb=void 0,za=void 0,aa=void 0,$=void 0,Z=void 0,ya=void 0,qa=void 0,L=void 0,Ga=void 0,na=void 0,nc=void 0,ia=void 0,oc=void 0,pc=void 0,qc=void 0,Ba=void 0,zb=void 0,Ab=void 0,Ha=void 0,Bb=void 0,Aa=void 0,va=void 0,Cb=void 0,Oa=void 0,Qb=\nvoid 0,Ma=void 0,ib=void 0,Ya=void 0,Za=void 0,uc=void 0,Rb=void 0,db=0,eb=0,qb=0,rb=0,Db=0,Sa=0,Ca=0,Pa=0,Ka=0,ja=0,ta=0,I=0,Ia=void 0,Qa=C.__vertexArray,sb=C.__uvArray,fb=C.__uv2Array,Ta=C.__normalArray,ra=C.__tangentArray,La=C.__colorArray,Ua=C.__skinIndexArray,Va=C.__skinWeightArray,Eb=C.__morphTargetsArrays,Jc=C.__morphNormalsArrays,Kb=C.__webglCustomAttributesList,z=void 0,Sb=C.__faceArray,Ja=C.__lineArray,wa=P.geometry,$a=wa.elementsNeedUpdate,Kc=wa.uvsNeedUpdate,ec=wa.normalsNeedUpdate,da=\nwa.tangentsNeedUpdate,wb=wa.colorsNeedUpdate,U=wa.morphTargetsNeedUpdate,fa=wa.vertices,N=C.faces3,xa=wa.faces,ua=wa.faceVertexUvs[0],Lc=wa.faceVertexUvs[1],Fc=wa.skinIndices,Tb=wa.skinWeights,kb=wa.morphTargets,Da=wa.morphNormals;if(wa.verticesNeedUpdate){M=0;for(ea=N.length;M<ea;M++)ca=xa[N[M]],aa=fa[ca.a],$=fa[ca.b],Z=fa[ca.c],Qa[eb]=aa.x,Qa[eb+1]=aa.y,Qa[eb+2]=aa.z,Qa[eb+3]=$.x,Qa[eb+4]=$.y,Qa[eb+5]=$.z,Qa[eb+6]=Z.x,Qa[eb+7]=Z.y,Qa[eb+8]=Z.z,eb+=9;l.bindBuffer(l.ARRAY_BUFFER,C.__webglVertexBuffer);\nl.bufferData(l.ARRAY_BUFFER,Qa,S)}if(U)for(Ma=0,ib=kb.length;Ma<ib;Ma++){M=ta=0;for(ea=N.length;M<ea;M++)uc=N[M],ca=xa[uc],aa=kb[Ma].vertices[ca.a],$=kb[Ma].vertices[ca.b],Z=kb[Ma].vertices[ca.c],Ya=Eb[Ma],Ya[ta]=aa.x,Ya[ta+1]=aa.y,Ya[ta+2]=aa.z,Ya[ta+3]=$.x,Ya[ta+4]=$.y,Ya[ta+5]=$.z,Ya[ta+6]=Z.x,Ya[ta+7]=Z.y,Ya[ta+8]=Z.z,X.morphNormals&&(bb?(Rb=Da[Ma].vertexNormals[uc],Ga=Rb.a,na=Rb.b,nc=Rb.c):nc=na=Ga=Da[Ma].faceNormals[uc],Za=Jc[Ma],Za[ta]=Ga.x,Za[ta+1]=Ga.y,Za[ta+2]=Ga.z,Za[ta+3]=na.x,Za[ta+4]=\nna.y,Za[ta+5]=na.z,Za[ta+6]=nc.x,Za[ta+7]=nc.y,Za[ta+8]=nc.z),ta+=9;l.bindBuffer(l.ARRAY_BUFFER,C.__webglMorphTargetsBuffers[Ma]);l.bufferData(l.ARRAY_BUFFER,Eb[Ma],S);X.morphNormals&&(l.bindBuffer(l.ARRAY_BUFFER,C.__webglMorphNormalsBuffers[Ma]),l.bufferData(l.ARRAY_BUFFER,Jc[Ma],S))}if(Tb.length){M=0;for(ea=N.length;M<ea;M++)ca=xa[N[M]],qc=Tb[ca.a],Ba=Tb[ca.b],zb=Tb[ca.c],Va[ja]=qc.x,Va[ja+1]=qc.y,Va[ja+2]=qc.z,Va[ja+3]=qc.w,Va[ja+4]=Ba.x,Va[ja+5]=Ba.y,Va[ja+6]=Ba.z,Va[ja+7]=Ba.w,Va[ja+8]=zb.x,\nVa[ja+9]=zb.y,Va[ja+10]=zb.z,Va[ja+11]=zb.w,Ab=Fc[ca.a],Ha=Fc[ca.b],Bb=Fc[ca.c],Ua[ja]=Ab.x,Ua[ja+1]=Ab.y,Ua[ja+2]=Ab.z,Ua[ja+3]=Ab.w,Ua[ja+4]=Ha.x,Ua[ja+5]=Ha.y,Ua[ja+6]=Ha.z,Ua[ja+7]=Ha.w,Ua[ja+8]=Bb.x,Ua[ja+9]=Bb.y,Ua[ja+10]=Bb.z,Ua[ja+11]=Bb.w,ja+=12;0<ja&&(l.bindBuffer(l.ARRAY_BUFFER,C.__webglSkinIndicesBuffer),l.bufferData(l.ARRAY_BUFFER,Ua,S),l.bindBuffer(l.ARRAY_BUFFER,C.__webglSkinWeightsBuffer),l.bufferData(l.ARRAY_BUFFER,Va,S))}if(wb){M=0;for(ea=N.length;M<ea;M++)ca=xa[N[M]],sa=ca.vertexColors,\nFa=ca.color,3===sa.length&&X.vertexColors===THREE.VertexColors?(ia=sa[0],oc=sa[1],pc=sa[2]):pc=oc=ia=Fa,La[Ka]=ia.r,La[Ka+1]=ia.g,La[Ka+2]=ia.b,La[Ka+3]=oc.r,La[Ka+4]=oc.g,La[Ka+5]=oc.b,La[Ka+6]=pc.r,La[Ka+7]=pc.g,La[Ka+8]=pc.b,Ka+=9;0<Ka&&(l.bindBuffer(l.ARRAY_BUFFER,C.__webglColorBuffer),l.bufferData(l.ARRAY_BUFFER,La,S))}if(da&&wa.hasTangents){M=0;for(ea=N.length;M<ea;M++)ca=xa[N[M]],la=ca.vertexTangents,ya=la[0],qa=la[1],L=la[2],ra[Ca]=ya.x,ra[Ca+1]=ya.y,ra[Ca+2]=ya.z,ra[Ca+3]=ya.w,ra[Ca+4]=qa.x,\nra[Ca+5]=qa.y,ra[Ca+6]=qa.z,ra[Ca+7]=qa.w,ra[Ca+8]=L.x,ra[Ca+9]=L.y,ra[Ca+10]=L.z,ra[Ca+11]=L.w,Ca+=12;l.bindBuffer(l.ARRAY_BUFFER,C.__webglTangentBuffer);l.bufferData(l.ARRAY_BUFFER,ra,S)}if(ec){M=0;for(ea=N.length;M<ea;M++)if(ca=xa[N[M]],ma=ca.vertexNormals,pa=ca.normal,3===ma.length&&bb)for(Aa=0;3>Aa;Aa++)Cb=ma[Aa],Ta[Sa]=Cb.x,Ta[Sa+1]=Cb.y,Ta[Sa+2]=Cb.z,Sa+=3;else for(Aa=0;3>Aa;Aa++)Ta[Sa]=pa.x,Ta[Sa+1]=pa.y,Ta[Sa+2]=pa.z,Sa+=3;l.bindBuffer(l.ARRAY_BUFFER,C.__webglNormalBuffer);l.bufferData(l.ARRAY_BUFFER,\nTa,S)}if(Kc&&ua){M=0;for(ea=N.length;M<ea;M++)if(Y=N[M],hb=ua[Y],void 0!==hb)for(Aa=0;3>Aa;Aa++)Oa=hb[Aa],sb[qb]=Oa.x,sb[qb+1]=Oa.y,qb+=2;0<qb&&(l.bindBuffer(l.ARRAY_BUFFER,C.__webglUVBuffer),l.bufferData(l.ARRAY_BUFFER,sb,S))}if(Kc&&Lc){M=0;for(ea=N.length;M<ea;M++)if(Y=N[M],za=Lc[Y],void 0!==za)for(Aa=0;3>Aa;Aa++)Qb=za[Aa],fb[rb]=Qb.x,fb[rb+1]=Qb.y,rb+=2;0<rb&&(l.bindBuffer(l.ARRAY_BUFFER,C.__webglUV2Buffer),l.bufferData(l.ARRAY_BUFFER,fb,S))}if($a){M=0;for(ea=N.length;M<ea;M++)Sb[Db]=db,Sb[Db+\n1]=db+1,Sb[Db+2]=db+2,Db+=3,Ja[Pa]=db,Ja[Pa+1]=db+1,Ja[Pa+2]=db,Ja[Pa+3]=db+2,Ja[Pa+4]=db+1,Ja[Pa+5]=db+2,Pa+=6,db+=3;l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,C.__webglFaceBuffer);l.bufferData(l.ELEMENT_ARRAY_BUFFER,Sb,S);l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,C.__webglLineBuffer);l.bufferData(l.ELEMENT_ARRAY_BUFFER,Ja,S)}if(Kb)for(Aa=0,va=Kb.length;Aa<va;Aa++)if(z=Kb[Aa],z.__original.needsUpdate){I=0;if(1===z.size)if(void 0===z.boundTo||\"vertices\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)ca=xa[N[M]],z.array[I]=\nz.value[ca.a],z.array[I+1]=z.value[ca.b],z.array[I+2]=z.value[ca.c],I+=3;else{if(\"faces\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)Ia=z.value[N[M]],z.array[I]=Ia,z.array[I+1]=Ia,z.array[I+2]=Ia,I+=3}else if(2===z.size)if(void 0===z.boundTo||\"vertices\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)ca=xa[N[M]],aa=z.value[ca.a],$=z.value[ca.b],Z=z.value[ca.c],z.array[I]=aa.x,z.array[I+1]=aa.y,z.array[I+2]=$.x,z.array[I+3]=$.y,z.array[I+4]=Z.x,z.array[I+5]=Z.y,I+=6;else{if(\"faces\"===z.boundTo)for(M=0,ea=N.length;M<\nea;M++)Z=$=aa=Ia=z.value[N[M]],z.array[I]=aa.x,z.array[I+1]=aa.y,z.array[I+2]=$.x,z.array[I+3]=$.y,z.array[I+4]=Z.x,z.array[I+5]=Z.y,I+=6}else if(3===z.size){var ka;ka=\"c\"===z.type?[\"r\",\"g\",\"b\"]:[\"x\",\"y\",\"z\"];if(void 0===z.boundTo||\"vertices\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)ca=xa[N[M]],aa=z.value[ca.a],$=z.value[ca.b],Z=z.value[ca.c],z.array[I]=aa[ka[0]],z.array[I+1]=aa[ka[1]],z.array[I+2]=aa[ka[2]],z.array[I+3]=$[ka[0]],z.array[I+4]=$[ka[1]],z.array[I+5]=$[ka[2]],z.array[I+6]=Z[ka[0]],z.array[I+\n7]=Z[ka[1]],z.array[I+8]=Z[ka[2]],I+=9;else if(\"faces\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)Z=$=aa=Ia=z.value[N[M]],z.array[I]=aa[ka[0]],z.array[I+1]=aa[ka[1]],z.array[I+2]=aa[ka[2]],z.array[I+3]=$[ka[0]],z.array[I+4]=$[ka[1]],z.array[I+5]=$[ka[2]],z.array[I+6]=Z[ka[0]],z.array[I+7]=Z[ka[1]],z.array[I+8]=Z[ka[2]],I+=9;else if(\"faceVertices\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)Ia=z.value[N[M]],aa=Ia[0],$=Ia[1],Z=Ia[2],z.array[I]=aa[ka[0]],z.array[I+1]=aa[ka[1]],z.array[I+2]=aa[ka[2]],z.array[I+\n3]=$[ka[0]],z.array[I+4]=$[ka[1]],z.array[I+5]=$[ka[2]],z.array[I+6]=Z[ka[0]],z.array[I+7]=Z[ka[1]],z.array[I+8]=Z[ka[2]],I+=9}else if(4===z.size)if(void 0===z.boundTo||\"vertices\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)ca=xa[N[M]],aa=z.value[ca.a],$=z.value[ca.b],Z=z.value[ca.c],z.array[I]=aa.x,z.array[I+1]=aa.y,z.array[I+2]=aa.z,z.array[I+3]=aa.w,z.array[I+4]=$.x,z.array[I+5]=$.y,z.array[I+6]=$.z,z.array[I+7]=$.w,z.array[I+8]=Z.x,z.array[I+9]=Z.y,z.array[I+10]=Z.z,z.array[I+11]=Z.w,I+=12;else if(\"faces\"===\nz.boundTo)for(M=0,ea=N.length;M<ea;M++)Z=$=aa=Ia=z.value[N[M]],z.array[I]=aa.x,z.array[I+1]=aa.y,z.array[I+2]=aa.z,z.array[I+3]=aa.w,z.array[I+4]=$.x,z.array[I+5]=$.y,z.array[I+6]=$.z,z.array[I+7]=$.w,z.array[I+8]=Z.x,z.array[I+9]=Z.y,z.array[I+10]=Z.z,z.array[I+11]=Z.w,I+=12;else if(\"faceVertices\"===z.boundTo)for(M=0,ea=N.length;M<ea;M++)Ia=z.value[N[M]],aa=Ia[0],$=Ia[1],Z=Ia[2],z.array[I]=aa.x,z.array[I+1]=aa.y,z.array[I+2]=aa.z,z.array[I+3]=aa.w,z.array[I+4]=$.x,z.array[I+5]=$.y,z.array[I+6]=$.z,\nz.array[I+7]=$.w,z.array[I+8]=Z.x,z.array[I+9]=Z.y,z.array[I+10]=Z.z,z.array[I+11]=Z.w,I+=12;l.bindBuffer(l.ARRAY_BUFFER,z.buffer);l.bufferData(l.ARRAY_BUFFER,z.array,S)}T&&(delete C.__inittedArrays,delete C.__colorArray,delete C.__normalArray,delete C.__tangentArray,delete C.__uvArray,delete C.__uv2Array,delete C.__faceArray,delete C.__vertexArray,delete C.__lineArray,delete C.__skinIndexArray,delete C.__skinWeightArray)}}}r.verticesNeedUpdate=!1;r.morphTargetsNeedUpdate=!1;r.elementsNeedUpdate=\n!1;r.uvsNeedUpdate=!1;r.normalsNeedUpdate=!1;r.colorsNeedUpdate=!1;r.tangentsNeedUpdate=!1;G.attributes&&y(G)}else if(e instanceof THREE.Line){G=d(e,r);w=G.attributes&&v(G);if(r.verticesNeedUpdate||r.colorsNeedUpdate||r.lineDistancesNeedUpdate||w){var Zb=l.DYNAMIC_DRAW,ab,Fb,gb,$b,ga,vc,dc=r.vertices,fc=r.colors,Pb=r.lineDistances,kc=dc.length,lc=fc.length,mc=Pb.length,wc=r.__vertexArray,xc=r.__colorArray,jc=r.__lineDistanceArray,sc=r.colorsNeedUpdate,tc=r.lineDistancesNeedUpdate,gc=r.__webglCustomAttributesList,\nyc,Lb,Ea,hc,Wa,oa;if(r.verticesNeedUpdate){for(ab=0;ab<kc;ab++)$b=dc[ab],ga=3*ab,wc[ga]=$b.x,wc[ga+1]=$b.y,wc[ga+2]=$b.z;l.bindBuffer(l.ARRAY_BUFFER,r.__webglVertexBuffer);l.bufferData(l.ARRAY_BUFFER,wc,Zb)}if(sc){for(Fb=0;Fb<lc;Fb++)vc=fc[Fb],ga=3*Fb,xc[ga]=vc.r,xc[ga+1]=vc.g,xc[ga+2]=vc.b;l.bindBuffer(l.ARRAY_BUFFER,r.__webglColorBuffer);l.bufferData(l.ARRAY_BUFFER,xc,Zb)}if(tc){for(gb=0;gb<mc;gb++)jc[gb]=Pb[gb];l.bindBuffer(l.ARRAY_BUFFER,r.__webglLineDistanceBuffer);l.bufferData(l.ARRAY_BUFFER,\njc,Zb)}if(gc)for(yc=0,Lb=gc.length;yc<Lb;yc++)if(oa=gc[yc],oa.needsUpdate&&(void 0===oa.boundTo||\"vertices\"===oa.boundTo)){ga=0;hc=oa.value.length;if(1===oa.size)for(Ea=0;Ea<hc;Ea++)oa.array[Ea]=oa.value[Ea];else if(2===oa.size)for(Ea=0;Ea<hc;Ea++)Wa=oa.value[Ea],oa.array[ga]=Wa.x,oa.array[ga+1]=Wa.y,ga+=2;else if(3===oa.size)if(\"c\"===oa.type)for(Ea=0;Ea<hc;Ea++)Wa=oa.value[Ea],oa.array[ga]=Wa.r,oa.array[ga+1]=Wa.g,oa.array[ga+2]=Wa.b,ga+=3;else for(Ea=0;Ea<hc;Ea++)Wa=oa.value[Ea],oa.array[ga]=Wa.x,\noa.array[ga+1]=Wa.y,oa.array[ga+2]=Wa.z,ga+=3;else if(4===oa.size)for(Ea=0;Ea<hc;Ea++)Wa=oa.value[Ea],oa.array[ga]=Wa.x,oa.array[ga+1]=Wa.y,oa.array[ga+2]=Wa.z,oa.array[ga+3]=Wa.w,ga+=4;l.bindBuffer(l.ARRAY_BUFFER,oa.buffer);l.bufferData(l.ARRAY_BUFFER,oa.array,Zb)}}r.verticesNeedUpdate=!1;r.colorsNeedUpdate=!1;r.lineDistancesNeedUpdate=!1;G.attributes&&y(G)}else if(e instanceof THREE.PointCloud){G=d(e,r);w=G.attributes&&v(G);if(r.verticesNeedUpdate||r.colorsNeedUpdate||e.sortParticles||w){var Mb=\nl.DYNAMIC_DRAW,Xa,tb,ub,W,vb,Ub,zc=r.vertices,pb=zc.length,Nb=r.colors,Ob=Nb.length,ac=r.__vertexArray,bc=r.__colorArray,Gb=r.__sortArray,Xb=r.verticesNeedUpdate,Yb=r.colorsNeedUpdate,Hb=r.__webglCustomAttributesList,lb,ic,ba,mb,ha,V;if(e.sortParticles){Gc.copy(Ac);Gc.multiply(e.matrixWorld);for(Xa=0;Xa<pb;Xa++)ub=zc[Xa],Na.copy(ub),Na.applyProjection(Gc),Gb[Xa]=[Na.z,Xa];Gb.sort(p);for(Xa=0;Xa<pb;Xa++)ub=zc[Gb[Xa][1]],W=3*Xa,ac[W]=ub.x,ac[W+1]=ub.y,ac[W+2]=ub.z;for(tb=0;tb<Ob;tb++)W=3*tb,Ub=Nb[Gb[tb][1]],\nbc[W]=Ub.r,bc[W+1]=Ub.g,bc[W+2]=Ub.b;if(Hb)for(lb=0,ic=Hb.length;lb<ic;lb++)if(V=Hb[lb],void 0===V.boundTo||\"vertices\"===V.boundTo)if(W=0,mb=V.value.length,1===V.size)for(ba=0;ba<mb;ba++)vb=Gb[ba][1],V.array[ba]=V.value[vb];else if(2===V.size)for(ba=0;ba<mb;ba++)vb=Gb[ba][1],ha=V.value[vb],V.array[W]=ha.x,V.array[W+1]=ha.y,W+=2;else if(3===V.size)if(\"c\"===V.type)for(ba=0;ba<mb;ba++)vb=Gb[ba][1],ha=V.value[vb],V.array[W]=ha.r,V.array[W+1]=ha.g,V.array[W+2]=ha.b,W+=3;else for(ba=0;ba<mb;ba++)vb=Gb[ba][1],\nha=V.value[vb],V.array[W]=ha.x,V.array[W+1]=ha.y,V.array[W+2]=ha.z,W+=3;else if(4===V.size)for(ba=0;ba<mb;ba++)vb=Gb[ba][1],ha=V.value[vb],V.array[W]=ha.x,V.array[W+1]=ha.y,V.array[W+2]=ha.z,V.array[W+3]=ha.w,W+=4}else{if(Xb)for(Xa=0;Xa<pb;Xa++)ub=zc[Xa],W=3*Xa,ac[W]=ub.x,ac[W+1]=ub.y,ac[W+2]=ub.z;if(Yb)for(tb=0;tb<Ob;tb++)Ub=Nb[tb],W=3*tb,bc[W]=Ub.r,bc[W+1]=Ub.g,bc[W+2]=Ub.b;if(Hb)for(lb=0,ic=Hb.length;lb<ic;lb++)if(V=Hb[lb],V.needsUpdate&&(void 0===V.boundTo||\"vertices\"===V.boundTo))if(mb=V.value.length,\nW=0,1===V.size)for(ba=0;ba<mb;ba++)V.array[ba]=V.value[ba];else if(2===V.size)for(ba=0;ba<mb;ba++)ha=V.value[ba],V.array[W]=ha.x,V.array[W+1]=ha.y,W+=2;else if(3===V.size)if(\"c\"===V.type)for(ba=0;ba<mb;ba++)ha=V.value[ba],V.array[W]=ha.r,V.array[W+1]=ha.g,V.array[W+2]=ha.b,W+=3;else for(ba=0;ba<mb;ba++)ha=V.value[ba],V.array[W]=ha.x,V.array[W+1]=ha.y,V.array[W+2]=ha.z,W+=3;else if(4===V.size)for(ba=0;ba<mb;ba++)ha=V.value[ba],V.array[W]=ha.x,V.array[W+1]=ha.y,V.array[W+2]=ha.z,V.array[W+3]=ha.w,W+=\n4}if(Xb||e.sortParticles)l.bindBuffer(l.ARRAY_BUFFER,r.__webglVertexBuffer),l.bufferData(l.ARRAY_BUFFER,ac,Mb);if(Yb||e.sortParticles)l.bindBuffer(l.ARRAY_BUFFER,r.__webglColorBuffer),l.bufferData(l.ARRAY_BUFFER,bc,Mb);if(Hb)for(lb=0,ic=Hb.length;lb<ic;lb++)if(V=Hb[lb],V.needsUpdate||e.sortParticles)l.bindBuffer(l.ARRAY_BUFFER,V.buffer),l.bufferData(l.ARRAY_BUFFER,V.array,Mb)}r.verticesNeedUpdate=!1;r.colorsNeedUpdate=!1;G.attributes&&y(G)}for(var cc=0,nb=t.length;cc<nb;cc++){var Bc=t[cc],Vb=Bc,rc=\nVb.object,Cc=Vb.buffer,Dc=rc.geometry,Wb=rc.material;Wb instanceof THREE.MeshFaceMaterial?(Wb=Wb.materials[Dc instanceof THREE.BufferGeometry?0:Cc.materialIndex],Vb.material=Wb,Wb.transparent?Ib.push(Vb):Jb.push(Vb)):Wb&&(Vb.material=Wb,Wb.transparent?Ib.push(Vb):Jb.push(Vb));Bc.render=!0;!0===J.sortObjects&&(null!==e.renderDepth?Bc.z=e.renderDepth:(Na.setFromMatrixPosition(e.matrixWorld),Na.applyProjection(Ac),Bc.z=Na.z))}}}}cc=0;for(nb=e.children.length;cc<nb;cc++)q(a,e.children[cc])}}function m(a,\nb,c,d,e,f){for(var g,h=a.length-1;-1!==h;h--){g=a[h];var k=g.object,l=g.buffer;x(k,b);if(f)g=f;else{g=g.material;if(!g)continue;e&&J.setBlending(g.blending,g.blendEquation,g.blendSrc,g.blendDst);J.setDepthTest(g.depthTest);J.setDepthWrite(g.depthWrite);B(g.polygonOffset,g.polygonOffsetFactor,g.polygonOffsetUnits)}J.setMaterialFaces(g);l instanceof THREE.BufferGeometry?J.renderBufferDirect(b,c,d,g,l,k):J.renderBuffer(b,c,d,g,l,k)}}function r(a,b,c,d,e,f,g){for(var h,k=0,l=a.length;k<l;k++){h=a[k];\nvar m=h.object;if(m.visible){if(g)h=g;else{h=h[b];if(!h)continue;f&&J.setBlending(h.blending,h.blendEquation,h.blendSrc,h.blendDst);J.setDepthTest(h.depthTest);J.setDepthWrite(h.depthWrite);B(h.polygonOffset,h.polygonOffsetFactor,h.polygonOffsetUnits)}J.renderImmediateObject(c,d,e,h,m)}}}function t(a){var b=a.object.material;b.transparent?(a.transparent=b,a.opaque=null):(a.opaque=b,a.transparent=null)}function s(a,b,d){var e=b.material,f=!1;if(void 0===xb[d.id]||!0===d.groupsNeedUpdate){delete ob[b.id];\na=xb;for(var g=d.id,e=e instanceof THREE.MeshFaceMaterial,h=pa.get(\"OES_element_index_uint\")?4294967296:65535,k,f={},m=d.morphTargets.length,n=d.morphNormals.length,p,r={},q=[],t=0,s=d.faces.length;t<s;t++){k=d.faces[t];var v=e?k.materialIndex:0;v in f||(f[v]={hash:v,counter:0});k=f[v].hash+\"_\"+f[v].counter;k in r||(p={id:rc++,faces3:[],materialIndex:v,vertices:0,numMorphTargets:m,numMorphNormals:n},r[k]=p,q.push(p));r[k].vertices+3>h&&(f[v].counter+=1,k=f[v].hash+\"_\"+f[v].counter,k in r||(p={id:rc++,\nfaces3:[],materialIndex:v,vertices:0,numMorphTargets:m,numMorphNormals:n},r[k]=p,q.push(p)));r[k].faces3.push(t);r[k].vertices+=3}a[g]=q;d.groupsNeedUpdate=!1}a=xb[d.id];g=0;for(e=a.length;g<e;g++){h=a[g];if(void 0===h.__webglVertexBuffer){f=h;f.__webglVertexBuffer=l.createBuffer();f.__webglNormalBuffer=l.createBuffer();f.__webglTangentBuffer=l.createBuffer();f.__webglColorBuffer=l.createBuffer();f.__webglUVBuffer=l.createBuffer();f.__webglUV2Buffer=l.createBuffer();f.__webglSkinIndicesBuffer=l.createBuffer();\nf.__webglSkinWeightsBuffer=l.createBuffer();f.__webglFaceBuffer=l.createBuffer();f.__webglLineBuffer=l.createBuffer();n=m=void 0;if(f.numMorphTargets)for(f.__webglMorphTargetsBuffers=[],m=0,n=f.numMorphTargets;m<n;m++)f.__webglMorphTargetsBuffers.push(l.createBuffer());if(f.numMorphNormals)for(f.__webglMorphNormalsBuffers=[],m=0,n=f.numMorphNormals;m<n;m++)f.__webglMorphNormalsBuffers.push(l.createBuffer());J.info.memory.geometries++;c(h,b);d.verticesNeedUpdate=!0;d.morphTargetsNeedUpdate=!0;d.elementsNeedUpdate=\n!0;d.uvsNeedUpdate=!0;d.normalsNeedUpdate=!0;d.tangentsNeedUpdate=!0;f=d.colorsNeedUpdate=!0}else f=!1;(f||void 0===b.__webglActive)&&u(ob,h,b)}b.__webglActive=!0}function u(a,b,c){var d=c.id;a[d]=a[d]||[];a[d].push({id:d,buffer:b,object:c,material:null,z:0})}function v(a){for(var b in a.attributes)if(a.attributes[b].needsUpdate)return!0;return!1}function y(a){for(var b in a.attributes)a.attributes[b].needsUpdate=!1}function G(a,b,c,d,e){var f,g,h,k;dc=0;if(d.needsUpdate){d.program&&Cc(d);d.addEventListener(\"dispose\",\nDc);var m;d instanceof THREE.MeshDepthMaterial?m=\"depth\":d instanceof THREE.MeshNormalMaterial?m=\"normal\":d instanceof THREE.MeshBasicMaterial?m=\"basic\":d instanceof THREE.MeshLambertMaterial?m=\"lambert\":d instanceof THREE.MeshPhongMaterial?m=\"phong\":d instanceof THREE.LineBasicMaterial?m=\"basic\":d instanceof THREE.LineDashedMaterial?m=\"dashed\":d instanceof THREE.PointCloudMaterial&&(m=\"particle_basic\");if(m){var n=THREE.ShaderLib[m];d.__webglShader={uniforms:THREE.UniformsUtils.clone(n.uniforms),\nvertexShader:n.vertexShader,fragmentShader:n.fragmentShader}}else d.__webglShader={uniforms:d.uniforms,vertexShader:d.vertexShader,fragmentShader:d.fragmentShader};for(var p=0,r=0,q=0,t=0,s=0,u=b.length;s<u;s++){var v=b[s];v.onlyShadow||!1===v.visible||(v instanceof THREE.DirectionalLight&&p++,v instanceof THREE.PointLight&&r++,v instanceof THREE.SpotLight&&q++,v instanceof THREE.HemisphereLight&&t++)}f=p;g=r;h=q;k=t;for(var y,G=0,x=0,B=b.length;x<B;x++){var A=b[x];A.castShadow&&(A instanceof THREE.SpotLight&&\nG++,A instanceof THREE.DirectionalLight&&!A.shadowCascade&&G++)}y=G;var C;if(jc&&e&&e.skeleton&&e.skeleton.useVertexTexture)C=1024;else{var H=l.getParameter(l.MAX_VERTEX_UNIFORM_VECTORS),S=Math.floor((H-20)/4);void 0!==e&&e instanceof THREE.SkinnedMesh&&(S=Math.min(e.skeleton.bones.length,S),S<e.skeleton.bones.length&&console.warn(\"WebGLRenderer: too many bones - \"+e.skeleton.bones.length+\", this GPU supports just \"+S+\" (try OpenGL instead of ANGLE)\"));C=S}var P={precision:X,supportsVertexTextures:sc,\nmap:!!d.map,envMap:!!d.envMap,lightMap:!!d.lightMap,bumpMap:!!d.bumpMap,normalMap:!!d.normalMap,specularMap:!!d.specularMap,alphaMap:!!d.alphaMap,vertexColors:d.vertexColors,fog:c,useFog:d.fog,fogExp:c instanceof THREE.FogExp2,sizeAttenuation:d.sizeAttenuation,logarithmicDepthBuffer:Fa,skinning:d.skinning,maxBones:C,useVertexTexture:jc&&e&&e.skeleton&&e.skeleton.useVertexTexture,morphTargets:d.morphTargets,morphNormals:d.morphNormals,maxMorphTargets:J.maxMorphTargets,maxMorphNormals:J.maxMorphNormals,\nmaxDirLights:f,maxPointLights:g,maxSpotLights:h,maxHemiLights:k,maxShadows:y,shadowMapEnabled:J.shadowMapEnabled&&e.receiveShadow&&0<y,shadowMapType:J.shadowMapType,shadowMapDebug:J.shadowMapDebug,shadowMapCascade:J.shadowMapCascade,alphaTest:d.alphaTest,metal:d.metal,wrapAround:d.wrapAround,doubleSided:d.side===THREE.DoubleSide,flipSided:d.side===THREE.BackSide},T=[];m?T.push(m):(T.push(d.fragmentShader),T.push(d.vertexShader));if(void 0!==d.defines)for(var bb in d.defines)T.push(bb),T.push(d.defines[bb]);\nfor(bb in P)T.push(bb),T.push(P[bb]);for(var M=T.join(),Y,jb=0,ca=hb.length;jb<ca;jb++){var cb=hb[jb];if(cb.code===M){Y=cb;Y.usedTimes++;break}}void 0===Y&&(Y=new THREE.WebGLProgram(J,M,d,P),hb.push(Y),J.info.memory.programs=hb.length);d.program=Y;var ob=Y.attributes;if(d.morphTargets){d.numSupportedMorphTargets=0;for(var ma,pa=\"morphTarget\",la=0;la<J.maxMorphTargets;la++)ma=pa+la,0<=ob[ma]&&d.numSupportedMorphTargets++}if(d.morphNormals)for(d.numSupportedMorphNormals=0,pa=\"morphNormal\",la=0;la<J.maxMorphNormals;la++)ma=\npa+la,0<=ob[ma]&&d.numSupportedMorphNormals++;d.uniformsList=[];for(var Jb in d.__webglShader.uniforms){var za=d.program.uniforms[Jb];za&&d.uniformsList.push([d.__webglShader.uniforms[Jb],za])}d.needsUpdate=!1}d.morphTargets&&!e.__webglMorphTargetInfluences&&(e.__webglMorphTargetInfluences=new Float32Array(J.maxMorphTargets));var aa=!1,$=!1,Z=!1,yb=d.program,qa=yb.uniforms,L=d.__webglShader.uniforms;yb.id!==tc&&(l.useProgram(yb.program),tc=yb.id,Z=$=aa=!0);d.id!==Kb&&(-1===Kb&&(Z=!0),Kb=d.id,$=!0);\nif(aa||a!==ec)l.uniformMatrix4fv(qa.projectionMatrix,!1,a.projectionMatrix.elements),Fa&&l.uniform1f(qa.logDepthBufFC,2/(Math.log(a.far+1)/Math.LN2)),a!==ec&&(ec=a),(d instanceof THREE.ShaderMaterial||d instanceof THREE.MeshPhongMaterial||d.envMap)&&null!==qa.cameraPosition&&(Na.setFromMatrixPosition(a.matrixWorld),l.uniform3f(qa.cameraPosition,Na.x,Na.y,Na.z)),(d instanceof THREE.MeshPhongMaterial||d instanceof THREE.MeshLambertMaterial||d instanceof THREE.ShaderMaterial||d.skinning)&&null!==qa.viewMatrix&&\nl.uniformMatrix4fv(qa.viewMatrix,!1,a.matrixWorldInverse.elements);if(d.skinning)if(e.bindMatrix&&null!==qa.bindMatrix&&l.uniformMatrix4fv(qa.bindMatrix,!1,e.bindMatrix.elements),e.bindMatrixInverse&&null!==qa.bindMatrixInverse&&l.uniformMatrix4fv(qa.bindMatrixInverse,!1,e.bindMatrixInverse.elements),jc&&e.skeleton&&e.skeleton.useVertexTexture){if(null!==qa.boneTexture){var Ib=K();l.uniform1i(qa.boneTexture,Ib);J.setTexture(e.skeleton.boneTexture,Ib)}null!==qa.boneTextureWidth&&l.uniform1i(qa.boneTextureWidth,\ne.skeleton.boneTextureWidth);null!==qa.boneTextureHeight&&l.uniform1i(qa.boneTextureHeight,e.skeleton.boneTextureHeight)}else e.skeleton&&e.skeleton.boneMatrices&&null!==qa.boneGlobalMatrices&&l.uniformMatrix4fv(qa.boneGlobalMatrices,!1,e.skeleton.boneMatrices);if($){c&&d.fog&&(L.fogColor.value=c.color,c instanceof THREE.Fog?(L.fogNear.value=c.near,L.fogFar.value=c.far):c instanceof THREE.FogExp2&&(L.fogDensity.value=c.density));if(d instanceof THREE.MeshPhongMaterial||d instanceof THREE.MeshLambertMaterial||\nd.lights){if(fc){var Z=!0,na,Ra,ia,ya=0,Ga=0,Oa=0,Ba,zb,Ab,Ha,Bb,Aa,va=Mc,Cb=va.directional.colors,ib=va.directional.positions,Qb=va.point.colors,Ma=va.point.positions,xb=va.point.distances,Ya=va.spot.colors,Za=va.spot.positions,Mb=va.spot.distances,Rb=va.spot.directions,db=va.spot.anglesCos,eb=va.spot.exponents,qb=va.hemi.skyColors,rb=va.hemi.groundColors,Db=va.hemi.positions,Sa=0,Ca=0,Pa=0,Ka=0,ja=0,ta=0,I=0,Ia=0,Qa=0,sb=0,fb=0,Ta=0;na=0;for(Ra=b.length;na<Ra;na++)ia=b[na],ia.onlyShadow||(Ba=ia.color,\nHa=ia.intensity,Aa=ia.distance,ia instanceof THREE.AmbientLight?ia.visible&&(J.gammaInput?(ya+=Ba.r*Ba.r,Ga+=Ba.g*Ba.g,Oa+=Ba.b*Ba.b):(ya+=Ba.r,Ga+=Ba.g,Oa+=Ba.b)):ia instanceof THREE.DirectionalLight?(ja+=1,ia.visible&&(sa.setFromMatrixPosition(ia.matrixWorld),Na.setFromMatrixPosition(ia.target.matrixWorld),sa.sub(Na),sa.normalize(),Qa=3*Sa,ib[Qa]=sa.x,ib[Qa+1]=sa.y,ib[Qa+2]=sa.z,J.gammaInput?D(Cb,Qa,Ba,Ha*Ha):E(Cb,Qa,Ba,Ha),Sa+=1)):ia instanceof THREE.PointLight?(ta+=1,ia.visible&&(sb=3*Ca,J.gammaInput?\nD(Qb,sb,Ba,Ha*Ha):E(Qb,sb,Ba,Ha),Na.setFromMatrixPosition(ia.matrixWorld),Ma[sb]=Na.x,Ma[sb+1]=Na.y,Ma[sb+2]=Na.z,xb[Ca]=Aa,Ca+=1)):ia instanceof THREE.SpotLight?(I+=1,ia.visible&&(fb=3*Pa,J.gammaInput?D(Ya,fb,Ba,Ha*Ha):E(Ya,fb,Ba,Ha),sa.setFromMatrixPosition(ia.matrixWorld),Za[fb]=sa.x,Za[fb+1]=sa.y,Za[fb+2]=sa.z,Mb[Pa]=Aa,Na.setFromMatrixPosition(ia.target.matrixWorld),sa.sub(Na),sa.normalize(),Rb[fb]=sa.x,Rb[fb+1]=sa.y,Rb[fb+2]=sa.z,db[Pa]=Math.cos(ia.angle),eb[Pa]=ia.exponent,Pa+=1)):ia instanceof\nTHREE.HemisphereLight&&(Ia+=1,ia.visible&&(sa.setFromMatrixPosition(ia.matrixWorld),sa.normalize(),Ta=3*Ka,Db[Ta]=sa.x,Db[Ta+1]=sa.y,Db[Ta+2]=sa.z,zb=ia.color,Ab=ia.groundColor,J.gammaInput?(Bb=Ha*Ha,D(qb,Ta,zb,Bb),D(rb,Ta,Ab,Bb)):(E(qb,Ta,zb,Ha),E(rb,Ta,Ab,Ha)),Ka+=1)));na=3*Sa;for(Ra=Math.max(Cb.length,3*ja);na<Ra;na++)Cb[na]=0;na=3*Ca;for(Ra=Math.max(Qb.length,3*ta);na<Ra;na++)Qb[na]=0;na=3*Pa;for(Ra=Math.max(Ya.length,3*I);na<Ra;na++)Ya[na]=0;na=3*Ka;for(Ra=Math.max(qb.length,3*Ia);na<Ra;na++)qb[na]=\n0;na=3*Ka;for(Ra=Math.max(rb.length,3*Ia);na<Ra;na++)rb[na]=0;va.directional.length=Sa;va.point.length=Ca;va.spot.length=Pa;va.hemi.length=Ka;va.ambient[0]=ya;va.ambient[1]=Ga;va.ambient[2]=Oa;fc=!1}if(Z){var ra=Mc;L.ambientLightColor.value=ra.ambient;L.directionalLightColor.value=ra.directional.colors;L.directionalLightDirection.value=ra.directional.positions;L.pointLightColor.value=ra.point.colors;L.pointLightPosition.value=ra.point.positions;L.pointLightDistance.value=ra.point.distances;L.spotLightColor.value=\nra.spot.colors;L.spotLightPosition.value=ra.spot.positions;L.spotLightDistance.value=ra.spot.distances;L.spotLightDirection.value=ra.spot.directions;L.spotLightAngleCos.value=ra.spot.anglesCos;L.spotLightExponent.value=ra.spot.exponents;L.hemisphereLightSkyColor.value=ra.hemi.skyColors;L.hemisphereLightGroundColor.value=ra.hemi.groundColors;L.hemisphereLightDirection.value=ra.hemi.positions;w(L,!0)}else w(L,!1)}if(d instanceof THREE.MeshBasicMaterial||d instanceof THREE.MeshLambertMaterial||d instanceof\nTHREE.MeshPhongMaterial){L.opacity.value=d.opacity;J.gammaInput?L.diffuse.value.copyGammaToLinear(d.color):L.diffuse.value=d.color;L.map.value=d.map;L.lightMap.value=d.lightMap;L.specularMap.value=d.specularMap;L.alphaMap.value=d.alphaMap;d.bumpMap&&(L.bumpMap.value=d.bumpMap,L.bumpScale.value=d.bumpScale);d.normalMap&&(L.normalMap.value=d.normalMap,L.normalScale.value.copy(d.normalScale));var La;d.map?La=d.map:d.specularMap?La=d.specularMap:d.normalMap?La=d.normalMap:d.bumpMap?La=d.bumpMap:d.alphaMap&&\n(La=d.alphaMap);if(void 0!==La){var Ua=La.offset,Va=La.repeat;L.offsetRepeat.value.set(Ua.x,Ua.y,Va.x,Va.y)}L.envMap.value=d.envMap;L.flipEnvMap.value=d.envMap instanceof THREE.WebGLRenderTargetCube?1:-1;L.reflectivity.value=d.reflectivity;L.refractionRatio.value=d.refractionRatio;L.combine.value=d.combine;L.useRefract.value=d.envMap&&d.envMap.mapping instanceof THREE.CubeRefractionMapping}d instanceof THREE.LineBasicMaterial?(L.diffuse.value=d.color,L.opacity.value=d.opacity):d instanceof THREE.LineDashedMaterial?\n(L.diffuse.value=d.color,L.opacity.value=d.opacity,L.dashSize.value=d.dashSize,L.totalSize.value=d.dashSize+d.gapSize,L.scale.value=d.scale):d instanceof THREE.PointCloudMaterial?(L.psColor.value=d.color,L.opacity.value=d.opacity,L.size.value=d.size,L.scale.value=O.height/2,L.map.value=d.map):d instanceof THREE.MeshPhongMaterial?(L.shininess.value=d.shininess,J.gammaInput?(L.ambient.value.copyGammaToLinear(d.ambient),L.emissive.value.copyGammaToLinear(d.emissive),L.specular.value.copyGammaToLinear(d.specular)):\n(L.ambient.value=d.ambient,L.emissive.value=d.emissive,L.specular.value=d.specular),d.wrapAround&&L.wrapRGB.value.copy(d.wrapRGB)):d instanceof THREE.MeshLambertMaterial?(J.gammaInput?(L.ambient.value.copyGammaToLinear(d.ambient),L.emissive.value.copyGammaToLinear(d.emissive)):(L.ambient.value=d.ambient,L.emissive.value=d.emissive),d.wrapAround&&L.wrapRGB.value.copy(d.wrapRGB)):d instanceof THREE.MeshDepthMaterial?(L.mNear.value=a.near,L.mFar.value=a.far,L.opacity.value=d.opacity):d instanceof THREE.MeshNormalMaterial&&\n(L.opacity.value=d.opacity);if(e.receiveShadow&&!d._shadowPass&&L.shadowMatrix)for(var Eb=0,pb=0,Nb=b.length;pb<Nb;pb++){var z=b[pb];z.castShadow&&(z instanceof THREE.SpotLight||z instanceof THREE.DirectionalLight&&!z.shadowCascade)&&(L.shadowMap.value[Eb]=z.shadowMap,L.shadowMapSize.value[Eb]=z.shadowMapSize,L.shadowMatrix.value[Eb]=z.shadowMatrix,L.shadowDarkness.value[Eb]=z.shadowDarkness,L.shadowBias.value[Eb]=z.shadowBias,Eb++)}for(var Sb=d.uniformsList,Ja,wa,$a,nb=0,Pb=Sb.length;nb<Pb;nb++){var da=\nSb[nb][0];if(!1!==da.needsUpdate){var wb=da.type,U=da.value,fa=Sb[nb][1];switch(wb){case \"1i\":l.uniform1i(fa,U);break;case \"1f\":l.uniform1f(fa,U);break;case \"2f\":l.uniform2f(fa,U[0],U[1]);break;case \"3f\":l.uniform3f(fa,U[0],U[1],U[2]);break;case \"4f\":l.uniform4f(fa,U[0],U[1],U[2],U[3]);break;case \"1iv\":l.uniform1iv(fa,U);break;case \"3iv\":l.uniform3iv(fa,U);break;case \"1fv\":l.uniform1fv(fa,U);break;case \"2fv\":l.uniform2fv(fa,U);break;case \"3fv\":l.uniform3fv(fa,U);break;case \"4fv\":l.uniform4fv(fa,U);\nbreak;case \"Matrix3fv\":l.uniformMatrix3fv(fa,!1,U);break;case \"Matrix4fv\":l.uniformMatrix4fv(fa,!1,U);break;case \"i\":l.uniform1i(fa,U);break;case \"f\":l.uniform1f(fa,U);break;case \"v2\":l.uniform2f(fa,U.x,U.y);break;case \"v3\":l.uniform3f(fa,U.x,U.y,U.z);break;case \"v4\":l.uniform4f(fa,U.x,U.y,U.z,U.w);break;case \"c\":l.uniform3f(fa,U.r,U.g,U.b);break;case \"iv1\":l.uniform1iv(fa,U);break;case \"iv\":l.uniform3iv(fa,U);break;case \"fv1\":l.uniform1fv(fa,U);break;case \"fv\":l.uniform3fv(fa,U);break;case \"v2v\":void 0===\nda._array&&(da._array=new Float32Array(2*U.length));for(var N=0,xa=U.length;N<xa;N++)$a=2*N,da._array[$a]=U[N].x,da._array[$a+1]=U[N].y;l.uniform2fv(fa,da._array);break;case \"v3v\":void 0===da._array&&(da._array=new Float32Array(3*U.length));N=0;for(xa=U.length;N<xa;N++)$a=3*N,da._array[$a]=U[N].x,da._array[$a+1]=U[N].y,da._array[$a+2]=U[N].z;l.uniform3fv(fa,da._array);break;case \"v4v\":void 0===da._array&&(da._array=new Float32Array(4*U.length));N=0;for(xa=U.length;N<xa;N++)$a=4*N,da._array[$a]=U[N].x,\nda._array[$a+1]=U[N].y,da._array[$a+2]=U[N].z,da._array[$a+3]=U[N].w;l.uniform4fv(fa,da._array);break;case \"m3\":l.uniformMatrix3fv(fa,!1,U.elements);break;case \"m3v\":void 0===da._array&&(da._array=new Float32Array(9*U.length));N=0;for(xa=U.length;N<xa;N++)U[N].flattenToArrayOffset(da._array,9*N);l.uniformMatrix3fv(fa,!1,da._array);break;case \"m4\":l.uniformMatrix4fv(fa,!1,U.elements);break;case \"m4v\":void 0===da._array&&(da._array=new Float32Array(16*U.length));N=0;for(xa=U.length;N<xa;N++)U[N].flattenToArrayOffset(da._array,\n16*N);l.uniformMatrix4fv(fa,!1,da._array);break;case \"t\":Ja=U;wa=K();l.uniform1i(fa,wa);if(!Ja)continue;if(Ja instanceof THREE.CubeTexture||Ja.image instanceof Array&&6===Ja.image.length){var ua=Ja,Lb=wa;if(6===ua.image.length)if(ua.needsUpdate){ua.image.__webglTextureCube||(ua.addEventListener(\"dispose\",gc),ua.image.__webglTextureCube=l.createTexture(),J.info.memory.textures++);l.activeTexture(l.TEXTURE0+Lb);l.bindTexture(l.TEXTURE_CUBE_MAP,ua.image.__webglTextureCube);l.pixelStorei(l.UNPACK_FLIP_Y_WEBGL,\nua.flipY);for(var Ob=ua instanceof THREE.CompressedTexture,Tb=ua.image[0]instanceof THREE.DataTexture,kb=[],Da=0;6>Da;Da++)kb[Da]=!J.autoScaleCubemaps||Ob||Tb?Tb?ua.image[Da].image:ua.image[Da]:R(ua.image[Da],$c);var ka=kb[0],Zb=THREE.Math.isPowerOfTwo(ka.width)&&THREE.Math.isPowerOfTwo(ka.height),ab=Q(ua.format),Fb=Q(ua.type);F(l.TEXTURE_CUBE_MAP,ua,Zb);for(Da=0;6>Da;Da++)if(Ob)for(var gb,$b=kb[Da].mipmaps,ga=0,Xb=$b.length;ga<Xb;ga++)gb=$b[ga],ua.format!==THREE.RGBAFormat&&ua.format!==THREE.RGBFormat?\n-1<Nc().indexOf(ab)?l.compressedTexImage2D(l.TEXTURE_CUBE_MAP_POSITIVE_X+Da,ga,ab,gb.width,gb.height,0,gb.data):console.warn(\"Attempt to load unsupported compressed texture format\"):l.texImage2D(l.TEXTURE_CUBE_MAP_POSITIVE_X+Da,ga,ab,gb.width,gb.height,0,ab,Fb,gb.data);else Tb?l.texImage2D(l.TEXTURE_CUBE_MAP_POSITIVE_X+Da,0,ab,kb[Da].width,kb[Da].height,0,ab,Fb,kb[Da].data):l.texImage2D(l.TEXTURE_CUBE_MAP_POSITIVE_X+Da,0,ab,ab,Fb,kb[Da]);ua.generateMipmaps&&Zb&&l.generateMipmap(l.TEXTURE_CUBE_MAP);\nua.needsUpdate=!1;if(ua.onUpdate)ua.onUpdate()}else l.activeTexture(l.TEXTURE0+Lb),l.bindTexture(l.TEXTURE_CUBE_MAP,ua.image.__webglTextureCube)}else if(Ja instanceof THREE.WebGLRenderTargetCube){var Yb=Ja;l.activeTexture(l.TEXTURE0+wa);l.bindTexture(l.TEXTURE_CUBE_MAP,Yb.__webglTexture)}else J.setTexture(Ja,wa);break;case \"tv\":void 0===da._array&&(da._array=[]);N=0;for(xa=da.value.length;N<xa;N++)da._array[N]=K();l.uniform1iv(fa,da._array);N=0;for(xa=da.value.length;N<xa;N++)Ja=da.value[N],wa=da._array[N],\nJa&&J.setTexture(Ja,wa);break;default:console.warn(\"THREE.WebGLRenderer: Unknown uniform type: \"+wb)}}}}l.uniformMatrix4fv(qa.modelViewMatrix,!1,e._modelViewMatrix.elements);qa.normalMatrix&&l.uniformMatrix3fv(qa.normalMatrix,!1,e._normalMatrix.elements);null!==qa.modelMatrix&&l.uniformMatrix4fv(qa.modelMatrix,!1,e.matrixWorld.elements);return yb}function w(a,b){a.ambientLightColor.needsUpdate=b;a.directionalLightColor.needsUpdate=b;a.directionalLightDirection.needsUpdate=b;a.pointLightColor.needsUpdate=\nb;a.pointLightPosition.needsUpdate=b;a.pointLightDistance.needsUpdate=b;a.spotLightColor.needsUpdate=b;a.spotLightPosition.needsUpdate=b;a.spotLightDistance.needsUpdate=b;a.spotLightDirection.needsUpdate=b;a.spotLightAngleCos.needsUpdate=b;a.spotLightExponent.needsUpdate=b;a.hemisphereLightSkyColor.needsUpdate=b;a.hemisphereLightGroundColor.needsUpdate=b;a.hemisphereLightDirection.needsUpdate=b}function K(){var a=dc;a>=Oc&&console.warn(\"WebGLRenderer: trying to use \"+a+\" texture units while this GPU supports only \"+\nOc);dc+=1;return a}function x(a,b){a._modelViewMatrix.multiplyMatrices(b.matrixWorldInverse,a.matrixWorld);a._normalMatrix.getNormalMatrix(a._modelViewMatrix)}function D(a,b,c,d){a[b]=c.r*c.r*d;a[b+1]=c.g*c.g*d;a[b+2]=c.b*c.b*d}function E(a,b,c,d){a[b]=c.r*d;a[b+1]=c.g*d;a[b+2]=c.b*d}function A(a){a!==Pc&&(l.lineWidth(a),Pc=a)}function B(a,b,c){Qc!==a&&(a?l.enable(l.POLYGON_OFFSET_FILL):l.disable(l.POLYGON_OFFSET_FILL),Qc=a);!a||Rc===b&&Sc===c||(l.polygonOffset(b,c),Rc=b,Sc=c)}function F(a,b,c){c?\n(l.texParameteri(a,l.TEXTURE_WRAP_S,Q(b.wrapS)),l.texParameteri(a,l.TEXTURE_WRAP_T,Q(b.wrapT)),l.texParameteri(a,l.TEXTURE_MAG_FILTER,Q(b.magFilter)),l.texParameteri(a,l.TEXTURE_MIN_FILTER,Q(b.minFilter))):(l.texParameteri(a,l.TEXTURE_WRAP_S,l.CLAMP_TO_EDGE),l.texParameteri(a,l.TEXTURE_WRAP_T,l.CLAMP_TO_EDGE),l.texParameteri(a,l.TEXTURE_MAG_FILTER,T(b.magFilter)),l.texParameteri(a,l.TEXTURE_MIN_FILTER,T(b.minFilter)));(c=pa.get(\"EXT_texture_filter_anisotropic\"))&&b.type!==THREE.FloatType&&(1<b.anisotropy||\nb.__oldAnisotropy)&&(l.texParameterf(a,c.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(b.anisotropy,J.getMaxAnisotropy())),b.__oldAnisotropy=b.anisotropy)}function R(a,b){if(a.width>b||a.height>b){var c=b/Math.max(a.width,a.height),d=document.createElement(\"canvas\");d.width=Math.floor(a.width*c);d.height=Math.floor(a.height*c);d.getContext(\"2d\").drawImage(a,0,0,a.width,a.height,0,0,d.width,d.height);console.log(\"THREE.WebGLRenderer:\",a,\"is too big (\"+a.width+\"x\"+a.height+\"). Resized to \"+d.width+\"x\"+d.height+\n\".\");return d}return a}function H(a,b){l.bindRenderbuffer(l.RENDERBUFFER,a);b.depthBuffer&&!b.stencilBuffer?(l.renderbufferStorage(l.RENDERBUFFER,l.DEPTH_COMPONENT16,b.width,b.height),l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_ATTACHMENT,l.RENDERBUFFER,a)):b.depthBuffer&&b.stencilBuffer?(l.renderbufferStorage(l.RENDERBUFFER,l.DEPTH_STENCIL,b.width,b.height),l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_STENCIL_ATTACHMENT,l.RENDERBUFFER,a)):l.renderbufferStorage(l.RENDERBUFFER,l.RGBA4,b.width,\nb.height)}function C(a){a instanceof THREE.WebGLRenderTargetCube?(l.bindTexture(l.TEXTURE_CUBE_MAP,a.__webglTexture),l.generateMipmap(l.TEXTURE_CUBE_MAP),l.bindTexture(l.TEXTURE_CUBE_MAP,null)):(l.bindTexture(l.TEXTURE_2D,a.__webglTexture),l.generateMipmap(l.TEXTURE_2D),l.bindTexture(l.TEXTURE_2D,null))}function T(a){return a===THREE.NearestFilter||a===THREE.NearestMipMapNearestFilter||a===THREE.NearestMipMapLinearFilter?l.NEAREST:l.LINEAR}function Q(a){var b;if(a===THREE.RepeatWrapping)return l.REPEAT;\nif(a===THREE.ClampToEdgeWrapping)return l.CLAMP_TO_EDGE;if(a===THREE.MirroredRepeatWrapping)return l.MIRRORED_REPEAT;if(a===THREE.NearestFilter)return l.NEAREST;if(a===THREE.NearestMipMapNearestFilter)return l.NEAREST_MIPMAP_NEAREST;if(a===THREE.NearestMipMapLinearFilter)return l.NEAREST_MIPMAP_LINEAR;if(a===THREE.LinearFilter)return l.LINEAR;if(a===THREE.LinearMipMapNearestFilter)return l.LINEAR_MIPMAP_NEAREST;if(a===THREE.LinearMipMapLinearFilter)return l.LINEAR_MIPMAP_LINEAR;if(a===THREE.UnsignedByteType)return l.UNSIGNED_BYTE;\nif(a===THREE.UnsignedShort4444Type)return l.UNSIGNED_SHORT_4_4_4_4;if(a===THREE.UnsignedShort5551Type)return l.UNSIGNED_SHORT_5_5_5_1;if(a===THREE.UnsignedShort565Type)return l.UNSIGNED_SHORT_5_6_5;if(a===THREE.ByteType)return l.BYTE;if(a===THREE.ShortType)return l.SHORT;if(a===THREE.UnsignedShortType)return l.UNSIGNED_SHORT;if(a===THREE.IntType)return l.INT;if(a===THREE.UnsignedIntType)return l.UNSIGNED_INT;if(a===THREE.FloatType)return l.FLOAT;if(a===THREE.AlphaFormat)return l.ALPHA;if(a===THREE.RGBFormat)return l.RGB;\nif(a===THREE.RGBAFormat)return l.RGBA;if(a===THREE.LuminanceFormat)return l.LUMINANCE;if(a===THREE.LuminanceAlphaFormat)return l.LUMINANCE_ALPHA;if(a===THREE.AddEquation)return l.FUNC_ADD;if(a===THREE.SubtractEquation)return l.FUNC_SUBTRACT;if(a===THREE.ReverseSubtractEquation)return l.FUNC_REVERSE_SUBTRACT;if(a===THREE.ZeroFactor)return l.ZERO;if(a===THREE.OneFactor)return l.ONE;if(a===THREE.SrcColorFactor)return l.SRC_COLOR;if(a===THREE.OneMinusSrcColorFactor)return l.ONE_MINUS_SRC_COLOR;if(a===\nTHREE.SrcAlphaFactor)return l.SRC_ALPHA;if(a===THREE.OneMinusSrcAlphaFactor)return l.ONE_MINUS_SRC_ALPHA;if(a===THREE.DstAlphaFactor)return l.DST_ALPHA;if(a===THREE.OneMinusDstAlphaFactor)return l.ONE_MINUS_DST_ALPHA;if(a===THREE.DstColorFactor)return l.DST_COLOR;if(a===THREE.OneMinusDstColorFactor)return l.ONE_MINUS_DST_COLOR;if(a===THREE.SrcAlphaSaturateFactor)return l.SRC_ALPHA_SATURATE;b=pa.get(\"WEBGL_compressed_texture_s3tc\");if(null!==b){if(a===THREE.RGB_S3TC_DXT1_Format)return b.COMPRESSED_RGB_S3TC_DXT1_EXT;\nif(a===THREE.RGBA_S3TC_DXT1_Format)return b.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(a===THREE.RGBA_S3TC_DXT3_Format)return b.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(a===THREE.RGBA_S3TC_DXT5_Format)return b.COMPRESSED_RGBA_S3TC_DXT5_EXT}b=pa.get(\"WEBGL_compressed_texture_pvrtc\");if(null!==b){if(a===THREE.RGB_PVRTC_4BPPV1_Format)return b.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(a===THREE.RGB_PVRTC_2BPPV1_Format)return b.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(a===THREE.RGBA_PVRTC_4BPPV1_Format)return b.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;\nif(a===THREE.RGBA_PVRTC_2BPPV1_Format)return b.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}b=pa.get(\"EXT_blend_minmax\");if(null!==b){if(a===THREE.MinEquation)return b.MIN_EXT;if(a===THREE.MaxEquation)return b.MAX_EXT}return 0}console.log(\"THREE.WebGLRenderer\",THREE.REVISION);a=a||{};var O=void 0!==a.canvas?a.canvas:document.createElement(\"canvas\"),S=void 0!==a.context?a.context:null,X=void 0!==a.precision?a.precision:\"highp\",Y=void 0!==a.alpha?a.alpha:!1,la=void 0!==a.depth?a.depth:!0,ma=void 0!==a.stencil?\na.stencil:!0,ya=void 0!==a.antialias?a.antialias:!1,P=void 0!==a.premultipliedAlpha?a.premultipliedAlpha:!0,Ga=void 0!==a.preserveDrawingBuffer?a.preserveDrawingBuffer:!1,Fa=void 0!==a.logarithmicDepthBuffer?a.logarithmicDepthBuffer:!1,za=new THREE.Color(0),bb=0,cb=[],ob={},jb=[],Jb=[],Ib=[],yb=[],Ra=[];this.domElement=O;this.context=null;this.devicePixelRatio=void 0!==a.devicePixelRatio?a.devicePixelRatio:void 0!==self.devicePixelRatio?self.devicePixelRatio:1;this.sortObjects=this.autoClearStencil=\nthis.autoClearDepth=this.autoClearColor=this.autoClear=!0;this.shadowMapEnabled=this.gammaOutput=this.gammaInput=!1;this.shadowMapType=THREE.PCFShadowMap;this.shadowMapCullFace=THREE.CullFaceFront;this.shadowMapCascade=this.shadowMapDebug=!1;this.maxMorphTargets=8;this.maxMorphNormals=4;this.autoScaleCubemaps=!0;this.info={memory:{programs:0,geometries:0,textures:0},render:{calls:0,vertices:0,faces:0,points:0}};var J=this,hb=[],tc=null,Tc=null,Kb=-1,Oa=-1,ec=null,dc=0,Lb=-1,Mb=-1,pb=-1,Nb=-1,Ob=-1,\nXb=-1,Yb=-1,nb=-1,Qc=null,Rc=null,Sc=null,Pc=null,Pb=0,kc=0,lc=O.width,mc=O.height,Uc=0,Vc=0,wb=new Uint8Array(16),ib=new Uint8Array(16),Ec=new THREE.Frustum,Ac=new THREE.Matrix4,Gc=new THREE.Matrix4,Na=new THREE.Vector3,sa=new THREE.Vector3,fc=!0,Mc={ambient:[0,0,0],directional:{length:0,colors:[],positions:[]},point:{length:0,colors:[],positions:[],distances:[]},spot:{length:0,colors:[],positions:[],distances:[],directions:[],anglesCos:[],exponents:[]},hemi:{length:0,skyColors:[],groundColors:[],\npositions:[]}},l;try{var Wc={alpha:Y,depth:la,stencil:ma,antialias:ya,premultipliedAlpha:P,preserveDrawingBuffer:Ga};l=S||O.getContext(\"webgl\",Wc)||O.getContext(\"experimental-webgl\",Wc);if(null===l){if(null!==O.getContext(\"webgl\"))throw\"Error creating WebGL context with your selected attributes.\";throw\"Error creating WebGL context.\";}}catch(ad){console.error(ad)}void 0===l.getShaderPrecisionFormat&&(l.getShaderPrecisionFormat=function(){return{rangeMin:1,rangeMax:1,precision:1}});var pa=new THREE.WebGLExtensions(l);\npa.get(\"OES_texture_float\");pa.get(\"OES_texture_float_linear\");pa.get(\"OES_standard_derivatives\");Fa&&pa.get(\"EXT_frag_depth\");l.clearColor(0,0,0,1);l.clearDepth(1);l.clearStencil(0);l.enable(l.DEPTH_TEST);l.depthFunc(l.LEQUAL);l.frontFace(l.CCW);l.cullFace(l.BACK);l.enable(l.CULL_FACE);l.enable(l.BLEND);l.blendEquation(l.FUNC_ADD);l.blendFunc(l.SRC_ALPHA,l.ONE_MINUS_SRC_ALPHA);l.viewport(Pb,kc,lc,mc);l.clearColor(za.r,za.g,za.b,bb);this.context=l;var Oc=l.getParameter(l.MAX_TEXTURE_IMAGE_UNITS),\nbd=l.getParameter(l.MAX_VERTEX_TEXTURE_IMAGE_UNITS),cd=l.getParameter(l.MAX_TEXTURE_SIZE),$c=l.getParameter(l.MAX_CUBE_MAP_TEXTURE_SIZE),sc=0<bd,jc=sc&&pa.get(\"OES_texture_float\"),dd=l.getShaderPrecisionFormat(l.VERTEX_SHADER,l.HIGH_FLOAT),ed=l.getShaderPrecisionFormat(l.VERTEX_SHADER,l.MEDIUM_FLOAT);l.getShaderPrecisionFormat(l.VERTEX_SHADER,l.LOW_FLOAT);var fd=l.getShaderPrecisionFormat(l.FRAGMENT_SHADER,l.HIGH_FLOAT),gd=l.getShaderPrecisionFormat(l.FRAGMENT_SHADER,l.MEDIUM_FLOAT);l.getShaderPrecisionFormat(l.FRAGMENT_SHADER,\nl.LOW_FLOAT);var Nc=function(){var a;return function(){if(void 0!==a)return a;a=[];if(pa.get(\"WEBGL_compressed_texture_pvrtc\")||pa.get(\"WEBGL_compressed_texture_s3tc\"))for(var b=l.getParameter(l.COMPRESSED_TEXTURE_FORMATS),c=0;c<b.length;c++)a.push(b[c]);return a}}(),hd=0<dd.precision&&0<fd.precision,Xc=0<ed.precision&&0<gd.precision;\"highp\"!==X||hd||(Xc?(X=\"mediump\",console.warn(\"THREE.WebGLRenderer: highp not supported, using mediump.\")):(X=\"lowp\",console.warn(\"THREE.WebGLRenderer: highp and mediump not supported, using lowp.\")));\n\"mediump\"!==X||Xc||(X=\"lowp\",console.warn(\"THREE.WebGLRenderer: mediump not supported, using lowp.\"));var id=new THREE.ShadowMapPlugin(this,cb,ob,jb),jd=new THREE.SpritePlugin(this,yb),kd=new THREE.LensFlarePlugin(this,Ra);this.getContext=function(){return l};this.supportsVertexTextures=function(){return sc};this.supportsFloatTextures=function(){return pa.get(\"OES_texture_float\")};this.supportsStandardDerivatives=function(){return pa.get(\"OES_standard_derivatives\")};this.supportsCompressedTextureS3TC=\nfunction(){return pa.get(\"WEBGL_compressed_texture_s3tc\")};this.supportsCompressedTexturePVRTC=function(){return pa.get(\"WEBGL_compressed_texture_pvrtc\")};this.supportsBlendMinMax=function(){return pa.get(\"EXT_blend_minmax\")};this.getMaxAnisotropy=function(){var a;return function(){if(void 0!==a)return a;var b=pa.get(\"EXT_texture_filter_anisotropic\");return a=null!==b?l.getParameter(b.MAX_TEXTURE_MAX_ANISOTROPY_EXT):0}}();this.getPrecision=function(){return X};this.setSize=function(a,b,c){O.width=\na*this.devicePixelRatio;O.height=b*this.devicePixelRatio;!1!==c&&(O.style.width=a+\"px\",O.style.height=b+\"px\");this.setViewport(0,0,a,b)};this.setViewport=function(a,b,c,d){Pb=a*this.devicePixelRatio;kc=b*this.devicePixelRatio;lc=c*this.devicePixelRatio;mc=d*this.devicePixelRatio;l.viewport(Pb,kc,lc,mc)};this.setScissor=function(a,b,c,d){l.scissor(a*this.devicePixelRatio,b*this.devicePixelRatio,c*this.devicePixelRatio,d*this.devicePixelRatio)};this.enableScissorTest=function(a){a?l.enable(l.SCISSOR_TEST):\nl.disable(l.SCISSOR_TEST)};this.setClearColor=function(a,b){za.set(a);bb=void 0!==b?b:1;l.clearColor(za.r,za.g,za.b,bb)};this.setClearColorHex=function(a,b){console.warn(\"THREE.WebGLRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.\");this.setClearColor(a,b)};this.getClearColor=function(){return za};this.getClearAlpha=function(){return bb};this.clear=function(a,b,c){var d=0;if(void 0===a||a)d|=l.COLOR_BUFFER_BIT;if(void 0===b||b)d|=l.DEPTH_BUFFER_BIT;if(void 0===c||c)d|=\nl.STENCIL_BUFFER_BIT;l.clear(d)};this.clearColor=function(){l.clear(l.COLOR_BUFFER_BIT)};this.clearDepth=function(){l.clear(l.DEPTH_BUFFER_BIT)};this.clearStencil=function(){l.clear(l.STENCIL_BUFFER_BIT)};this.clearTarget=function(a,b,c,d){this.setRenderTarget(a);this.clear(b,c,d)};this.resetGLState=function(){ec=tc=null;Kb=Oa=Mb=Lb=nb=Yb=pb=-1;fc=!0};var Hc=function(a){a.target.traverse(function(a){a.removeEventListener(\"remove\",Hc);if(a instanceof THREE.Mesh||a instanceof THREE.PointCloud||a instanceof\nTHREE.Line)delete ob[a.id];else if(a instanceof THREE.ImmediateRenderObject||a.immediateRenderCallback)for(var b=jb,c=b.length-1;0<=c;c--)b[c].object===a&&b.splice(c,1);delete a.__webglInit;delete a._modelViewMatrix;delete a._normalMatrix;delete a.__webglActive})},Ic=function(a){a=a.target;a.removeEventListener(\"dispose\",Ic);delete a.__webglInit;if(a instanceof THREE.BufferGeometry){for(var b in a.attributes){var c=a.attributes[b];void 0!==c.buffer&&(l.deleteBuffer(c.buffer),delete c.buffer)}J.info.memory.geometries--}else if(b=\nxb[a.id],void 0!==b){for(var c=0,d=b.length;c<d;c++){var e=b[c];if(void 0!==e.numMorphTargets){for(var f=0,g=e.numMorphTargets;f<g;f++)l.deleteBuffer(e.__webglMorphTargetsBuffers[f]);delete e.__webglMorphTargetsBuffers}if(void 0!==e.numMorphNormals){f=0;for(g=e.numMorphNormals;f<g;f++)l.deleteBuffer(e.__webglMorphNormalsBuffers[f]);delete e.__webglMorphNormalsBuffers}Yc(e)}delete xb[a.id]}else Yc(a);Oa=-1},gc=function(a){a=a.target;a.removeEventListener(\"dispose\",gc);a.image&&a.image.__webglTextureCube?\n(l.deleteTexture(a.image.__webglTextureCube),delete a.image.__webglTextureCube):void 0!==a.__webglInit&&(l.deleteTexture(a.__webglTexture),delete a.__webglTexture,delete a.__webglInit);J.info.memory.textures--},Zc=function(a){a=a.target;a.removeEventListener(\"dispose\",Zc);if(a&&void 0!==a.__webglTexture){l.deleteTexture(a.__webglTexture);delete a.__webglTexture;if(a instanceof THREE.WebGLRenderTargetCube)for(var b=0;6>b;b++)l.deleteFramebuffer(a.__webglFramebuffer[b]),l.deleteRenderbuffer(a.__webglRenderbuffer[b]);\nelse l.deleteFramebuffer(a.__webglFramebuffer),l.deleteRenderbuffer(a.__webglRenderbuffer);delete a.__webglFramebuffer;delete a.__webglRenderbuffer}J.info.memory.textures--},Dc=function(a){a=a.target;a.removeEventListener(\"dispose\",Dc);Cc(a)},Yc=function(a){for(var b=\"__webglVertexBuffer __webglNormalBuffer __webglTangentBuffer __webglColorBuffer __webglUVBuffer __webglUV2Buffer __webglSkinIndicesBuffer __webglSkinWeightsBuffer __webglFaceBuffer __webglLineBuffer __webglLineDistanceBuffer\".split(\" \"),\nc=0,d=b.length;c<d;c++){var e=b[c];void 0!==a[e]&&(l.deleteBuffer(a[e]),delete a[e])}if(void 0!==a.__webglCustomAttributesList){for(e in a.__webglCustomAttributesList)l.deleteBuffer(a.__webglCustomAttributesList[e].buffer);delete a.__webglCustomAttributesList}J.info.memory.geometries--},Cc=function(a){var b=a.program.program;if(void 0!==b){a.program=void 0;var c,d,e=!1;a=0;for(c=hb.length;a<c;a++)if(d=hb[a],d.program===b){d.usedTimes--;0===d.usedTimes&&(e=!0);break}if(!0===e){e=[];a=0;for(c=hb.length;a<\nc;a++)d=hb[a],d.program!==b&&e.push(d);hb=e;l.deleteProgram(b);J.info.memory.programs--}}};this.renderBufferImmediate=function(a,b,c){f();a.hasPositions&&!a.__webglVertexBuffer&&(a.__webglVertexBuffer=l.createBuffer());a.hasNormals&&!a.__webglNormalBuffer&&(a.__webglNormalBuffer=l.createBuffer());a.hasUvs&&!a.__webglUvBuffer&&(a.__webglUvBuffer=l.createBuffer());a.hasColors&&!a.__webglColorBuffer&&(a.__webglColorBuffer=l.createBuffer());a.hasPositions&&(l.bindBuffer(l.ARRAY_BUFFER,a.__webglVertexBuffer),\nl.bufferData(l.ARRAY_BUFFER,a.positionArray,l.DYNAMIC_DRAW),g(b.attributes.position),l.vertexAttribPointer(b.attributes.position,3,l.FLOAT,!1,0,0));if(a.hasNormals){l.bindBuffer(l.ARRAY_BUFFER,a.__webglNormalBuffer);if(c.shading===THREE.FlatShading){var d,e,k,m,n,p,r,q,t,s,v,u=3*a.count;for(v=0;v<u;v+=9)s=a.normalArray,d=s[v],e=s[v+1],k=s[v+2],m=s[v+3],p=s[v+4],q=s[v+5],n=s[v+6],r=s[v+7],t=s[v+8],d=(d+m+n)/3,e=(e+p+r)/3,k=(k+q+t)/3,s[v]=d,s[v+1]=e,s[v+2]=k,s[v+3]=d,s[v+4]=e,s[v+5]=k,s[v+6]=d,s[v+\n7]=e,s[v+8]=k}l.bufferData(l.ARRAY_BUFFER,a.normalArray,l.DYNAMIC_DRAW);g(b.attributes.normal);l.vertexAttribPointer(b.attributes.normal,3,l.FLOAT,!1,0,0)}a.hasUvs&&c.map&&(l.bindBuffer(l.ARRAY_BUFFER,a.__webglUvBuffer),l.bufferData(l.ARRAY_BUFFER,a.uvArray,l.DYNAMIC_DRAW),g(b.attributes.uv),l.vertexAttribPointer(b.attributes.uv,2,l.FLOAT,!1,0,0));a.hasColors&&c.vertexColors!==THREE.NoColors&&(l.bindBuffer(l.ARRAY_BUFFER,a.__webglColorBuffer),l.bufferData(l.ARRAY_BUFFER,a.colorArray,l.DYNAMIC_DRAW),\ng(b.attributes.color),l.vertexAttribPointer(b.attributes.color,3,l.FLOAT,!1,0,0));h();l.drawArrays(l.TRIANGLES,0,a.count);a.count=0};this.renderBufferDirect=function(a,b,c,d,g,h){if(!1!==d.visible)if(a=G(a,b,c,d,h),b=!1,c=16777215*g.id+2*a.id+(d.wireframe?1:0),c!==Oa&&(Oa=c,b=!0),b&&f(),h instanceof THREE.Mesh)if(h=!0===d.wireframe?l.LINES:l.TRIANGLES,c=g.attributes.index){var k,m;c.array instanceof Uint32Array&&pa.get(\"OES_element_index_uint\")?(k=l.UNSIGNED_INT,m=4):(k=l.UNSIGNED_SHORT,m=2);var n=\ng.offsets;if(0===n.length)b&&(e(d,a,g,0),l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,c.buffer)),l.drawElements(h,c.array.length,k,0),J.info.render.calls++,J.info.render.vertices+=c.array.length,J.info.render.faces+=c.array.length/3;else{b=!0;for(var p=0,r=n.length;p<r;p++){var q=n[p].index;b&&(e(d,a,g,q),l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,c.buffer));l.drawElements(h,n[p].count,k,n[p].start*m);J.info.render.calls++;J.info.render.vertices+=n[p].count;J.info.render.faces+=n[p].count/3}}}else b&&e(d,a,g,0),\nd=g.attributes.position,l.drawArrays(h,0,d.array.length/3),J.info.render.calls++,J.info.render.vertices+=d.array.length/3,J.info.render.faces+=d.array.length/9;else if(h instanceof THREE.PointCloud)b&&e(d,a,g,0),d=g.attributes.position,l.drawArrays(l.POINTS,0,d.array.length/3),J.info.render.calls++,J.info.render.points+=d.array.length/3;else if(h instanceof THREE.Line)if(h=h.mode===THREE.LineStrip?l.LINE_STRIP:l.LINES,A(d.linewidth),c=g.attributes.index)if(c.array instanceof Uint32Array?(k=l.UNSIGNED_INT,\nm=4):(k=l.UNSIGNED_SHORT,m=2),n=g.offsets,0===n.length)b&&(e(d,a,g,0),l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,c.buffer)),l.drawElements(h,c.array.length,k,0),J.info.render.calls++,J.info.render.vertices+=c.array.length;else for(1<n.length&&(b=!0),p=0,r=n.length;p<r;p++)q=n[p].index,b&&(e(d,a,g,q),l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,c.buffer)),l.drawElements(h,n[p].count,k,n[p].start*m),J.info.render.calls++,J.info.render.vertices+=n[p].count;else b&&e(d,a,g,0),d=g.attributes.position,l.drawArrays(h,0,\nd.array.length/3),J.info.render.calls++,J.info.render.points+=d.array.length/3};this.renderBuffer=function(a,b,c,d,e,k){if(!1!==d.visible){c=G(a,b,c,d,k);b=c.attributes;a=!1;c=16777215*e.id+2*c.id+(d.wireframe?1:0);c!==Oa&&(Oa=c,a=!0);a&&f();if(!d.morphTargets&&0<=b.position)a&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglVertexBuffer),g(b.position),l.vertexAttribPointer(b.position,3,l.FLOAT,!1,0,0));else if(k.morphTargetBase){c=d.program.attributes;-1!==k.morphTargetBase&&0<=c.position?(l.bindBuffer(l.ARRAY_BUFFER,\ne.__webglMorphTargetsBuffers[k.morphTargetBase]),g(c.position),l.vertexAttribPointer(c.position,3,l.FLOAT,!1,0,0)):0<=c.position&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglVertexBuffer),g(c.position),l.vertexAttribPointer(c.position,3,l.FLOAT,!1,0,0));if(k.morphTargetForcedOrder.length)for(var m=0,n=k.morphTargetForcedOrder,r=k.morphTargetInfluences;m<d.numSupportedMorphTargets&&m<n.length;)0<=c[\"morphTarget\"+m]&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglMorphTargetsBuffers[n[m]]),g(c[\"morphTarget\"+m]),l.vertexAttribPointer(c[\"morphTarget\"+\nm],3,l.FLOAT,!1,0,0)),0<=c[\"morphNormal\"+m]&&d.morphNormals&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglMorphNormalsBuffers[n[m]]),g(c[\"morphNormal\"+m]),l.vertexAttribPointer(c[\"morphNormal\"+m],3,l.FLOAT,!1,0,0)),k.__webglMorphTargetInfluences[m]=r[n[m]],m++;else{var n=[],r=k.morphTargetInfluences,q,t=r.length;for(q=0;q<t;q++)m=r[q],0<m&&n.push([m,q]);n.length>d.numSupportedMorphTargets?(n.sort(p),n.length=d.numSupportedMorphTargets):n.length>d.numSupportedMorphNormals?n.sort(p):0===n.length&&n.push([0,\n0]);for(m=0;m<d.numSupportedMorphTargets;)n[m]?(q=n[m][1],0<=c[\"morphTarget\"+m]&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglMorphTargetsBuffers[q]),g(c[\"morphTarget\"+m]),l.vertexAttribPointer(c[\"morphTarget\"+m],3,l.FLOAT,!1,0,0)),0<=c[\"morphNormal\"+m]&&d.morphNormals&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglMorphNormalsBuffers[q]),g(c[\"morphNormal\"+m]),l.vertexAttribPointer(c[\"morphNormal\"+m],3,l.FLOAT,!1,0,0)),k.__webglMorphTargetInfluences[m]=r[q]):k.__webglMorphTargetInfluences[m]=0,m++}null!==d.program.uniforms.morphTargetInfluences&&\nl.uniform1fv(d.program.uniforms.morphTargetInfluences,k.__webglMorphTargetInfluences)}if(a){if(e.__webglCustomAttributesList)for(c=0,r=e.__webglCustomAttributesList.length;c<r;c++)n=e.__webglCustomAttributesList[c],0<=b[n.buffer.belongsToAttribute]&&(l.bindBuffer(l.ARRAY_BUFFER,n.buffer),g(b[n.buffer.belongsToAttribute]),l.vertexAttribPointer(b[n.buffer.belongsToAttribute],n.size,l.FLOAT,!1,0,0));0<=b.color&&(0<k.geometry.colors.length||0<k.geometry.faces.length?(l.bindBuffer(l.ARRAY_BUFFER,e.__webglColorBuffer),\ng(b.color),l.vertexAttribPointer(b.color,3,l.FLOAT,!1,0,0)):void 0!==d.defaultAttributeValues&&l.vertexAttrib3fv(b.color,d.defaultAttributeValues.color));0<=b.normal&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglNormalBuffer),g(b.normal),l.vertexAttribPointer(b.normal,3,l.FLOAT,!1,0,0));0<=b.tangent&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglTangentBuffer),g(b.tangent),l.vertexAttribPointer(b.tangent,4,l.FLOAT,!1,0,0));0<=b.uv&&(k.geometry.faceVertexUvs[0]?(l.bindBuffer(l.ARRAY_BUFFER,e.__webglUVBuffer),g(b.uv),\nl.vertexAttribPointer(b.uv,2,l.FLOAT,!1,0,0)):void 0!==d.defaultAttributeValues&&l.vertexAttrib2fv(b.uv,d.defaultAttributeValues.uv));0<=b.uv2&&(k.geometry.faceVertexUvs[1]?(l.bindBuffer(l.ARRAY_BUFFER,e.__webglUV2Buffer),g(b.uv2),l.vertexAttribPointer(b.uv2,2,l.FLOAT,!1,0,0)):void 0!==d.defaultAttributeValues&&l.vertexAttrib2fv(b.uv2,d.defaultAttributeValues.uv2));d.skinning&&0<=b.skinIndex&&0<=b.skinWeight&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglSkinIndicesBuffer),g(b.skinIndex),l.vertexAttribPointer(b.skinIndex,\n4,l.FLOAT,!1,0,0),l.bindBuffer(l.ARRAY_BUFFER,e.__webglSkinWeightsBuffer),g(b.skinWeight),l.vertexAttribPointer(b.skinWeight,4,l.FLOAT,!1,0,0));0<=b.lineDistance&&(l.bindBuffer(l.ARRAY_BUFFER,e.__webglLineDistanceBuffer),g(b.lineDistance),l.vertexAttribPointer(b.lineDistance,1,l.FLOAT,!1,0,0))}h();k instanceof THREE.Mesh?(k=e.__typeArray===Uint32Array?l.UNSIGNED_INT:l.UNSIGNED_SHORT,d.wireframe?(A(d.wireframeLinewidth),a&&l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,e.__webglLineBuffer),l.drawElements(l.LINES,\ne.__webglLineCount,k,0)):(a&&l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,e.__webglFaceBuffer),l.drawElements(l.TRIANGLES,e.__webglFaceCount,k,0)),J.info.render.calls++,J.info.render.vertices+=e.__webglFaceCount,J.info.render.faces+=e.__webglFaceCount/3):k instanceof THREE.Line?(k=k.mode===THREE.LineStrip?l.LINE_STRIP:l.LINES,A(d.linewidth),l.drawArrays(k,0,e.__webglLineCount),J.info.render.calls++):k instanceof THREE.PointCloud&&(l.drawArrays(l.POINTS,0,e.__webglParticleCount),J.info.render.calls++,J.info.render.points+=\ne.__webglParticleCount)}};this.render=function(a,b,c,d){if(!1===b instanceof THREE.Camera)console.error(\"THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.\");else{var e=a.fog;Kb=Oa=-1;ec=null;fc=!0;!0===a.autoUpdate&&a.updateMatrixWorld();void 0===b.parent&&b.updateMatrixWorld();a.traverse(function(a){a instanceof THREE.SkinnedMesh&&a.skeleton.update()});b.matrixWorldInverse.getInverse(b.matrixWorld);Ac.multiplyMatrices(b.projectionMatrix,b.matrixWorldInverse);Ec.setFromMatrix(Ac);\ncb.length=0;Jb.length=0;Ib.length=0;yb.length=0;Ra.length=0;q(a,a);!0===J.sortObjects&&(Jb.sort(k),Ib.sort(n));id.render(a,b);J.info.render.calls=0;J.info.render.vertices=0;J.info.render.faces=0;J.info.render.points=0;this.setRenderTarget(c);(this.autoClear||d)&&this.clear(this.autoClearColor,this.autoClearDepth,this.autoClearStencil);d=0;for(var f=jb.length;d<f;d++){var g=jb[d],h=g.object;h.visible&&(x(h,b),t(g))}a.overrideMaterial?(d=a.overrideMaterial,this.setBlending(d.blending,d.blendEquation,\nd.blendSrc,d.blendDst),this.setDepthTest(d.depthTest),this.setDepthWrite(d.depthWrite),B(d.polygonOffset,d.polygonOffsetFactor,d.polygonOffsetUnits),m(Jb,b,cb,e,!0,d),m(Ib,b,cb,e,!0,d),r(jb,\"\",b,cb,e,!1,d)):(d=null,this.setBlending(THREE.NoBlending),m(Jb,b,cb,e,!1,d),r(jb,\"opaque\",b,cb,e,!1,d),m(Ib,b,cb,e,!0,d),r(jb,\"transparent\",b,cb,e,!0,d));jd.render(a,b);kd.render(a,b,Uc,Vc);c&&c.generateMipmaps&&c.minFilter!==THREE.NearestFilter&&c.minFilter!==THREE.LinearFilter&&C(c);this.setDepthTest(!0);this.setDepthWrite(!0)}};\nthis.renderImmediateObject=function(a,b,c,d,e){var f=G(a,b,c,d,e);Oa=-1;J.setMaterialFaces(d);e.immediateRenderCallback?e.immediateRenderCallback(f,l,Ec):e.render(function(a){J.renderBufferImmediate(a,f,d)})};var xb={},rc=0;this.setFaceCulling=function(a,b){a===THREE.CullFaceNone?l.disable(l.CULL_FACE):(b===THREE.FrontFaceDirectionCW?l.frontFace(l.CW):l.frontFace(l.CCW),a===THREE.CullFaceBack?l.cullFace(l.BACK):a===THREE.CullFaceFront?l.cullFace(l.FRONT):l.cullFace(l.FRONT_AND_BACK),l.enable(l.CULL_FACE))};\nthis.setMaterialFaces=function(a){var b=a.side===THREE.DoubleSide;a=a.side===THREE.BackSide;Lb!==b&&(b?l.disable(l.CULL_FACE):l.enable(l.CULL_FACE),Lb=b);Mb!==a&&(a?l.frontFace(l.CW):l.frontFace(l.CCW),Mb=a)};this.setDepthTest=function(a){Yb!==a&&(a?l.enable(l.DEPTH_TEST):l.disable(l.DEPTH_TEST),Yb=a)};this.setDepthWrite=function(a){nb!==a&&(l.depthMask(a),nb=a)};this.setBlending=function(a,b,c,d){a!==pb&&(a===THREE.NoBlending?l.disable(l.BLEND):a===THREE.AdditiveBlending?(l.enable(l.BLEND),l.blendEquation(l.FUNC_ADD),\nl.blendFunc(l.SRC_ALPHA,l.ONE)):a===THREE.SubtractiveBlending?(l.enable(l.BLEND),l.blendEquation(l.FUNC_ADD),l.blendFunc(l.ZERO,l.ONE_MINUS_SRC_COLOR)):a===THREE.MultiplyBlending?(l.enable(l.BLEND),l.blendEquation(l.FUNC_ADD),l.blendFunc(l.ZERO,l.SRC_COLOR)):a===THREE.CustomBlending?l.enable(l.BLEND):(l.enable(l.BLEND),l.blendEquationSeparate(l.FUNC_ADD,l.FUNC_ADD),l.blendFuncSeparate(l.SRC_ALPHA,l.ONE_MINUS_SRC_ALPHA,l.ONE,l.ONE_MINUS_SRC_ALPHA)),pb=a);if(a===THREE.CustomBlending){if(b!==Nb&&(l.blendEquation(Q(b)),\nNb=b),c!==Ob||d!==Xb)l.blendFunc(Q(c),Q(d)),Ob=c,Xb=d}else Xb=Ob=Nb=null};this.uploadTexture=function(a){void 0===a.__webglInit&&(a.__webglInit=!0,a.addEventListener(\"dispose\",gc),a.__webglTexture=l.createTexture(),J.info.memory.textures++);l.bindTexture(l.TEXTURE_2D,a.__webglTexture);l.pixelStorei(l.UNPACK_FLIP_Y_WEBGL,a.flipY);l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha);l.pixelStorei(l.UNPACK_ALIGNMENT,a.unpackAlignment);a.image=R(a.image,cd);var b=a.image,c=THREE.Math.isPowerOfTwo(b.width)&&\nTHREE.Math.isPowerOfTwo(b.height),d=Q(a.format),e=Q(a.type);F(l.TEXTURE_2D,a,c);var f=a.mipmaps;if(a instanceof THREE.DataTexture)if(0<f.length&&c){for(var g=0,h=f.length;g<h;g++)b=f[g],l.texImage2D(l.TEXTURE_2D,g,d,b.width,b.height,0,d,e,b.data);a.generateMipmaps=!1}else l.texImage2D(l.TEXTURE_2D,0,d,b.width,b.height,0,d,e,b.data);else if(a instanceof THREE.CompressedTexture)for(g=0,h=f.length;g<h;g++)b=f[g],a.format!==THREE.RGBAFormat&&a.format!==THREE.RGBFormat?-1<Nc().indexOf(d)?l.compressedTexImage2D(l.TEXTURE_2D,\ng,d,b.width,b.height,0,b.data):console.warn(\"Attempt to load unsupported compressed texture format\"):l.texImage2D(l.TEXTURE_2D,g,d,b.width,b.height,0,d,e,b.data);else if(0<f.length&&c){g=0;for(h=f.length;g<h;g++)b=f[g],l.texImage2D(l.TEXTURE_2D,g,d,d,e,b);a.generateMipmaps=!1}else l.texImage2D(l.TEXTURE_2D,0,d,d,e,a.image);a.generateMipmaps&&c&&l.generateMipmap(l.TEXTURE_2D);a.needsUpdate=!1;if(a.onUpdate)a.onUpdate()};this.setTexture=function(a,b){l.activeTexture(l.TEXTURE0+b);a.needsUpdate?J.uploadTexture(a):\nl.bindTexture(l.TEXTURE_2D,a.__webglTexture)};this.setRenderTarget=function(a){var b=a instanceof THREE.WebGLRenderTargetCube;if(a&&void 0===a.__webglFramebuffer){void 0===a.depthBuffer&&(a.depthBuffer=!0);void 0===a.stencilBuffer&&(a.stencilBuffer=!0);a.addEventListener(\"dispose\",Zc);a.__webglTexture=l.createTexture();J.info.memory.textures++;var c=THREE.Math.isPowerOfTwo(a.width)&&THREE.Math.isPowerOfTwo(a.height),d=Q(a.format),e=Q(a.type);if(b){a.__webglFramebuffer=[];a.__webglRenderbuffer=[];\nl.bindTexture(l.TEXTURE_CUBE_MAP,a.__webglTexture);F(l.TEXTURE_CUBE_MAP,a,c);for(var f=0;6>f;f++){a.__webglFramebuffer[f]=l.createFramebuffer();a.__webglRenderbuffer[f]=l.createRenderbuffer();l.texImage2D(l.TEXTURE_CUBE_MAP_POSITIVE_X+f,0,d,a.width,a.height,0,d,e,null);var g=a,h=l.TEXTURE_CUBE_MAP_POSITIVE_X+f;l.bindFramebuffer(l.FRAMEBUFFER,a.__webglFramebuffer[f]);l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,h,g.__webglTexture,0);H(a.__webglRenderbuffer[f],a)}c&&l.generateMipmap(l.TEXTURE_CUBE_MAP)}else a.__webglFramebuffer=\nl.createFramebuffer(),a.__webglRenderbuffer=a.shareDepthFrom?a.shareDepthFrom.__webglRenderbuffer:l.createRenderbuffer(),l.bindTexture(l.TEXTURE_2D,a.__webglTexture),F(l.TEXTURE_2D,a,c),l.texImage2D(l.TEXTURE_2D,0,d,a.width,a.height,0,d,e,null),d=l.TEXTURE_2D,l.bindFramebuffer(l.FRAMEBUFFER,a.__webglFramebuffer),l.framebufferTexture2D(l.FRAMEBUFFER,l.COLOR_ATTACHMENT0,d,a.__webglTexture,0),a.shareDepthFrom?a.depthBuffer&&!a.stencilBuffer?l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_ATTACHMENT,\nl.RENDERBUFFER,a.__webglRenderbuffer):a.depthBuffer&&a.stencilBuffer&&l.framebufferRenderbuffer(l.FRAMEBUFFER,l.DEPTH_STENCIL_ATTACHMENT,l.RENDERBUFFER,a.__webglRenderbuffer):H(a.__webglRenderbuffer,a),c&&l.generateMipmap(l.TEXTURE_2D);b?l.bindTexture(l.TEXTURE_CUBE_MAP,null):l.bindTexture(l.TEXTURE_2D,null);l.bindRenderbuffer(l.RENDERBUFFER,null);l.bindFramebuffer(l.FRAMEBUFFER,null)}a?(b=b?a.__webglFramebuffer[a.activeCubeFace]:a.__webglFramebuffer,c=a.width,a=a.height,e=d=0):(b=null,c=lc,a=mc,\nd=Pb,e=kc);b!==Tc&&(l.bindFramebuffer(l.FRAMEBUFFER,b),l.viewport(d,e,c,a),Tc=b);Uc=c;Vc=a};this.initMaterial=function(){console.warn(\"THREE.WebGLRenderer: .initMaterial() has been removed.\")};this.addPrePlugin=function(){console.warn(\"THREE.WebGLRenderer: .addPrePlugin() has been removed.\")};this.addPostPlugin=function(){console.warn(\"THREE.WebGLRenderer: .addPostPlugin() has been removed.\")};this.updateShadowMap=function(){console.warn(\"THREE.WebGLRenderer: .updateShadowMap() has been removed.\")}};\nTHREE.WebGLRenderTarget=function(a,b,c){this.width=a;this.height=b;c=c||{};this.wrapS=void 0!==c.wrapS?c.wrapS:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==c.wrapT?c.wrapT:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==c.magFilter?c.magFilter:THREE.LinearFilter;this.minFilter=void 0!==c.minFilter?c.minFilter:THREE.LinearMipMapLinearFilter;this.anisotropy=void 0!==c.anisotropy?c.anisotropy:1;this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1);this.format=void 0!==c.format?c.format:\nTHREE.RGBAFormat;this.type=void 0!==c.type?c.type:THREE.UnsignedByteType;this.depthBuffer=void 0!==c.depthBuffer?c.depthBuffer:!0;this.stencilBuffer=void 0!==c.stencilBuffer?c.stencilBuffer:!0;this.generateMipmaps=!0;this.shareDepthFrom=null};\nTHREE.WebGLRenderTarget.prototype={constructor:THREE.WebGLRenderTarget,setSize:function(a,b){this.width=a;this.height=b},clone:function(){var a=new THREE.WebGLRenderTarget(this.width,this.height);a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.format=this.format;a.type=this.type;a.depthBuffer=this.depthBuffer;a.stencilBuffer=this.stencilBuffer;a.generateMipmaps=this.generateMipmaps;\na.shareDepthFrom=this.shareDepthFrom;return a},dispose:function(){this.dispatchEvent({type:\"dispose\"})}};THREE.EventDispatcher.prototype.apply(THREE.WebGLRenderTarget.prototype);THREE.WebGLRenderTargetCube=function(a,b,c){THREE.WebGLRenderTarget.call(this,a,b,c);this.activeCubeFace=0};THREE.WebGLRenderTargetCube.prototype=Object.create(THREE.WebGLRenderTarget.prototype);\nTHREE.WebGLExtensions=function(a){var b={};this.get=function(c){if(void 0!==b[c])return b[c];var d;switch(c){case \"OES_texture_float\":d=a.getExtension(\"OES_texture_float\");break;case \"OES_texture_float_linear\":d=a.getExtension(\"OES_texture_float_linear\");break;case \"OES_standard_derivatives\":d=a.getExtension(\"OES_standard_derivatives\");break;case \"EXT_texture_filter_anisotropic\":d=a.getExtension(\"EXT_texture_filter_anisotropic\")||a.getExtension(\"MOZ_EXT_texture_filter_anisotropic\")||a.getExtension(\"WEBKIT_EXT_texture_filter_anisotropic\");\nbreak;case \"WEBGL_compressed_texture_s3tc\":d=a.getExtension(\"WEBGL_compressed_texture_s3tc\")||a.getExtension(\"MOZ_WEBGL_compressed_texture_s3tc\")||a.getExtension(\"WEBKIT_WEBGL_compressed_texture_s3tc\");break;case \"WEBGL_compressed_texture_pvrtc\":d=a.getExtension(\"WEBGL_compressed_texture_pvrtc\")||a.getExtension(\"WEBKIT_WEBGL_compressed_texture_pvrtc\");break;case \"OES_element_index_uint\":d=a.getExtension(\"OES_element_index_uint\");break;case \"EXT_blend_minmax\":d=a.getExtension(\"EXT_blend_minmax\");break;\ncase \"EXT_frag_depth\":d=a.getExtension(\"EXT_frag_depth\")}null===d&&console.log(\"THREE.WebGLRenderer: \"+c+\" extension not supported.\");return b[c]=d}};\nTHREE.WebGLProgram=function(){var a=0;return function(b,c,d,e){var f=b.context,g=d.defines,h=d.__webglShader.uniforms,k=d.attributes,n=d.__webglShader.vertexShader,p=d.__webglShader.fragmentShader,q=d.index0AttributeName;void 0===q&&!0===e.morphTargets&&(q=\"position\");var m=\"SHADOWMAP_TYPE_BASIC\";e.shadowMapType===THREE.PCFShadowMap?m=\"SHADOWMAP_TYPE_PCF\":e.shadowMapType===THREE.PCFSoftShadowMap&&(m=\"SHADOWMAP_TYPE_PCF_SOFT\");var r,t;r=[];for(var s in g)t=g[s],!1!==t&&(t=\"#define \"+s+\" \"+t,r.push(t));\nr=r.join(\"\\n\");g=f.createProgram();d instanceof THREE.RawShaderMaterial?b=d=\"\":(d=[\"precision \"+e.precision+\" float;\",\"precision \"+e.precision+\" int;\",r,e.supportsVertexTextures?\"#define VERTEX_TEXTURES\":\"\",b.gammaInput?\"#define GAMMA_INPUT\":\"\",b.gammaOutput?\"#define GAMMA_OUTPUT\":\"\",\"#define MAX_DIR_LIGHTS \"+e.maxDirLights,\"#define MAX_POINT_LIGHTS \"+e.maxPointLights,\"#define MAX_SPOT_LIGHTS \"+e.maxSpotLights,\"#define MAX_HEMI_LIGHTS \"+e.maxHemiLights,\"#define MAX_SHADOWS \"+e.maxShadows,\"#define MAX_BONES \"+\ne.maxBones,e.map?\"#define USE_MAP\":\"\",e.envMap?\"#define USE_ENVMAP\":\"\",e.lightMap?\"#define USE_LIGHTMAP\":\"\",e.bumpMap?\"#define USE_BUMPMAP\":\"\",e.normalMap?\"#define USE_NORMALMAP\":\"\",e.specularMap?\"#define USE_SPECULARMAP\":\"\",e.alphaMap?\"#define USE_ALPHAMAP\":\"\",e.vertexColors?\"#define USE_COLOR\":\"\",e.skinning?\"#define USE_SKINNING\":\"\",e.useVertexTexture?\"#define BONE_TEXTURE\":\"\",e.morphTargets?\"#define USE_MORPHTARGETS\":\"\",e.morphNormals?\"#define USE_MORPHNORMALS\":\"\",e.wrapAround?\"#define WRAP_AROUND\":\n\"\",e.doubleSided?\"#define DOUBLE_SIDED\":\"\",e.flipSided?\"#define FLIP_SIDED\":\"\",e.shadowMapEnabled?\"#define USE_SHADOWMAP\":\"\",e.shadowMapEnabled?\"#define \"+m:\"\",e.shadowMapDebug?\"#define SHADOWMAP_DEBUG\":\"\",e.shadowMapCascade?\"#define SHADOWMAP_CASCADE\":\"\",e.sizeAttenuation?\"#define USE_SIZEATTENUATION\":\"\",e.logarithmicDepthBuffer?\"#define USE_LOGDEPTHBUF\":\"\",\"uniform mat4 modelMatrix;\\nuniform mat4 modelViewMatrix;\\nuniform mat4 projectionMatrix;\\nuniform mat4 viewMatrix;\\nuniform mat3 normalMatrix;\\nuniform vec3 cameraPosition;\\nattribute vec3 position;\\nattribute vec3 normal;\\nattribute vec2 uv;\\nattribute vec2 uv2;\\n#ifdef USE_COLOR\\n\\tattribute vec3 color;\\n#endif\\n#ifdef USE_MORPHTARGETS\\n\\tattribute vec3 morphTarget0;\\n\\tattribute vec3 morphTarget1;\\n\\tattribute vec3 morphTarget2;\\n\\tattribute vec3 morphTarget3;\\n\\t#ifdef USE_MORPHNORMALS\\n\\t\\tattribute vec3 morphNormal0;\\n\\t\\tattribute vec3 morphNormal1;\\n\\t\\tattribute vec3 morphNormal2;\\n\\t\\tattribute vec3 morphNormal3;\\n\\t#else\\n\\t\\tattribute vec3 morphTarget4;\\n\\t\\tattribute vec3 morphTarget5;\\n\\t\\tattribute vec3 morphTarget6;\\n\\t\\tattribute vec3 morphTarget7;\\n\\t#endif\\n#endif\\n#ifdef USE_SKINNING\\n\\tattribute vec4 skinIndex;\\n\\tattribute vec4 skinWeight;\\n#endif\\n\"].join(\"\\n\"),\nb=[\"precision \"+e.precision+\" float;\",\"precision \"+e.precision+\" int;\",e.bumpMap||e.normalMap?\"#extension GL_OES_standard_derivatives : enable\":\"\",r,\"#define MAX_DIR_LIGHTS \"+e.maxDirLights,\"#define MAX_POINT_LIGHTS \"+e.maxPointLights,\"#define MAX_SPOT_LIGHTS \"+e.maxSpotLights,\"#define MAX_HEMI_LIGHTS \"+e.maxHemiLights,\"#define MAX_SHADOWS \"+e.maxShadows,e.alphaTest?\"#define ALPHATEST \"+e.alphaTest:\"\",b.gammaInput?\"#define GAMMA_INPUT\":\"\",b.gammaOutput?\"#define GAMMA_OUTPUT\":\"\",e.useFog&&e.fog?\"#define USE_FOG\":\n\"\",e.useFog&&e.fogExp?\"#define FOG_EXP2\":\"\",e.map?\"#define USE_MAP\":\"\",e.envMap?\"#define USE_ENVMAP\":\"\",e.lightMap?\"#define USE_LIGHTMAP\":\"\",e.bumpMap?\"#define USE_BUMPMAP\":\"\",e.normalMap?\"#define USE_NORMALMAP\":\"\",e.specularMap?\"#define USE_SPECULARMAP\":\"\",e.alphaMap?\"#define USE_ALPHAMAP\":\"\",e.vertexColors?\"#define USE_COLOR\":\"\",e.metal?\"#define METAL\":\"\",e.wrapAround?\"#define WRAP_AROUND\":\"\",e.doubleSided?\"#define DOUBLE_SIDED\":\"\",e.flipSided?\"#define FLIP_SIDED\":\"\",e.shadowMapEnabled?\"#define USE_SHADOWMAP\":\n\"\",e.shadowMapEnabled?\"#define \"+m:\"\",e.shadowMapDebug?\"#define SHADOWMAP_DEBUG\":\"\",e.shadowMapCascade?\"#define SHADOWMAP_CASCADE\":\"\",e.logarithmicDepthBuffer?\"#define USE_LOGDEPTHBUF\":\"\",\"uniform mat4 viewMatrix;\\nuniform vec3 cameraPosition;\\n\"].join(\"\\n\"));n=new THREE.WebGLShader(f,f.VERTEX_SHADER,d+n);p=new THREE.WebGLShader(f,f.FRAGMENT_SHADER,b+p);f.attachShader(g,n);f.attachShader(g,p);void 0!==q&&f.bindAttribLocation(g,0,q);f.linkProgram(g);!1===f.getProgramParameter(g,f.LINK_STATUS)&&(console.error(\"THREE.WebGLProgram: Could not initialise shader.\"),\nconsole.error(\"gl.VALIDATE_STATUS\",f.getProgramParameter(g,f.VALIDATE_STATUS)),console.error(\"gl.getError()\",f.getError()));\"\"!==f.getProgramInfoLog(g)&&console.warn(\"THREE.WebGLProgram: gl.getProgramInfoLog()\",f.getProgramInfoLog(g));f.deleteShader(n);f.deleteShader(p);q=\"viewMatrix modelViewMatrix projectionMatrix normalMatrix modelMatrix cameraPosition morphTargetInfluences bindMatrix bindMatrixInverse\".split(\" \");e.useVertexTexture?(q.push(\"boneTexture\"),q.push(\"boneTextureWidth\"),q.push(\"boneTextureHeight\")):\nq.push(\"boneGlobalMatrices\");e.logarithmicDepthBuffer&&q.push(\"logDepthBufFC\");for(var u in h)q.push(u);h=q;u={};q=0;for(b=h.length;q<b;q++)m=h[q],u[m]=f.getUniformLocation(g,m);this.uniforms=u;q=\"position normal uv uv2 tangent color skinIndex skinWeight lineDistance\".split(\" \");for(h=0;h<e.maxMorphTargets;h++)q.push(\"morphTarget\"+h);for(h=0;h<e.maxMorphNormals;h++)q.push(\"morphNormal\"+h);for(var v in k)q.push(v);e=q;k={};v=0;for(h=e.length;v<h;v++)u=e[v],k[u]=f.getAttribLocation(g,u);this.attributes=\nk;this.attributesKeys=Object.keys(this.attributes);this.id=a++;this.code=c;this.usedTimes=1;this.program=g;this.vertexShader=n;this.fragmentShader=p;return this}}();\nTHREE.WebGLShader=function(){var a=function(a){a=a.split(\"\\n\");for(var c=0;c<a.length;c++)a[c]=c+1+\": \"+a[c];return a.join(\"\\n\")};return function(b,c,d){c=b.createShader(c);b.shaderSource(c,d);b.compileShader(c);!1===b.getShaderParameter(c,b.COMPILE_STATUS)&&console.error(\"THREE.WebGLShader: Shader couldn't compile.\");\"\"!==b.getShaderInfoLog(c)&&(console.warn(\"THREE.WebGLShader: gl.getShaderInfoLog()\",b.getShaderInfoLog(c)),console.warn(a(d)));return c}}();\nTHREE.LensFlarePlugin=function(a,b){var c,d,e,f,g,h,k,n,p,q,m=a.context,r,t,s,u,v,y;this.render=function(G,w,K,x){if(0!==b.length){G=new THREE.Vector3;var D=x/K,E=.5*K,A=.5*x,B=16/x,F=new THREE.Vector2(B*D,B),R=new THREE.Vector3(1,1,0),H=new THREE.Vector2(1,1);if(void 0===s){var B=new Float32Array([-1,-1,0,0,1,-1,1,0,1,1,1,1,-1,1,0,1]),C=new Uint16Array([0,1,2,0,2,3]);r=m.createBuffer();t=m.createBuffer();m.bindBuffer(m.ARRAY_BUFFER,r);m.bufferData(m.ARRAY_BUFFER,B,m.STATIC_DRAW);m.bindBuffer(m.ELEMENT_ARRAY_BUFFER,\nt);m.bufferData(m.ELEMENT_ARRAY_BUFFER,C,m.STATIC_DRAW);v=m.createTexture();y=m.createTexture();m.bindTexture(m.TEXTURE_2D,v);m.texImage2D(m.TEXTURE_2D,0,m.RGB,16,16,0,m.RGB,m.UNSIGNED_BYTE,null);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_S,m.CLAMP_TO_EDGE);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_T,m.CLAMP_TO_EDGE);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_MAG_FILTER,m.NEAREST);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_MIN_FILTER,m.NEAREST);m.bindTexture(m.TEXTURE_2D,y);m.texImage2D(m.TEXTURE_2D,0,\nm.RGBA,16,16,0,m.RGBA,m.UNSIGNED_BYTE,null);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_S,m.CLAMP_TO_EDGE);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_WRAP_T,m.CLAMP_TO_EDGE);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_MAG_FILTER,m.NEAREST);m.texParameteri(m.TEXTURE_2D,m.TEXTURE_MIN_FILTER,m.NEAREST);var B=(u=0<m.getParameter(m.MAX_VERTEX_TEXTURE_IMAGE_UNITS))?{vertexShader:\"uniform lowp int renderType;\\nuniform vec3 screenPosition;\\nuniform vec2 scale;\\nuniform float rotation;\\nuniform sampler2D occlusionMap;\\nattribute vec2 position;\\nattribute vec2 uv;\\nvarying vec2 vUV;\\nvarying float vVisibility;\\nvoid main() {\\nvUV = uv;\\nvec2 pos = position;\\nif( renderType == 2 ) {\\nvec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );\\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );\\nvVisibility =        visibility.r / 9.0;\\nvVisibility *= 1.0 - visibility.g / 9.0;\\nvVisibility *=       visibility.b / 9.0;\\nvVisibility *= 1.0 - visibility.a / 9.0;\\npos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;\\npos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;\\n}\\ngl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );\\n}\",\nfragmentShader:\"uniform lowp int renderType;\\nuniform sampler2D map;\\nuniform float opacity;\\nuniform vec3 color;\\nvarying vec2 vUV;\\nvarying float vVisibility;\\nvoid main() {\\nif( renderType == 0 ) {\\ngl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );\\n} else if( renderType == 1 ) {\\ngl_FragColor = texture2D( map, vUV );\\n} else {\\nvec4 texture = texture2D( map, vUV );\\ntexture.a *= opacity * vVisibility;\\ngl_FragColor = texture;\\ngl_FragColor.rgb *= color;\\n}\\n}\"}:{vertexShader:\"uniform lowp int renderType;\\nuniform vec3 screenPosition;\\nuniform vec2 scale;\\nuniform float rotation;\\nattribute vec2 position;\\nattribute vec2 uv;\\nvarying vec2 vUV;\\nvoid main() {\\nvUV = uv;\\nvec2 pos = position;\\nif( renderType == 2 ) {\\npos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;\\npos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;\\n}\\ngl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );\\n}\",\nfragmentShader:\"precision mediump float;\\nuniform lowp int renderType;\\nuniform sampler2D map;\\nuniform sampler2D occlusionMap;\\nuniform float opacity;\\nuniform vec3 color;\\nvarying vec2 vUV;\\nvoid main() {\\nif( renderType == 0 ) {\\ngl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );\\n} else if( renderType == 1 ) {\\ngl_FragColor = texture2D( map, vUV );\\n} else {\\nfloat visibility = texture2D( occlusionMap, vec2( 0.5, 0.1 ) ).a;\\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ).a;\\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ).a;\\nvisibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ).a;\\nvisibility = ( 1.0 - visibility / 4.0 );\\nvec4 texture = texture2D( map, vUV );\\ntexture.a *= opacity * visibility;\\ngl_FragColor = texture;\\ngl_FragColor.rgb *= color;\\n}\\n}\"},\nC=m.createProgram(),T=m.createShader(m.FRAGMENT_SHADER),Q=m.createShader(m.VERTEX_SHADER),O=\"precision \"+a.getPrecision()+\" float;\\n\";m.shaderSource(T,O+B.fragmentShader);m.shaderSource(Q,O+B.vertexShader);m.compileShader(T);m.compileShader(Q);m.attachShader(C,T);m.attachShader(C,Q);m.linkProgram(C);s=C;p=m.getAttribLocation(s,\"position\");q=m.getAttribLocation(s,\"uv\");c=m.getUniformLocation(s,\"renderType\");d=m.getUniformLocation(s,\"map\");e=m.getUniformLocation(s,\"occlusionMap\");f=m.getUniformLocation(s,\n\"opacity\");g=m.getUniformLocation(s,\"color\");h=m.getUniformLocation(s,\"scale\");k=m.getUniformLocation(s,\"rotation\");n=m.getUniformLocation(s,\"screenPosition\")}m.useProgram(s);m.enableVertexAttribArray(p);m.enableVertexAttribArray(q);m.uniform1i(e,0);m.uniform1i(d,1);m.bindBuffer(m.ARRAY_BUFFER,r);m.vertexAttribPointer(p,2,m.FLOAT,!1,16,0);m.vertexAttribPointer(q,2,m.FLOAT,!1,16,8);m.bindBuffer(m.ELEMENT_ARRAY_BUFFER,t);m.disable(m.CULL_FACE);m.depthMask(!1);C=0;for(T=b.length;C<T;C++)if(B=16/x,F.set(B*\nD,B),Q=b[C],G.set(Q.matrixWorld.elements[12],Q.matrixWorld.elements[13],Q.matrixWorld.elements[14]),G.applyMatrix4(w.matrixWorldInverse),G.applyProjection(w.projectionMatrix),R.copy(G),H.x=R.x*E+E,H.y=R.y*A+A,u||0<H.x&&H.x<K&&0<H.y&&H.y<x){m.activeTexture(m.TEXTURE1);m.bindTexture(m.TEXTURE_2D,v);m.copyTexImage2D(m.TEXTURE_2D,0,m.RGB,H.x-8,H.y-8,16,16,0);m.uniform1i(c,0);m.uniform2f(h,F.x,F.y);m.uniform3f(n,R.x,R.y,R.z);m.disable(m.BLEND);m.enable(m.DEPTH_TEST);m.drawElements(m.TRIANGLES,6,m.UNSIGNED_SHORT,\n0);m.activeTexture(m.TEXTURE0);m.bindTexture(m.TEXTURE_2D,y);m.copyTexImage2D(m.TEXTURE_2D,0,m.RGBA,H.x-8,H.y-8,16,16,0);m.uniform1i(c,1);m.disable(m.DEPTH_TEST);m.activeTexture(m.TEXTURE1);m.bindTexture(m.TEXTURE_2D,v);m.drawElements(m.TRIANGLES,6,m.UNSIGNED_SHORT,0);Q.positionScreen.copy(R);Q.customUpdateCallback?Q.customUpdateCallback(Q):Q.updateLensFlares();m.uniform1i(c,2);m.enable(m.BLEND);for(var O=0,S=Q.lensFlares.length;O<S;O++){var X=Q.lensFlares[O];.001<X.opacity&&.001<X.scale&&(R.x=X.x,\nR.y=X.y,R.z=X.z,B=X.size*X.scale/x,F.x=B*D,F.y=B,m.uniform3f(n,R.x,R.y,R.z),m.uniform2f(h,F.x,F.y),m.uniform1f(k,X.rotation),m.uniform1f(f,X.opacity),m.uniform3f(g,X.color.r,X.color.g,X.color.b),a.setBlending(X.blending,X.blendEquation,X.blendSrc,X.blendDst),a.setTexture(X.texture,1),m.drawElements(m.TRIANGLES,6,m.UNSIGNED_SHORT,0))}}m.enable(m.CULL_FACE);m.enable(m.DEPTH_TEST);m.depthMask(!0);a.resetGLState()}}};\nTHREE.ShadowMapPlugin=function(a,b,c,d){function e(a,b,d){if(b.visible){var f=c[b.id];if(f&&b.castShadow&&(!1===b.frustumCulled||!0===p.intersectsObject(b)))for(var g=0,h=f.length;g<h;g++){var k=f[g];b._modelViewMatrix.multiplyMatrices(d.matrixWorldInverse,b.matrixWorld);s.push(k)}g=0;for(h=b.children.length;g<h;g++)e(a,b.children[g],d)}}var f=a.context,g,h,k,n,p=new THREE.Frustum,q=new THREE.Matrix4,m=new THREE.Vector3,r=new THREE.Vector3,t=new THREE.Vector3,s=[],u=THREE.ShaderLib.depthRGBA,v=THREE.UniformsUtils.clone(u.uniforms);\ng=new THREE.ShaderMaterial({uniforms:v,vertexShader:u.vertexShader,fragmentShader:u.fragmentShader});h=new THREE.ShaderMaterial({uniforms:v,vertexShader:u.vertexShader,fragmentShader:u.fragmentShader,morphTargets:!0});k=new THREE.ShaderMaterial({uniforms:v,vertexShader:u.vertexShader,fragmentShader:u.fragmentShader,skinning:!0});n=new THREE.ShaderMaterial({uniforms:v,vertexShader:u.vertexShader,fragmentShader:u.fragmentShader,morphTargets:!0,skinning:!0});g._shadowPass=!0;h._shadowPass=!0;k._shadowPass=\n!0;n._shadowPass=!0;this.render=function(c,v){if(!1!==a.shadowMapEnabled){var u,K,x,D,E,A,B,F,R=[];D=0;f.clearColor(1,1,1,1);f.disable(f.BLEND);f.enable(f.CULL_FACE);f.frontFace(f.CCW);a.shadowMapCullFace===THREE.CullFaceFront?f.cullFace(f.FRONT):f.cullFace(f.BACK);a.setDepthTest(!0);u=0;for(K=b.length;u<K;u++)if(x=b[u],x.castShadow)if(x instanceof THREE.DirectionalLight&&x.shadowCascade)for(E=0;E<x.shadowCascadeCount;E++){var H;if(x.shadowCascadeArray[E])H=x.shadowCascadeArray[E];else{B=x;var C=\nE;H=new THREE.DirectionalLight;H.isVirtual=!0;H.onlyShadow=!0;H.castShadow=!0;H.shadowCameraNear=B.shadowCameraNear;H.shadowCameraFar=B.shadowCameraFar;H.shadowCameraLeft=B.shadowCameraLeft;H.shadowCameraRight=B.shadowCameraRight;H.shadowCameraBottom=B.shadowCameraBottom;H.shadowCameraTop=B.shadowCameraTop;H.shadowCameraVisible=B.shadowCameraVisible;H.shadowDarkness=B.shadowDarkness;H.shadowBias=B.shadowCascadeBias[C];H.shadowMapWidth=B.shadowCascadeWidth[C];H.shadowMapHeight=B.shadowCascadeHeight[C];\nH.pointsWorld=[];H.pointsFrustum=[];F=H.pointsWorld;A=H.pointsFrustum;for(var T=0;8>T;T++)F[T]=new THREE.Vector3,A[T]=new THREE.Vector3;F=B.shadowCascadeNearZ[C];B=B.shadowCascadeFarZ[C];A[0].set(-1,-1,F);A[1].set(1,-1,F);A[2].set(-1,1,F);A[3].set(1,1,F);A[4].set(-1,-1,B);A[5].set(1,-1,B);A[6].set(-1,1,B);A[7].set(1,1,B);H.originalCamera=v;A=new THREE.Gyroscope;A.position.copy(x.shadowCascadeOffset);A.add(H);A.add(H.target);v.add(A);x.shadowCascadeArray[E]=H;console.log(\"Created virtualLight\",H)}C=\nx;F=E;B=C.shadowCascadeArray[F];B.position.copy(C.position);B.target.position.copy(C.target.position);B.lookAt(B.target);B.shadowCameraVisible=C.shadowCameraVisible;B.shadowDarkness=C.shadowDarkness;B.shadowBias=C.shadowCascadeBias[F];A=C.shadowCascadeNearZ[F];C=C.shadowCascadeFarZ[F];B=B.pointsFrustum;B[0].z=A;B[1].z=A;B[2].z=A;B[3].z=A;B[4].z=C;B[5].z=C;B[6].z=C;B[7].z=C;R[D]=H;D++}else R[D]=x,D++;u=0;for(K=R.length;u<K;u++){x=R[u];x.shadowMap||(E=THREE.LinearFilter,a.shadowMapType===THREE.PCFSoftShadowMap&&\n(E=THREE.NearestFilter),x.shadowMap=new THREE.WebGLRenderTarget(x.shadowMapWidth,x.shadowMapHeight,{minFilter:E,magFilter:E,format:THREE.RGBAFormat}),x.shadowMapSize=new THREE.Vector2(x.shadowMapWidth,x.shadowMapHeight),x.shadowMatrix=new THREE.Matrix4);if(!x.shadowCamera){if(x instanceof THREE.SpotLight)x.shadowCamera=new THREE.PerspectiveCamera(x.shadowCameraFov,x.shadowMapWidth/x.shadowMapHeight,x.shadowCameraNear,x.shadowCameraFar);else if(x instanceof THREE.DirectionalLight)x.shadowCamera=new THREE.OrthographicCamera(x.shadowCameraLeft,\nx.shadowCameraRight,x.shadowCameraTop,x.shadowCameraBottom,x.shadowCameraNear,x.shadowCameraFar);else{console.error(\"Unsupported light type for shadow\");continue}c.add(x.shadowCamera);!0===c.autoUpdate&&c.updateMatrixWorld()}x.shadowCameraVisible&&!x.cameraHelper&&(x.cameraHelper=new THREE.CameraHelper(x.shadowCamera),c.add(x.cameraHelper));if(x.isVirtual&&H.originalCamera==v){E=v;D=x.shadowCamera;A=x.pointsFrustum;B=x.pointsWorld;m.set(Infinity,Infinity,Infinity);r.set(-Infinity,-Infinity,-Infinity);\nfor(C=0;8>C;C++)F=B[C],F.copy(A[C]),F.unproject(E),F.applyMatrix4(D.matrixWorldInverse),F.x<m.x&&(m.x=F.x),F.x>r.x&&(r.x=F.x),F.y<m.y&&(m.y=F.y),F.y>r.y&&(r.y=F.y),F.z<m.z&&(m.z=F.z),F.z>r.z&&(r.z=F.z);D.left=m.x;D.right=r.x;D.top=r.y;D.bottom=m.y;D.updateProjectionMatrix()}D=x.shadowMap;A=x.shadowMatrix;E=x.shadowCamera;E.position.setFromMatrixPosition(x.matrixWorld);t.setFromMatrixPosition(x.target.matrixWorld);E.lookAt(t);E.updateMatrixWorld();E.matrixWorldInverse.getInverse(E.matrixWorld);x.cameraHelper&&\n(x.cameraHelper.visible=x.shadowCameraVisible);x.shadowCameraVisible&&x.cameraHelper.update();A.set(.5,0,0,.5,0,.5,0,.5,0,0,.5,.5,0,0,0,1);A.multiply(E.projectionMatrix);A.multiply(E.matrixWorldInverse);q.multiplyMatrices(E.projectionMatrix,E.matrixWorldInverse);p.setFromMatrix(q);a.setRenderTarget(D);a.clear();s.length=0;e(c,c,E);x=0;for(D=s.length;x<D;x++)B=s[x],A=B.object,B=B.buffer,C=A.material instanceof THREE.MeshFaceMaterial?A.material.materials[0]:A.material,F=void 0!==A.geometry.morphTargets&&\n0<A.geometry.morphTargets.length&&C.morphTargets,T=A instanceof THREE.SkinnedMesh&&C.skinning,F=A.customDepthMaterial?A.customDepthMaterial:T?F?n:k:F?h:g,a.setMaterialFaces(C),B instanceof THREE.BufferGeometry?a.renderBufferDirect(E,b,null,F,B,A):a.renderBuffer(E,b,null,F,B,A);x=0;for(D=d.length;x<D;x++)B=d[x],A=B.object,A.visible&&A.castShadow&&(A._modelViewMatrix.multiplyMatrices(E.matrixWorldInverse,A.matrixWorld),a.renderImmediateObject(E,b,null,g,A))}u=a.getClearColor();K=a.getClearAlpha();f.clearColor(u.r,\nu.g,u.b,K);f.enable(f.BLEND);a.shadowMapCullFace===THREE.CullFaceFront&&f.cullFace(f.BACK);a.resetGLState()}}};\nTHREE.SpritePlugin=function(a,b){var c,d,e,f,g,h,k,n,p,q,m,r,t,s,u,v,y;function G(a,b){return a.z!==b.z?b.z-a.z:b.id-a.id}var w=a.context,K,x,D,E;this.render=function(A,B){if(0!==b.length){if(void 0===D){var F=new Float32Array([-.5,-.5,0,0,.5,-.5,1,0,.5,.5,1,1,-.5,.5,0,1]),R=new Uint16Array([0,1,2,0,2,3]);K=w.createBuffer();x=w.createBuffer();w.bindBuffer(w.ARRAY_BUFFER,K);w.bufferData(w.ARRAY_BUFFER,F,w.STATIC_DRAW);w.bindBuffer(w.ELEMENT_ARRAY_BUFFER,x);w.bufferData(w.ELEMENT_ARRAY_BUFFER,R,w.STATIC_DRAW);\nvar F=w.createProgram(),R=w.createShader(w.VERTEX_SHADER),H=w.createShader(w.FRAGMENT_SHADER);w.shaderSource(R,[\"precision \"+a.getPrecision()+\" float;\",\"uniform mat4 modelViewMatrix;\\nuniform mat4 projectionMatrix;\\nuniform float rotation;\\nuniform vec2 scale;\\nuniform vec2 uvOffset;\\nuniform vec2 uvScale;\\nattribute vec2 position;\\nattribute vec2 uv;\\nvarying vec2 vUV;\\nvoid main() {\\nvUV = uvOffset + uv * uvScale;\\nvec2 alignedPosition = position * scale;\\nvec2 rotatedPosition;\\nrotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\\nrotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\\nvec4 finalPosition;\\nfinalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\\nfinalPosition.xy += rotatedPosition;\\nfinalPosition = projectionMatrix * finalPosition;\\ngl_Position = finalPosition;\\n}\"].join(\"\\n\"));\nw.shaderSource(H,[\"precision \"+a.getPrecision()+\" float;\",\"uniform vec3 color;\\nuniform sampler2D map;\\nuniform float opacity;\\nuniform int fogType;\\nuniform vec3 fogColor;\\nuniform float fogDensity;\\nuniform float fogNear;\\nuniform float fogFar;\\nuniform float alphaTest;\\nvarying vec2 vUV;\\nvoid main() {\\nvec4 texture = texture2D( map, vUV );\\nif ( texture.a < alphaTest ) discard;\\ngl_FragColor = vec4( color * texture.xyz, texture.a * opacity );\\nif ( fogType > 0 ) {\\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\\nfloat fogFactor = 0.0;\\nif ( fogType == 1 ) {\\nfogFactor = smoothstep( fogNear, fogFar, depth );\\n} else {\\nconst float LOG2 = 1.442695;\\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\\n}\\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\\n}\\n}\"].join(\"\\n\"));\nw.compileShader(R);w.compileShader(H);w.attachShader(F,R);w.attachShader(F,H);w.linkProgram(F);D=F;v=w.getAttribLocation(D,\"position\");y=w.getAttribLocation(D,\"uv\");c=w.getUniformLocation(D,\"uvOffset\");d=w.getUniformLocation(D,\"uvScale\");e=w.getUniformLocation(D,\"rotation\");f=w.getUniformLocation(D,\"scale\");g=w.getUniformLocation(D,\"color\");h=w.getUniformLocation(D,\"map\");k=w.getUniformLocation(D,\"opacity\");n=w.getUniformLocation(D,\"modelViewMatrix\");p=w.getUniformLocation(D,\"projectionMatrix\");q=\nw.getUniformLocation(D,\"fogType\");m=w.getUniformLocation(D,\"fogDensity\");r=w.getUniformLocation(D,\"fogNear\");t=w.getUniformLocation(D,\"fogFar\");s=w.getUniformLocation(D,\"fogColor\");u=w.getUniformLocation(D,\"alphaTest\");F=document.createElement(\"canvas\");F.width=8;F.height=8;R=F.getContext(\"2d\");R.fillStyle=\"white\";R.fillRect(0,0,8,8);E=new THREE.Texture(F);E.needsUpdate=!0}w.useProgram(D);w.enableVertexAttribArray(v);w.enableVertexAttribArray(y);w.disable(w.CULL_FACE);w.enable(w.BLEND);w.bindBuffer(w.ARRAY_BUFFER,\nK);w.vertexAttribPointer(v,2,w.FLOAT,!1,16,0);w.vertexAttribPointer(y,2,w.FLOAT,!1,16,8);w.bindBuffer(w.ELEMENT_ARRAY_BUFFER,x);w.uniformMatrix4fv(p,!1,B.projectionMatrix.elements);w.activeTexture(w.TEXTURE0);w.uniform1i(h,0);R=F=0;(H=A.fog)?(w.uniform3f(s,H.color.r,H.color.g,H.color.b),H instanceof THREE.Fog?(w.uniform1f(r,H.near),w.uniform1f(t,H.far),w.uniform1i(q,1),R=F=1):H instanceof THREE.FogExp2&&(w.uniform1f(m,H.density),w.uniform1i(q,2),R=F=2)):(w.uniform1i(q,0),R=F=0);for(var H=0,C=b.length;H<\nC;H++){var T=b[H];T._modelViewMatrix.multiplyMatrices(B.matrixWorldInverse,T.matrixWorld);T.z=null===T.renderDepth?-T._modelViewMatrix.elements[14]:T.renderDepth}b.sort(G);for(var Q=[],H=0,C=b.length;H<C;H++){var T=b[H],O=T.material;w.uniform1f(u,O.alphaTest);w.uniformMatrix4fv(n,!1,T._modelViewMatrix.elements);Q[0]=T.scale.x;Q[1]=T.scale.y;T=0;A.fog&&O.fog&&(T=R);F!==T&&(w.uniform1i(q,T),F=T);null!==O.map?(w.uniform2f(c,O.map.offset.x,O.map.offset.y),w.uniform2f(d,O.map.repeat.x,O.map.repeat.y)):\n(w.uniform2f(c,0,0),w.uniform2f(d,1,1));w.uniform1f(k,O.opacity);w.uniform3f(g,O.color.r,O.color.g,O.color.b);w.uniform1f(e,O.rotation);w.uniform2fv(f,Q);a.setBlending(O.blending,O.blendEquation,O.blendSrc,O.blendDst);a.setDepthTest(O.depthTest);a.setDepthWrite(O.depthWrite);O.map&&O.map.image&&O.map.image.width?a.setTexture(O.map,0):a.setTexture(E,0);w.drawElements(w.TRIANGLES,6,w.UNSIGNED_SHORT,0)}w.enable(w.CULL_FACE);a.resetGLState()}}};\nTHREE.GeometryUtils={merge:function(a,b,c){console.warn(\"THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.\");var d;b instanceof THREE.Mesh&&(b.matrixAutoUpdate&&b.updateMatrix(),d=b.matrix,b=b.geometry);a.merge(b,d,c)},center:function(a){console.warn(\"THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.\");return a.center()}};\nTHREE.ImageUtils={crossOrigin:void 0,loadTexture:function(a,b,c,d){var e=new THREE.ImageLoader;e.crossOrigin=this.crossOrigin;var f=new THREE.Texture(void 0,b);e.load(a,function(a){f.image=a;f.needsUpdate=!0;c&&c(f)},void 0,function(a){d&&d(a)});f.sourceFile=a;return f},loadTextureCube:function(a,b,c,d){var e=new THREE.ImageLoader;e.crossOrigin=this.crossOrigin;var f=new THREE.CubeTexture([],b);f.flipY=!1;var g=0;b=function(b){e.load(a[b],function(a){f.images[b]=a;g+=1;6===g&&(f.needsUpdate=!0,c&&\nc(f))})};d=0;for(var h=a.length;d<h;++d)b(d);return f},loadCompressedTexture:function(){console.error(\"THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.\")},loadCompressedTextureCube:function(){console.error(\"THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.\")},getNormalMap:function(a,b){var c=function(a){var b=Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);return[a[0]/b,a[1]/b,a[2]/b]};b|=1;var d=a.width,e=a.height,f=document.createElement(\"canvas\");\nf.width=d;f.height=e;var g=f.getContext(\"2d\");g.drawImage(a,0,0);for(var h=g.getImageData(0,0,d,e).data,k=g.createImageData(d,e),n=k.data,p=0;p<d;p++)for(var q=0;q<e;q++){var m=0>q-1?0:q-1,r=q+1>e-1?e-1:q+1,t=0>p-1?0:p-1,s=p+1>d-1?d-1:p+1,u=[],v=[0,0,h[4*(q*d+p)]/255*b];u.push([-1,0,h[4*(q*d+t)]/255*b]);u.push([-1,-1,h[4*(m*d+t)]/255*b]);u.push([0,-1,h[4*(m*d+p)]/255*b]);u.push([1,-1,h[4*(m*d+s)]/255*b]);u.push([1,0,h[4*(q*d+s)]/255*b]);u.push([1,1,h[4*(r*d+s)]/255*b]);u.push([0,1,h[4*(r*d+p)]/255*\nb]);u.push([-1,1,h[4*(r*d+t)]/255*b]);m=[];t=u.length;for(r=0;r<t;r++){var s=u[r],y=u[(r+1)%t],s=[s[0]-v[0],s[1]-v[1],s[2]-v[2]],y=[y[0]-v[0],y[1]-v[1],y[2]-v[2]];m.push(c([s[1]*y[2]-s[2]*y[1],s[2]*y[0]-s[0]*y[2],s[0]*y[1]-s[1]*y[0]]))}u=[0,0,0];for(r=0;r<m.length;r++)u[0]+=m[r][0],u[1]+=m[r][1],u[2]+=m[r][2];u[0]/=m.length;u[1]/=m.length;u[2]/=m.length;v=4*(q*d+p);n[v]=(u[0]+1)/2*255|0;n[v+1]=(u[1]+1)/2*255|0;n[v+2]=255*u[2]|0;n[v+3]=255}g.putImageData(k,0,0);return f},generateDataTexture:function(a,\nb,c){var d=a*b,e=new Uint8Array(3*d),f=Math.floor(255*c.r),g=Math.floor(255*c.g);c=Math.floor(255*c.b);for(var h=0;h<d;h++)e[3*h]=f,e[3*h+1]=g,e[3*h+2]=c;a=new THREE.DataTexture(e,a,b,THREE.RGBFormat);a.needsUpdate=!0;return a}};\nTHREE.SceneUtils={createMultiMaterialObject:function(a,b){for(var c=new THREE.Object3D,d=0,e=b.length;d<e;d++)c.add(new THREE.Mesh(a,b[d]));return c},detach:function(a,b,c){a.applyMatrix(b.matrixWorld);b.remove(a);c.add(a)},attach:function(a,b,c){var d=new THREE.Matrix4;d.getInverse(c.matrixWorld);a.applyMatrix(d);b.remove(a);c.add(a)}};\nTHREE.FontUtils={faces:{},face:\"helvetiker\",weight:\"normal\",style:\"normal\",size:150,divisions:10,getFace:function(){try{return this.faces[this.face][this.weight][this.style]}catch(a){throw\"The font \"+this.face+\" with \"+this.weight+\" weight and \"+this.style+\" style is missing.\";}},loadFace:function(a){var b=a.familyName.toLowerCase();this.faces[b]=this.faces[b]||{};this.faces[b][a.cssFontWeight]=this.faces[b][a.cssFontWeight]||{};this.faces[b][a.cssFontWeight][a.cssFontStyle]=a;return this.faces[b][a.cssFontWeight][a.cssFontStyle]=\na},drawText:function(a){var b=this.getFace(),c=this.size/b.resolution,d=0,e=String(a).split(\"\"),f=e.length,g=[];for(a=0;a<f;a++){var h=new THREE.Path,h=this.extractGlyphPoints(e[a],b,c,d,h),d=d+h.offset;g.push(h.path)}return{paths:g,offset:d/2}},extractGlyphPoints:function(a,b,c,d,e){var f=[],g,h,k,n,p,q,m,r,t,s,u,v=b.glyphs[a]||b.glyphs[\"?\"];if(v){if(v.o)for(b=v._cachedOutline||(v._cachedOutline=v.o.split(\" \")),n=b.length,a=0;a<n;)switch(k=b[a++],k){case \"m\":k=b[a++]*c+d;p=b[a++]*c;e.moveTo(k,p);\nbreak;case \"l\":k=b[a++]*c+d;p=b[a++]*c;e.lineTo(k,p);break;case \"q\":k=b[a++]*c+d;p=b[a++]*c;r=b[a++]*c+d;t=b[a++]*c;e.quadraticCurveTo(r,t,k,p);if(g=f[f.length-1])for(q=g.x,m=g.y,g=1,h=this.divisions;g<=h;g++){var y=g/h;THREE.Shape.Utils.b2(y,q,r,k);THREE.Shape.Utils.b2(y,m,t,p)}break;case \"b\":if(k=b[a++]*c+d,p=b[a++]*c,r=b[a++]*c+d,t=b[a++]*c,s=b[a++]*c+d,u=b[a++]*c,e.bezierCurveTo(r,t,s,u,k,p),g=f[f.length-1])for(q=g.x,m=g.y,g=1,h=this.divisions;g<=h;g++)y=g/h,THREE.Shape.Utils.b3(y,q,r,s,k),THREE.Shape.Utils.b3(y,\nm,t,u,p)}return{offset:v.ha*c,path:e}}}};\nTHREE.FontUtils.generateShapes=function(a,b){b=b||{};var c=void 0!==b.curveSegments?b.curveSegments:4,d=void 0!==b.font?b.font:\"helvetiker\",e=void 0!==b.weight?b.weight:\"normal\",f=void 0!==b.style?b.style:\"normal\";THREE.FontUtils.size=void 0!==b.size?b.size:100;THREE.FontUtils.divisions=c;THREE.FontUtils.face=d;THREE.FontUtils.weight=e;THREE.FontUtils.style=f;c=THREE.FontUtils.drawText(a).paths;d=[];e=0;for(f=c.length;e<f;e++)Array.prototype.push.apply(d,c[e].toShapes());return d};\n(function(a){var b=function(a){for(var b=a.length,e=0,f=b-1,g=0;g<b;f=g++)e+=a[f].x*a[g].y-a[g].x*a[f].y;return.5*e};a.Triangulate=function(a,d){var e=a.length;if(3>e)return null;var f=[],g=[],h=[],k,n,p;if(0<b(a))for(n=0;n<e;n++)g[n]=n;else for(n=0;n<e;n++)g[n]=e-1-n;var q=2*e;for(n=e-1;2<e;){if(0>=q--){console.log(\"Warning, unable to triangulate polygon!\");break}k=n;e<=k&&(k=0);n=k+1;e<=n&&(n=0);p=n+1;e<=p&&(p=0);var m;a:{var r=m=void 0,t=void 0,s=void 0,u=void 0,v=void 0,y=void 0,G=void 0,w=void 0,\nr=a[g[k]].x,t=a[g[k]].y,s=a[g[n]].x,u=a[g[n]].y,v=a[g[p]].x,y=a[g[p]].y;if(1E-10>(s-r)*(y-t)-(u-t)*(v-r))m=!1;else{var K=void 0,x=void 0,D=void 0,E=void 0,A=void 0,B=void 0,F=void 0,R=void 0,H=void 0,C=void 0,H=R=F=w=G=void 0,K=v-s,x=y-u,D=r-v,E=t-y,A=s-r,B=u-t;for(m=0;m<e;m++)if(G=a[g[m]].x,w=a[g[m]].y,!(G===r&&w===t||G===s&&w===u||G===v&&w===y)&&(F=G-r,R=w-t,H=G-s,C=w-u,G-=v,w-=y,H=K*C-x*H,F=A*R-B*F,R=D*w-E*G,-1E-10<=H&&-1E-10<=R&&-1E-10<=F)){m=!1;break a}m=!0}}if(m){f.push([a[g[k]],a[g[n]],a[g[p]]]);\nh.push([g[k],g[n],g[p]]);k=n;for(p=n+1;p<e;k++,p++)g[k]=g[p];e--;q=2*e}}return d?h:f};a.Triangulate.area=b;return a})(THREE.FontUtils);self._typeface_js={faces:THREE.FontUtils.faces,loadFace:THREE.FontUtils.loadFace};THREE.typeface_js=self._typeface_js;\nTHREE.Audio=function(a){THREE.Object3D.call(this);this.type=\"Audio\";this.context=a.context;this.source=this.context.createBufferSource();this.gain=this.context.createGain();this.gain.connect(this.context.destination);this.panner=this.context.createPanner();this.panner.connect(this.gain)};THREE.Audio.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Audio.prototype.load=function(a){var b=this,c=new XMLHttpRequest;c.open(\"GET\",a,!0);c.responseType=\"arraybuffer\";c.onload=function(a){b.context.decodeAudioData(this.response,function(a){b.source.buffer=a;b.source.connect(b.panner);b.source.start(0)})};c.send();return this};THREE.Audio.prototype.setLoop=function(a){this.source.loop=a};THREE.Audio.prototype.setRefDistance=function(a){this.panner.refDistance=a};THREE.Audio.prototype.setRolloffFactor=function(a){this.panner.rolloffFactor=a};\nTHREE.Audio.prototype.updateMatrixWorld=function(){var a=new THREE.Vector3;return function(b){THREE.Object3D.prototype.updateMatrixWorld.call(this,b);a.setFromMatrixPosition(this.matrixWorld);this.panner.setPosition(a.x,a.y,a.z)}}();THREE.AudioListener=function(){THREE.Object3D.call(this);this.type=\"AudioListener\";this.context=new (window.AudioContext||window.webkitAudioContext)};THREE.AudioListener.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.AudioListener.prototype.updateMatrixWorld=function(){var a=new THREE.Vector3,b=new THREE.Quaternion,c=new THREE.Vector3,d=new THREE.Vector3,e=new THREE.Vector3,f=new THREE.Vector3;return function(g){THREE.Object3D.prototype.updateMatrixWorld.call(this,g);g=this.context.listener;this.matrixWorld.decompose(a,b,c);d.set(0,0,-1).applyQuaternion(b);e.subVectors(a,f);g.setPosition(a.x,a.y,a.z);g.setOrientation(d.x,d.y,d.z,this.up.x,this.up.y,this.up.z);g.setVelocity(e.x,e.y,e.z);f.copy(a)}}();\nTHREE.Curve=function(){};THREE.Curve.prototype.getPoint=function(a){console.log(\"Warning, getPoint() not implemented!\");return null};THREE.Curve.prototype.getPointAt=function(a){a=this.getUtoTmapping(a);return this.getPoint(a)};THREE.Curve.prototype.getPoints=function(a){a||(a=5);var b,c=[];for(b=0;b<=a;b++)c.push(this.getPoint(b/a));return c};THREE.Curve.prototype.getSpacedPoints=function(a){a||(a=5);var b,c=[];for(b=0;b<=a;b++)c.push(this.getPointAt(b/a));return c};\nTHREE.Curve.prototype.getLength=function(){var a=this.getLengths();return a[a.length-1]};THREE.Curve.prototype.getLengths=function(a){a||(a=this.__arcLengthDivisions?this.__arcLengthDivisions:200);if(this.cacheArcLengths&&this.cacheArcLengths.length==a+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;var b=[],c,d=this.getPoint(0),e,f=0;b.push(0);for(e=1;e<=a;e++)c=this.getPoint(e/a),f+=c.distanceTo(d),b.push(f),d=c;return this.cacheArcLengths=b};\nTHREE.Curve.prototype.updateArcLengths=function(){this.needsUpdate=!0;this.getLengths()};THREE.Curve.prototype.getUtoTmapping=function(a,b){var c=this.getLengths(),d=0,e=c.length,f;f=b?b:a*c[e-1];for(var g=0,h=e-1,k;g<=h;)if(d=Math.floor(g+(h-g)/2),k=c[d]-f,0>k)g=d+1;else if(0<k)h=d-1;else{h=d;break}d=h;if(c[d]==f)return d/(e-1);g=c[d];return c=(d+(f-g)/(c[d+1]-g))/(e-1)};THREE.Curve.prototype.getTangent=function(a){var b=a-1E-4;a+=1E-4;0>b&&(b=0);1<a&&(a=1);b=this.getPoint(b);return this.getPoint(a).clone().sub(b).normalize()};\nTHREE.Curve.prototype.getTangentAt=function(a){a=this.getUtoTmapping(a);return this.getTangent(a)};\nTHREE.Curve.Utils={tangentQuadraticBezier:function(a,b,c,d){return 2*(1-a)*(c-b)+2*a*(d-c)},tangentCubicBezier:function(a,b,c,d,e){return-3*b*(1-a)*(1-a)+3*c*(1-a)*(1-a)-6*a*c*(1-a)+6*a*d*(1-a)-3*a*a*d+3*a*a*e},tangentSpline:function(a,b,c,d,e){return 6*a*a-6*a+(3*a*a-4*a+1)+(-6*a*a+6*a)+(3*a*a-2*a)},interpolate:function(a,b,c,d,e){a=.5*(c-a);d=.5*(d-b);var f=e*e;return(2*b-2*c+a+d)*e*f+(-3*b+3*c-2*a-d)*f+a*e+b}};\nTHREE.Curve.create=function(a,b){a.prototype=Object.create(THREE.Curve.prototype);a.prototype.getPoint=b;return a};THREE.CurvePath=function(){this.curves=[];this.bends=[];this.autoClose=!1};THREE.CurvePath.prototype=Object.create(THREE.Curve.prototype);THREE.CurvePath.prototype.add=function(a){this.curves.push(a)};THREE.CurvePath.prototype.checkConnection=function(){};\nTHREE.CurvePath.prototype.closePath=function(){var a=this.curves[0].getPoint(0),b=this.curves[this.curves.length-1].getPoint(1);a.equals(b)||this.curves.push(new THREE.LineCurve(b,a))};THREE.CurvePath.prototype.getPoint=function(a){var b=a*this.getLength(),c=this.getCurveLengths();for(a=0;a<c.length;){if(c[a]>=b)return b=c[a]-b,a=this.curves[a],b=1-b/a.getLength(),a.getPointAt(b);a++}return null};THREE.CurvePath.prototype.getLength=function(){var a=this.getCurveLengths();return a[a.length-1]};\nTHREE.CurvePath.prototype.getCurveLengths=function(){if(this.cacheLengths&&this.cacheLengths.length==this.curves.length)return this.cacheLengths;var a=[],b=0,c,d=this.curves.length;for(c=0;c<d;c++)b+=this.curves[c].getLength(),a.push(b);return this.cacheLengths=a};\nTHREE.CurvePath.prototype.getBoundingBox=function(){var a=this.getPoints(),b,c,d,e,f,g;b=c=Number.NEGATIVE_INFINITY;e=f=Number.POSITIVE_INFINITY;var h,k,n,p,q=a[0]instanceof THREE.Vector3;p=q?new THREE.Vector3:new THREE.Vector2;k=0;for(n=a.length;k<n;k++)h=a[k],h.x>b?b=h.x:h.x<e&&(e=h.x),h.y>c?c=h.y:h.y<f&&(f=h.y),q&&(h.z>d?d=h.z:h.z<g&&(g=h.z)),p.add(h);a={minX:e,minY:f,maxX:b,maxY:c};q&&(a.maxZ=d,a.minZ=g);return a};\nTHREE.CurvePath.prototype.createPointsGeometry=function(a){a=this.getPoints(a,!0);return this.createGeometry(a)};THREE.CurvePath.prototype.createSpacedPointsGeometry=function(a){a=this.getSpacedPoints(a,!0);return this.createGeometry(a)};THREE.CurvePath.prototype.createGeometry=function(a){for(var b=new THREE.Geometry,c=0;c<a.length;c++)b.vertices.push(new THREE.Vector3(a[c].x,a[c].y,a[c].z||0));return b};THREE.CurvePath.prototype.addWrapPath=function(a){this.bends.push(a)};\nTHREE.CurvePath.prototype.getTransformedPoints=function(a,b){var c=this.getPoints(a),d,e;b||(b=this.bends);d=0;for(e=b.length;d<e;d++)c=this.getWrapPoints(c,b[d]);return c};THREE.CurvePath.prototype.getTransformedSpacedPoints=function(a,b){var c=this.getSpacedPoints(a),d,e;b||(b=this.bends);d=0;for(e=b.length;d<e;d++)c=this.getWrapPoints(c,b[d]);return c};\nTHREE.CurvePath.prototype.getWrapPoints=function(a,b){var c=this.getBoundingBox(),d,e,f,g,h,k;d=0;for(e=a.length;d<e;d++)f=a[d],g=f.x,h=f.y,k=g/c.maxX,k=b.getUtoTmapping(k,g),g=b.getPoint(k),k=b.getTangent(k),k.set(-k.y,k.x).multiplyScalar(h),f.x=g.x+k.x,f.y=g.y+k.y;return a};THREE.Gyroscope=function(){THREE.Object3D.call(this)};THREE.Gyroscope.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.Gyroscope.prototype.updateMatrixWorld=function(){var a=new THREE.Vector3,b=new THREE.Quaternion,c=new THREE.Vector3,d=new THREE.Vector3,e=new THREE.Quaternion,f=new THREE.Vector3;return function(g){this.matrixAutoUpdate&&this.updateMatrix();if(this.matrixWorldNeedsUpdate||g)this.parent?(this.matrixWorld.multiplyMatrices(this.parent.matrixWorld,this.matrix),this.matrixWorld.decompose(d,e,f),this.matrix.decompose(a,b,c),this.matrixWorld.compose(d,b,f)):this.matrixWorld.copy(this.matrix),this.matrixWorldNeedsUpdate=\n!1,g=!0;for(var h=0,k=this.children.length;h<k;h++)this.children[h].updateMatrixWorld(g)}}();THREE.Path=function(a){THREE.CurvePath.call(this);this.actions=[];a&&this.fromPoints(a)};THREE.Path.prototype=Object.create(THREE.CurvePath.prototype);THREE.PathActions={MOVE_TO:\"moveTo\",LINE_TO:\"lineTo\",QUADRATIC_CURVE_TO:\"quadraticCurveTo\",BEZIER_CURVE_TO:\"bezierCurveTo\",CSPLINE_THRU:\"splineThru\",ARC:\"arc\",ELLIPSE:\"ellipse\"};\nTHREE.Path.prototype.fromPoints=function(a){this.moveTo(a[0].x,a[0].y);for(var b=1,c=a.length;b<c;b++)this.lineTo(a[b].x,a[b].y)};THREE.Path.prototype.moveTo=function(a,b){var c=Array.prototype.slice.call(arguments);this.actions.push({action:THREE.PathActions.MOVE_TO,args:c})};\nTHREE.Path.prototype.lineTo=function(a,b){var c=Array.prototype.slice.call(arguments),d=this.actions[this.actions.length-1].args,d=new THREE.LineCurve(new THREE.Vector2(d[d.length-2],d[d.length-1]),new THREE.Vector2(a,b));this.curves.push(d);this.actions.push({action:THREE.PathActions.LINE_TO,args:c})};\nTHREE.Path.prototype.quadraticCurveTo=function(a,b,c,d){var e=Array.prototype.slice.call(arguments),f=this.actions[this.actions.length-1].args,f=new THREE.QuadraticBezierCurve(new THREE.Vector2(f[f.length-2],f[f.length-1]),new THREE.Vector2(a,b),new THREE.Vector2(c,d));this.curves.push(f);this.actions.push({action:THREE.PathActions.QUADRATIC_CURVE_TO,args:e})};\nTHREE.Path.prototype.bezierCurveTo=function(a,b,c,d,e,f){var g=Array.prototype.slice.call(arguments),h=this.actions[this.actions.length-1].args,h=new THREE.CubicBezierCurve(new THREE.Vector2(h[h.length-2],h[h.length-1]),new THREE.Vector2(a,b),new THREE.Vector2(c,d),new THREE.Vector2(e,f));this.curves.push(h);this.actions.push({action:THREE.PathActions.BEZIER_CURVE_TO,args:g})};\nTHREE.Path.prototype.splineThru=function(a){var b=Array.prototype.slice.call(arguments),c=this.actions[this.actions.length-1].args,c=[new THREE.Vector2(c[c.length-2],c[c.length-1])];Array.prototype.push.apply(c,a);c=new THREE.SplineCurve(c);this.curves.push(c);this.actions.push({action:THREE.PathActions.CSPLINE_THRU,args:b})};THREE.Path.prototype.arc=function(a,b,c,d,e,f){var g=this.actions[this.actions.length-1].args;this.absarc(a+g[g.length-2],b+g[g.length-1],c,d,e,f)};\nTHREE.Path.prototype.absarc=function(a,b,c,d,e,f){this.absellipse(a,b,c,c,d,e,f)};THREE.Path.prototype.ellipse=function(a,b,c,d,e,f,g){var h=this.actions[this.actions.length-1].args;this.absellipse(a+h[h.length-2],b+h[h.length-1],c,d,e,f,g)};THREE.Path.prototype.absellipse=function(a,b,c,d,e,f,g){var h=Array.prototype.slice.call(arguments),k=new THREE.EllipseCurve(a,b,c,d,e,f,g);this.curves.push(k);k=k.getPoint(1);h.push(k.x);h.push(k.y);this.actions.push({action:THREE.PathActions.ELLIPSE,args:h})};\nTHREE.Path.prototype.getSpacedPoints=function(a,b){a||(a=40);for(var c=[],d=0;d<a;d++)c.push(this.getPoint(d/a));return c};\nTHREE.Path.prototype.getPoints=function(a,b){if(this.useSpacedPoints)return console.log(\"tata\"),this.getSpacedPoints(a,b);a=a||12;var c=[],d,e,f,g,h,k,n,p,q,m,r,t,s;d=0;for(e=this.actions.length;d<e;d++)switch(f=this.actions[d],g=f.action,f=f.args,g){case THREE.PathActions.MOVE_TO:c.push(new THREE.Vector2(f[0],f[1]));break;case THREE.PathActions.LINE_TO:c.push(new THREE.Vector2(f[0],f[1]));break;case THREE.PathActions.QUADRATIC_CURVE_TO:h=f[2];k=f[3];q=f[0];m=f[1];0<c.length?(g=c[c.length-1],r=g.x,\nt=g.y):(g=this.actions[d-1].args,r=g[g.length-2],t=g[g.length-1]);for(f=1;f<=a;f++)s=f/a,g=THREE.Shape.Utils.b2(s,r,q,h),s=THREE.Shape.Utils.b2(s,t,m,k),c.push(new THREE.Vector2(g,s));break;case THREE.PathActions.BEZIER_CURVE_TO:h=f[4];k=f[5];q=f[0];m=f[1];n=f[2];p=f[3];0<c.length?(g=c[c.length-1],r=g.x,t=g.y):(g=this.actions[d-1].args,r=g[g.length-2],t=g[g.length-1]);for(f=1;f<=a;f++)s=f/a,g=THREE.Shape.Utils.b3(s,r,q,n,h),s=THREE.Shape.Utils.b3(s,t,m,p,k),c.push(new THREE.Vector2(g,s));break;case THREE.PathActions.CSPLINE_THRU:g=\nthis.actions[d-1].args;s=[new THREE.Vector2(g[g.length-2],g[g.length-1])];g=a*f[0].length;s=s.concat(f[0]);s=new THREE.SplineCurve(s);for(f=1;f<=g;f++)c.push(s.getPointAt(f/g));break;case THREE.PathActions.ARC:h=f[0];k=f[1];m=f[2];n=f[3];g=f[4];q=!!f[5];r=g-n;t=2*a;for(f=1;f<=t;f++)s=f/t,q||(s=1-s),s=n+s*r,g=h+m*Math.cos(s),s=k+m*Math.sin(s),c.push(new THREE.Vector2(g,s));break;case THREE.PathActions.ELLIPSE:for(h=f[0],k=f[1],m=f[2],p=f[3],n=f[4],g=f[5],q=!!f[6],r=g-n,t=2*a,f=1;f<=t;f++)s=f/t,q||\n(s=1-s),s=n+s*r,g=h+m*Math.cos(s),s=k+p*Math.sin(s),c.push(new THREE.Vector2(g,s))}d=c[c.length-1];1E-10>Math.abs(d.x-c[0].x)&&1E-10>Math.abs(d.y-c[0].y)&&c.splice(c.length-1,1);b&&c.push(c[0]);return c};\nTHREE.Path.prototype.toShapes=function(a,b){function c(a){for(var b=[],c=0,d=a.length;c<d;c++){var e=a[c],f=new THREE.Shape;f.actions=e.actions;f.curves=e.curves;b.push(f)}return b}function d(a,b){for(var c=b.length,d=!1,e=c-1,f=0;f<c;e=f++){var g=b[e],h=b[f],k=h.x-g.x,m=h.y-g.y;if(1E-10<Math.abs(m)){if(0>m&&(g=b[f],k=-k,h=b[e],m=-m),!(a.y<g.y||a.y>h.y))if(a.y==g.y){if(a.x==g.x)return!0}else{e=m*(a.x-g.x)-k*(a.y-g.y);if(0==e)return!0;0>e||(d=!d)}}else if(a.y==g.y&&(h.x<=a.x&&a.x<=g.x||g.x<=a.x&&a.x<=\nh.x))return!0}return d}var e=function(a){var b,c,d,e,f=[],g=new THREE.Path;b=0;for(c=a.length;b<c;b++)d=a[b],e=d.args,d=d.action,d==THREE.PathActions.MOVE_TO&&0!=g.actions.length&&(f.push(g),g=new THREE.Path),g[d].apply(g,e);0!=g.actions.length&&f.push(g);return f}(this.actions);if(0==e.length)return[];if(!0===b)return c(e);var f,g,h,k=[];if(1==e.length)return g=e[0],h=new THREE.Shape,h.actions=g.actions,h.curves=g.curves,k.push(h),k;var n=!THREE.Shape.Utils.isClockWise(e[0].getPoints()),n=a?!n:n;\nh=[];var p=[],q=[],m=0,r;p[m]=void 0;q[m]=[];var t,s;t=0;for(s=e.length;t<s;t++)g=e[t],r=g.getPoints(),f=THREE.Shape.Utils.isClockWise(r),(f=a?!f:f)?(!n&&p[m]&&m++,p[m]={s:new THREE.Shape,p:r},p[m].s.actions=g.actions,p[m].s.curves=g.curves,n&&m++,q[m]=[]):q[m].push({h:g,p:r[0]});if(!p[0])return c(e);if(1<p.length){t=!1;s=[];g=0;for(e=p.length;g<e;g++)h[g]=[];g=0;for(e=p.length;g<e;g++)for(f=q[g],n=0;n<f.length;n++){m=f[n];r=!0;for(var u=0;u<p.length;u++)d(m.p,p[u].p)&&(g!=u&&s.push({froms:g,tos:u,\nhole:n}),r?(r=!1,h[u].push(m)):t=!0);r&&h[g].push(m)}0<s.length&&(t||(q=h))}t=0;for(s=p.length;t<s;t++)for(h=p[t].s,k.push(h),g=q[t],e=0,f=g.length;e<f;e++)h.holes.push(g[e].h);return k};THREE.Shape=function(){THREE.Path.apply(this,arguments);this.holes=[]};THREE.Shape.prototype=Object.create(THREE.Path.prototype);THREE.Shape.prototype.extrude=function(a){return new THREE.ExtrudeGeometry(this,a)};THREE.Shape.prototype.makeGeometry=function(a){return new THREE.ShapeGeometry(this,a)};\nTHREE.Shape.prototype.getPointsHoles=function(a){var b,c=this.holes.length,d=[];for(b=0;b<c;b++)d[b]=this.holes[b].getTransformedPoints(a,this.bends);return d};THREE.Shape.prototype.getSpacedPointsHoles=function(a){var b,c=this.holes.length,d=[];for(b=0;b<c;b++)d[b]=this.holes[b].getTransformedSpacedPoints(a,this.bends);return d};THREE.Shape.prototype.extractAllPoints=function(a){return{shape:this.getTransformedPoints(a),holes:this.getPointsHoles(a)}};\nTHREE.Shape.prototype.extractPoints=function(a){return this.useSpacedPoints?this.extractAllSpacedPoints(a):this.extractAllPoints(a)};THREE.Shape.prototype.extractAllSpacedPoints=function(a){return{shape:this.getTransformedSpacedPoints(a),holes:this.getSpacedPointsHoles(a)}};\nTHREE.Shape.Utils={triangulateShape:function(a,b){function c(a,b,c){return a.x!=b.x?a.x<b.x?a.x<=c.x&&c.x<=b.x:b.x<=c.x&&c.x<=a.x:a.y<b.y?a.y<=c.y&&c.y<=b.y:b.y<=c.y&&c.y<=a.y}function d(a,b,d,e,f){var g=b.x-a.x,h=b.y-a.y,k=e.x-d.x,n=e.y-d.y,p=a.x-d.x,q=a.y-d.y,D=h*k-g*n,E=h*p-g*q;if(1E-10<Math.abs(D)){if(0<D){if(0>E||E>D)return[];k=n*p-k*q;if(0>k||k>D)return[]}else{if(0<E||E<D)return[];k=n*p-k*q;if(0<k||k<D)return[]}if(0==k)return!f||0!=E&&E!=D?[a]:[];if(k==D)return!f||0!=E&&E!=D?[b]:[];if(0==E)return[d];\nif(E==D)return[e];f=k/D;return[{x:a.x+f*g,y:a.y+f*h}]}if(0!=E||n*p!=k*q)return[];h=0==g&&0==h;k=0==k&&0==n;if(h&&k)return a.x!=d.x||a.y!=d.y?[]:[a];if(h)return c(d,e,a)?[a]:[];if(k)return c(a,b,d)?[d]:[];0!=g?(a.x<b.x?(g=a,k=a.x,h=b,a=b.x):(g=b,k=b.x,h=a,a=a.x),d.x<e.x?(b=d,D=d.x,n=e,d=e.x):(b=e,D=e.x,n=d,d=d.x)):(a.y<b.y?(g=a,k=a.y,h=b,a=b.y):(g=b,k=b.y,h=a,a=a.y),d.y<e.y?(b=d,D=d.y,n=e,d=e.y):(b=e,D=e.y,n=d,d=d.y));return k<=D?a<D?[]:a==D?f?[]:[b]:a<=d?[b,h]:[b,n]:k>d?[]:k==d?f?[]:[g]:a<=d?[g,h]:\n[g,n]}function e(a,b,c,d){var e=b.x-a.x,f=b.y-a.y;b=c.x-a.x;c=c.y-a.y;var g=d.x-a.x;d=d.y-a.y;a=e*c-f*b;e=e*d-f*g;return 1E-10<Math.abs(a)?(b=g*c-d*b,0<a?0<=e&&0<=b:0<=e||0<=b):0<e}var f,g,h,k,n,p={};h=a.concat();f=0;for(g=b.length;f<g;f++)Array.prototype.push.apply(h,b[f]);f=0;for(g=h.length;f<g;f++)n=h[f].x+\":\"+h[f].y,void 0!==p[n]&&console.log(\"Duplicate point\",n),p[n]=f;f=function(a,b){function c(a,b){var d=h.length-1,f=a-1;0>f&&(f=d);var g=a+1;g>d&&(g=0);d=e(h[a],h[f],h[g],k[b]);if(!d)return!1;\nd=k.length-1;f=b-1;0>f&&(f=d);g=b+1;g>d&&(g=0);return(d=e(k[b],k[f],k[g],h[a]))?!0:!1}function f(a,b){var c,e;for(c=0;c<h.length;c++)if(e=c+1,e%=h.length,e=d(a,b,h[c],h[e],!0),0<e.length)return!0;return!1}function g(a,c){var e,f,h,k;for(e=0;e<n.length;e++)for(f=b[n[e]],h=0;h<f.length;h++)if(k=h+1,k%=f.length,k=d(a,c,f[h],f[k],!0),0<k.length)return!0;return!1}var h=a.concat(),k,n=[],p,q,x,D,E,A=[],B,F,R,H=0;for(p=b.length;H<p;H++)n.push(H);B=0;for(var C=2*n.length;0<n.length;){C--;if(0>C){console.log(\"Infinite Loop! Holes left:\"+\nn.length+\", Probably Hole outside Shape!\");break}for(q=B;q<h.length;q++){x=h[q];p=-1;for(H=0;H<n.length;H++)if(D=n[H],E=x.x+\":\"+x.y+\":\"+D,void 0===A[E]){k=b[D];for(F=0;F<k.length;F++)if(D=k[F],c(q,F)&&!f(x,D)&&!g(x,D)){p=F;n.splice(H,1);B=h.slice(0,q+1);D=h.slice(q);F=k.slice(p);R=k.slice(0,p+1);h=B.concat(F).concat(R).concat(D);B=q;break}if(0<=p)break;A[E]=!0}if(0<=p)break}}return h}(a,b);var q=THREE.FontUtils.Triangulate(f,!1);f=0;for(g=q.length;f<g;f++)for(k=q[f],h=0;3>h;h++)n=k[h].x+\":\"+k[h].y,\nn=p[n],void 0!==n&&(k[h]=n);return q.concat()},isClockWise:function(a){return 0>THREE.FontUtils.Triangulate.area(a)},b2p0:function(a,b){var c=1-a;return c*c*b},b2p1:function(a,b){return 2*(1-a)*a*b},b2p2:function(a,b){return a*a*b},b2:function(a,b,c,d){return this.b2p0(a,b)+this.b2p1(a,c)+this.b2p2(a,d)},b3p0:function(a,b){var c=1-a;return c*c*c*b},b3p1:function(a,b){var c=1-a;return 3*c*c*a*b},b3p2:function(a,b){return 3*(1-a)*a*a*b},b3p3:function(a,b){return a*a*a*b},b3:function(a,b,c,d,e){return this.b3p0(a,\nb)+this.b3p1(a,c)+this.b3p2(a,d)+this.b3p3(a,e)}};THREE.LineCurve=function(a,b){this.v1=a;this.v2=b};THREE.LineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.LineCurve.prototype.getPoint=function(a){var b=this.v2.clone().sub(this.v1);b.multiplyScalar(a).add(this.v1);return b};THREE.LineCurve.prototype.getPointAt=function(a){return this.getPoint(a)};THREE.LineCurve.prototype.getTangent=function(a){return this.v2.clone().sub(this.v1).normalize()};\nTHREE.QuadraticBezierCurve=function(a,b,c){this.v0=a;this.v1=b;this.v2=c};THREE.QuadraticBezierCurve.prototype=Object.create(THREE.Curve.prototype);THREE.QuadraticBezierCurve.prototype.getPoint=function(a){var b=new THREE.Vector2;b.x=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);b.y=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);return b};\nTHREE.QuadraticBezierCurve.prototype.getTangent=function(a){var b=new THREE.Vector2;b.x=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.x,this.v1.x,this.v2.x);b.y=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.y,this.v1.y,this.v2.y);return b.normalize()};THREE.CubicBezierCurve=function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d};THREE.CubicBezierCurve.prototype=Object.create(THREE.Curve.prototype);\nTHREE.CubicBezierCurve.prototype.getPoint=function(a){var b;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);return new THREE.Vector2(b,a)};THREE.CubicBezierCurve.prototype.getTangent=function(a){var b;b=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);b=new THREE.Vector2(b,a);b.normalize();return b};\nTHREE.SplineCurve=function(a){this.points=void 0==a?[]:a};THREE.SplineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.SplineCurve.prototype.getPoint=function(a){var b=this.points;a*=b.length-1;var c=Math.floor(a);a-=c;var d=b[0==c?c:c-1],e=b[c],f=b[c>b.length-2?b.length-1:c+1],b=b[c>b.length-3?b.length-1:c+2],c=new THREE.Vector2;c.x=THREE.Curve.Utils.interpolate(d.x,e.x,f.x,b.x,a);c.y=THREE.Curve.Utils.interpolate(d.y,e.y,f.y,b.y,a);return c};\nTHREE.EllipseCurve=function(a,b,c,d,e,f,g){this.aX=a;this.aY=b;this.xRadius=c;this.yRadius=d;this.aStartAngle=e;this.aEndAngle=f;this.aClockwise=g};THREE.EllipseCurve.prototype=Object.create(THREE.Curve.prototype);\nTHREE.EllipseCurve.prototype.getPoint=function(a){var b=this.aEndAngle-this.aStartAngle;0>b&&(b+=2*Math.PI);b>2*Math.PI&&(b-=2*Math.PI);a=!0===this.aClockwise?this.aEndAngle+(1-a)*(2*Math.PI-b):this.aStartAngle+a*b;b=new THREE.Vector2;b.x=this.aX+this.xRadius*Math.cos(a);b.y=this.aY+this.yRadius*Math.sin(a);return b};THREE.ArcCurve=function(a,b,c,d,e,f){THREE.EllipseCurve.call(this,a,b,c,c,d,e,f)};THREE.ArcCurve.prototype=Object.create(THREE.EllipseCurve.prototype);\nTHREE.LineCurve3=THREE.Curve.create(function(a,b){this.v1=a;this.v2=b},function(a){var b=new THREE.Vector3;b.subVectors(this.v2,this.v1);b.multiplyScalar(a);b.add(this.v1);return b});THREE.QuadraticBezierCurve3=THREE.Curve.create(function(a,b,c){this.v0=a;this.v1=b;this.v2=c},function(a){var b=new THREE.Vector3;b.x=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);b.y=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);b.z=THREE.Shape.Utils.b2(a,this.v0.z,this.v1.z,this.v2.z);return b});\nTHREE.CubicBezierCurve3=THREE.Curve.create(function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d},function(a){var b=new THREE.Vector3;b.x=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);b.y=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);b.z=THREE.Shape.Utils.b3(a,this.v0.z,this.v1.z,this.v2.z,this.v3.z);return b});\nTHREE.SplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=this.points;a*=b.length-1;var c=Math.floor(a);a-=c;var d=b[0==c?c:c-1],e=b[c],f=b[c>b.length-2?b.length-1:c+1],b=b[c>b.length-3?b.length-1:c+2],c=new THREE.Vector3;c.x=THREE.Curve.Utils.interpolate(d.x,e.x,f.x,b.x,a);c.y=THREE.Curve.Utils.interpolate(d.y,e.y,f.y,b.y,a);c.z=THREE.Curve.Utils.interpolate(d.z,e.z,f.z,b.z,a);return c});\nTHREE.ClosedSplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=this.points;a*=b.length-0;var c=Math.floor(a);a-=c;var c=c+(0<c?0:(Math.floor(Math.abs(c)/b.length)+1)*b.length),d=b[(c-1)%b.length],e=b[c%b.length],f=b[(c+1)%b.length],b=b[(c+2)%b.length],c=new THREE.Vector3;c.x=THREE.Curve.Utils.interpolate(d.x,e.x,f.x,b.x,a);c.y=THREE.Curve.Utils.interpolate(d.y,e.y,f.y,b.y,a);c.z=THREE.Curve.Utils.interpolate(d.z,e.z,f.z,b.z,a);return c});\nTHREE.AnimationHandler={LINEAR:0,CATMULLROM:1,CATMULLROM_FORWARD:2,add:function(){console.warn(\"THREE.AnimationHandler.add() has been deprecated.\")},get:function(){console.warn(\"THREE.AnimationHandler.get() has been deprecated.\")},remove:function(){console.warn(\"THREE.AnimationHandler.remove() has been deprecated.\")},animations:[],init:function(a){if(!0!==a.initialized){for(var b=0;b<a.hierarchy.length;b++){for(var c=0;c<a.hierarchy[b].keys.length;c++)if(0>a.hierarchy[b].keys[c].time&&(a.hierarchy[b].keys[c].time=\n0),void 0!==a.hierarchy[b].keys[c].rot&&!(a.hierarchy[b].keys[c].rot instanceof THREE.Quaternion)){var d=a.hierarchy[b].keys[c].rot;a.hierarchy[b].keys[c].rot=(new THREE.Quaternion).fromArray(d)}if(a.hierarchy[b].keys.length&&void 0!==a.hierarchy[b].keys[0].morphTargets){d={};for(c=0;c<a.hierarchy[b].keys.length;c++)for(var e=0;e<a.hierarchy[b].keys[c].morphTargets.length;e++){var f=a.hierarchy[b].keys[c].morphTargets[e];d[f]=-1}a.hierarchy[b].usedMorphTargets=d;for(c=0;c<a.hierarchy[b].keys.length;c++){var g=\n{};for(f in d){for(e=0;e<a.hierarchy[b].keys[c].morphTargets.length;e++)if(a.hierarchy[b].keys[c].morphTargets[e]===f){g[f]=a.hierarchy[b].keys[c].morphTargetsInfluences[e];break}e===a.hierarchy[b].keys[c].morphTargets.length&&(g[f]=0)}a.hierarchy[b].keys[c].morphTargetsInfluences=g}}for(c=1;c<a.hierarchy[b].keys.length;c++)a.hierarchy[b].keys[c].time===a.hierarchy[b].keys[c-1].time&&(a.hierarchy[b].keys.splice(c,1),c--);for(c=0;c<a.hierarchy[b].keys.length;c++)a.hierarchy[b].keys[c].index=c}a.initialized=\n!0;return a}},parse:function(a){var b=function(a,c){c.push(a);for(var d=0;d<a.children.length;d++)b(a.children[d],c)},c=[];if(a instanceof THREE.SkinnedMesh)for(var d=0;d<a.skeleton.bones.length;d++)c.push(a.skeleton.bones[d]);else b(a,c);return c},play:function(a){-1===this.animations.indexOf(a)&&this.animations.push(a)},stop:function(a){a=this.animations.indexOf(a);-1!==a&&this.animations.splice(a,1)},update:function(a){for(var b=0;b<this.animations.length;b++)this.animations[b].resetBlendWeights();\nfor(b=0;b<this.animations.length;b++)this.animations[b].update(a)}};THREE.Animation=function(a,b){this.root=a;this.data=THREE.AnimationHandler.init(b);this.hierarchy=THREE.AnimationHandler.parse(a);this.currentTime=0;this.timeScale=1;this.isPlaying=!1;this.loop=!0;this.weight=0;this.interpolationType=THREE.AnimationHandler.LINEAR};THREE.Animation.prototype.keyTypes=[\"pos\",\"rot\",\"scl\"];\nTHREE.Animation.prototype.play=function(a,b){this.currentTime=void 0!==a?a:0;this.weight=void 0!==b?b:1;this.isPlaying=!0;this.reset();THREE.AnimationHandler.play(this)};THREE.Animation.prototype.stop=function(){this.isPlaying=!1;THREE.AnimationHandler.stop(this)};\nTHREE.Animation.prototype.reset=function(){for(var a=0,b=this.hierarchy.length;a<b;a++){var c=this.hierarchy[a];c.matrixAutoUpdate=!0;void 0===c.animationCache&&(c.animationCache={animations:{},blending:{positionWeight:0,quaternionWeight:0,scaleWeight:0}});void 0===c.animationCache.animations[this.data.name]&&(c.animationCache.animations[this.data.name]={},c.animationCache.animations[this.data.name].prevKey={pos:0,rot:0,scl:0},c.animationCache.animations[this.data.name].nextKey={pos:0,rot:0,scl:0},\nc.animationCache.animations[this.data.name].originalMatrix=c.matrix);for(var c=c.animationCache.animations[this.data.name],d=0;3>d;d++){for(var e=this.keyTypes[d],f=this.data.hierarchy[a].keys[0],g=this.getNextKeyWith(e,a,1);g.time<this.currentTime&&g.index>f.index;)f=g,g=this.getNextKeyWith(e,a,g.index+1);c.prevKey[e]=f;c.nextKey[e]=g}}};\nTHREE.Animation.prototype.resetBlendWeights=function(){for(var a=0,b=this.hierarchy.length;a<b;a++){var c=this.hierarchy[a];void 0!==c.animationCache&&(c.animationCache.blending.positionWeight=0,c.animationCache.blending.quaternionWeight=0,c.animationCache.blending.scaleWeight=0)}};\nTHREE.Animation.prototype.update=function(){var a=[],b=new THREE.Vector3,c=new THREE.Vector3,d=new THREE.Quaternion,e=function(a,b){var c=[],d=[],e,q,m,r,t,s;e=(a.length-1)*b;q=Math.floor(e);e-=q;c[0]=0===q?q:q-1;c[1]=q;c[2]=q>a.length-2?q:q+1;c[3]=q>a.length-3?q:q+2;q=a[c[0]];r=a[c[1]];t=a[c[2]];s=a[c[3]];c=e*e;m=e*c;d[0]=f(q[0],r[0],t[0],s[0],e,c,m);d[1]=f(q[1],r[1],t[1],s[1],e,c,m);d[2]=f(q[2],r[2],t[2],s[2],e,c,m);return d},f=function(a,b,c,d,e,f,m){a=.5*(c-a);d=.5*(d-b);return(2*(b-c)+a+d)*m+\n(-3*(b-c)-2*a-d)*f+a*e+b};return function(f){if(!1!==this.isPlaying&&(this.currentTime+=f*this.timeScale,0!==this.weight)){f=this.data.length;if(this.currentTime>f||0>this.currentTime)if(this.loop)this.currentTime%=f,0>this.currentTime&&(this.currentTime+=f),this.reset();else{this.stop();return}f=0;for(var h=this.hierarchy.length;f<h;f++)for(var k=this.hierarchy[f],n=k.animationCache.animations[this.data.name],p=k.animationCache.blending,q=0;3>q;q++){var m=this.keyTypes[q],r=n.prevKey[m],t=n.nextKey[m];\nif(0<this.timeScale&&t.time<=this.currentTime||0>this.timeScale&&r.time>=this.currentTime){r=this.data.hierarchy[f].keys[0];for(t=this.getNextKeyWith(m,f,1);t.time<this.currentTime&&t.index>r.index;)r=t,t=this.getNextKeyWith(m,f,t.index+1);n.prevKey[m]=r;n.nextKey[m]=t}k.matrixAutoUpdate=!0;k.matrixWorldNeedsUpdate=!0;var s=(this.currentTime-r.time)/(t.time-r.time),u=r[m],v=t[m];0>s&&(s=0);1<s&&(s=1);if(\"pos\"===m)if(this.interpolationType===THREE.AnimationHandler.LINEAR)c.x=u[0]+(v[0]-u[0])*s,c.y=\nu[1]+(v[1]-u[1])*s,c.z=u[2]+(v[2]-u[2])*s,r=this.weight/(this.weight+p.positionWeight),k.position.lerp(c,r),p.positionWeight+=this.weight;else{if(this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD)a[0]=this.getPrevKeyWith(\"pos\",f,r.index-1).pos,a[1]=u,a[2]=v,a[3]=this.getNextKeyWith(\"pos\",f,t.index+1).pos,s=.33*s+.33,t=e(a,s),r=this.weight/(this.weight+p.positionWeight),p.positionWeight+=this.weight,m=k.position,m.x+=(t[0]-\nm.x)*r,m.y+=(t[1]-m.y)*r,m.z+=(t[2]-m.z)*r,this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD&&(s=e(a,1.01*s),b.set(s[0],s[1],s[2]),b.sub(m),b.y=0,b.normalize(),s=Math.atan2(b.x,b.z),k.rotation.set(0,s,0))}else\"rot\"===m?(THREE.Quaternion.slerp(u,v,d,s),0===p.quaternionWeight?(k.quaternion.copy(d),p.quaternionWeight=this.weight):(r=this.weight/(this.weight+p.quaternionWeight),THREE.Quaternion.slerp(k.quaternion,d,k.quaternion,r),p.quaternionWeight+=this.weight)):\"scl\"===m&&(c.x=u[0]+\n(v[0]-u[0])*s,c.y=u[1]+(v[1]-u[1])*s,c.z=u[2]+(v[2]-u[2])*s,r=this.weight/(this.weight+p.scaleWeight),k.scale.lerp(c,r),p.scaleWeight+=this.weight)}return!0}}}();THREE.Animation.prototype.getNextKeyWith=function(a,b,c){var d=this.data.hierarchy[b].keys;for(c=this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD?c<d.length-1?c:d.length-1:c%d.length;c<d.length;c++)if(void 0!==d[c][a])return d[c];return this.data.hierarchy[b].keys[0]};\nTHREE.Animation.prototype.getPrevKeyWith=function(a,b,c){var d=this.data.hierarchy[b].keys;for(c=this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD?0<c?c:0:0<=c?c:c+d.length;0<=c;c--)if(void 0!==d[c][a])return d[c];return this.data.hierarchy[b].keys[d.length-1]};\nTHREE.KeyFrameAnimation=function(a){this.root=a.node;this.data=THREE.AnimationHandler.init(a);this.hierarchy=THREE.AnimationHandler.parse(this.root);this.currentTime=0;this.timeScale=.001;this.isPlaying=!1;this.loop=this.isPaused=!0;a=0;for(var b=this.hierarchy.length;a<b;a++){var c=this.data.hierarchy[a].sids,d=this.hierarchy[a];if(this.data.hierarchy[a].keys.length&&c){for(var e=0;e<c.length;e++){var f=c[e],g=this.getNextKeyWith(f,a,0);g&&g.apply(f)}d.matrixAutoUpdate=!1;this.data.hierarchy[a].node.updateMatrix();\nd.matrixWorldNeedsUpdate=!0}}};\nTHREE.KeyFrameAnimation.prototype.play=function(a){this.currentTime=void 0!==a?a:0;if(!1===this.isPlaying){this.isPlaying=!0;var b=this.hierarchy.length,c,d;for(a=0;a<b;a++)c=this.hierarchy[a],d=this.data.hierarchy[a],void 0===d.animationCache&&(d.animationCache={},d.animationCache.prevKey=null,d.animationCache.nextKey=null,d.animationCache.originalMatrix=c.matrix),c=this.data.hierarchy[a].keys,c.length&&(d.animationCache.prevKey=c[0],d.animationCache.nextKey=c[1],this.startTime=Math.min(c[0].time,\nthis.startTime),this.endTime=Math.max(c[c.length-1].time,this.endTime));this.update(0)}this.isPaused=!1;THREE.AnimationHandler.play(this)};THREE.KeyFrameAnimation.prototype.stop=function(){this.isPaused=this.isPlaying=!1;THREE.AnimationHandler.stop(this);for(var a=0;a<this.data.hierarchy.length;a++){var b=this.hierarchy[a],c=this.data.hierarchy[a];if(void 0!==c.animationCache){var d=c.animationCache.originalMatrix;d.copy(b.matrix);b.matrix=d;delete c.animationCache}}};\nTHREE.KeyFrameAnimation.prototype.update=function(a){if(!1!==this.isPlaying){this.currentTime+=a*this.timeScale;a=this.data.length;!0===this.loop&&this.currentTime>a&&(this.currentTime%=a);this.currentTime=Math.min(this.currentTime,a);a=0;for(var b=this.hierarchy.length;a<b;a++){var c=this.hierarchy[a],d=this.data.hierarchy[a],e=d.keys,d=d.animationCache;if(e.length){var f=d.prevKey,g=d.nextKey;if(g.time<=this.currentTime){for(;g.time<this.currentTime&&g.index>f.index;)f=g,g=e[f.index+1];d.prevKey=\nf;d.nextKey=g}g.time>=this.currentTime?f.interpolate(g,this.currentTime):f.interpolate(g,g.time);this.data.hierarchy[a].node.updateMatrix();c.matrixWorldNeedsUpdate=!0}}}};THREE.KeyFrameAnimation.prototype.getNextKeyWith=function(a,b,c){b=this.data.hierarchy[b].keys;for(c%=b.length;c<b.length;c++)if(b[c].hasTarget(a))return b[c];return b[0]};\nTHREE.KeyFrameAnimation.prototype.getPrevKeyWith=function(a,b,c){b=this.data.hierarchy[b].keys;for(c=0<=c?c:c+b.length;0<=c;c--)if(b[c].hasTarget(a))return b[c];return b[b.length-1]};THREE.MorphAnimation=function(a){this.mesh=a;this.frames=a.morphTargetInfluences.length;this.currentTime=0;this.duration=1E3;this.loop=!0;this.isPlaying=!1};\nTHREE.MorphAnimation.prototype={play:function(){this.isPlaying=!0},pause:function(){this.isPlaying=!1},update:function(){var a=0,b=0;return function(c){if(!1!==this.isPlaying){this.currentTime+=c;!0===this.loop&&this.currentTime>this.duration&&(this.currentTime%=this.duration);this.currentTime=Math.min(this.currentTime,this.duration);c=this.duration/this.frames;var d=Math.floor(this.currentTime/c);d!=b&&(this.mesh.morphTargetInfluences[a]=0,this.mesh.morphTargetInfluences[b]=1,this.mesh.morphTargetInfluences[d]=\n0,a=b,b=d);this.mesh.morphTargetInfluences[d]=this.currentTime%c/c;this.mesh.morphTargetInfluences[a]=1-this.mesh.morphTargetInfluences[d]}}}()};\nTHREE.BoxGeometry=function(a,b,c,d,e,f){function g(a,b,c,d,e,f,g,s){var u,v=h.widthSegments,y=h.heightSegments,G=e/2,w=f/2,K=h.vertices.length;if(\"x\"===a&&\"y\"===b||\"y\"===a&&\"x\"===b)u=\"z\";else if(\"x\"===a&&\"z\"===b||\"z\"===a&&\"x\"===b)u=\"y\",y=h.depthSegments;else if(\"z\"===a&&\"y\"===b||\"y\"===a&&\"z\"===b)u=\"x\",v=h.depthSegments;var x=v+1,D=y+1,E=e/v,A=f/y,B=new THREE.Vector3;B[u]=0<g?1:-1;for(e=0;e<D;e++)for(f=0;f<x;f++){var F=new THREE.Vector3;F[a]=(f*E-G)*c;F[b]=(e*A-w)*d;F[u]=g;h.vertices.push(F)}for(e=\n0;e<y;e++)for(f=0;f<v;f++)w=f+x*e,a=f+x*(e+1),b=f+1+x*(e+1),c=f+1+x*e,d=new THREE.Vector2(f/v,1-e/y),g=new THREE.Vector2(f/v,1-(e+1)/y),u=new THREE.Vector2((f+1)/v,1-(e+1)/y),G=new THREE.Vector2((f+1)/v,1-e/y),w=new THREE.Face3(w+K,a+K,c+K),w.normal.copy(B),w.vertexNormals.push(B.clone(),B.clone(),B.clone()),w.materialIndex=s,h.faces.push(w),h.faceVertexUvs[0].push([d,g,G]),w=new THREE.Face3(a+K,b+K,c+K),w.normal.copy(B),w.vertexNormals.push(B.clone(),B.clone(),B.clone()),w.materialIndex=s,h.faces.push(w),\nh.faceVertexUvs[0].push([g.clone(),u,G.clone()])}THREE.Geometry.call(this);this.type=\"BoxGeometry\";this.parameters={width:a,height:b,depth:c,widthSegments:d,heightSegments:e,depthSegments:f};this.widthSegments=d||1;this.heightSegments=e||1;this.depthSegments=f||1;var h=this;d=a/2;e=b/2;f=c/2;g(\"z\",\"y\",-1,-1,c,b,d,0);g(\"z\",\"y\",1,-1,c,b,-d,1);g(\"x\",\"z\",1,1,a,c,e,2);g(\"x\",\"z\",1,-1,a,c,-e,3);g(\"x\",\"y\",1,-1,a,b,f,4);g(\"x\",\"y\",-1,-1,a,b,-f,5);this.mergeVertices()};THREE.BoxGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.CircleGeometry=function(a,b,c,d){THREE.Geometry.call(this);this.type=\"CircleGeometry\";this.parameters={radius:a,segments:b,thetaStart:c,thetaLength:d};a=a||50;b=void 0!==b?Math.max(3,b):8;c=void 0!==c?c:0;d=void 0!==d?d:2*Math.PI;var e,f=[];e=new THREE.Vector3;var g=new THREE.Vector2(.5,.5);this.vertices.push(e);f.push(g);for(e=0;e<=b;e++){var h=new THREE.Vector3,k=c+e/b*d;h.x=a*Math.cos(k);h.y=a*Math.sin(k);this.vertices.push(h);f.push(new THREE.Vector2((h.x/a+1)/2,(h.y/a+1)/2))}c=new THREE.Vector3(0,\n0,1);for(e=1;e<=b;e++)this.faces.push(new THREE.Face3(e,e+1,0,[c.clone(),c.clone(),c.clone()])),this.faceVertexUvs[0].push([f[e].clone(),f[e+1].clone(),g.clone()]);this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,a)};THREE.CircleGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.CubeGeometry=function(a,b,c,d,e,f){console.warn(\"THREE.CubeGeometry has been renamed to THREE.BoxGeometry.\");return new THREE.BoxGeometry(a,b,c,d,e,f)};\nTHREE.CylinderGeometry=function(a,b,c,d,e,f){THREE.Geometry.call(this);this.type=\"CylinderGeometry\";this.parameters={radiusTop:a,radiusBottom:b,height:c,radialSegments:d,heightSegments:e,openEnded:f};a=void 0!==a?a:20;b=void 0!==b?b:20;c=void 0!==c?c:100;d=d||8;e=e||1;f=void 0!==f?f:!1;var g=c/2,h,k,n=[],p=[];for(k=0;k<=e;k++){var q=[],m=[],r=k/e,t=r*(b-a)+a;for(h=0;h<=d;h++){var s=h/d,u=new THREE.Vector3;u.x=t*Math.sin(s*Math.PI*2);u.y=-r*c+g;u.z=t*Math.cos(s*Math.PI*2);this.vertices.push(u);q.push(this.vertices.length-\n1);m.push(new THREE.Vector2(s,1-r))}n.push(q);p.push(m)}c=(b-a)/c;for(h=0;h<d;h++)for(0!==a?(q=this.vertices[n[0][h]].clone(),m=this.vertices[n[0][h+1]].clone()):(q=this.vertices[n[1][h]].clone(),m=this.vertices[n[1][h+1]].clone()),q.setY(Math.sqrt(q.x*q.x+q.z*q.z)*c).normalize(),m.setY(Math.sqrt(m.x*m.x+m.z*m.z)*c).normalize(),k=0;k<e;k++){var r=n[k][h],t=n[k+1][h],s=n[k+1][h+1],u=n[k][h+1],v=q.clone(),y=q.clone(),G=m.clone(),w=m.clone(),K=p[k][h].clone(),x=p[k+1][h].clone(),D=p[k+1][h+1].clone(),\nE=p[k][h+1].clone();this.faces.push(new THREE.Face3(r,t,u,[v,y,w]));this.faceVertexUvs[0].push([K,x,E]);this.faces.push(new THREE.Face3(t,s,u,[y.clone(),G,w.clone()]));this.faceVertexUvs[0].push([x.clone(),D,E.clone()])}if(!1===f&&0<a)for(this.vertices.push(new THREE.Vector3(0,g,0)),h=0;h<d;h++)r=n[0][h],t=n[0][h+1],s=this.vertices.length-1,v=new THREE.Vector3(0,1,0),y=new THREE.Vector3(0,1,0),G=new THREE.Vector3(0,1,0),K=p[0][h].clone(),x=p[0][h+1].clone(),D=new THREE.Vector2(x.x,0),this.faces.push(new THREE.Face3(r,\nt,s,[v,y,G])),this.faceVertexUvs[0].push([K,x,D]);if(!1===f&&0<b)for(this.vertices.push(new THREE.Vector3(0,-g,0)),h=0;h<d;h++)r=n[k][h+1],t=n[k][h],s=this.vertices.length-1,v=new THREE.Vector3(0,-1,0),y=new THREE.Vector3(0,-1,0),G=new THREE.Vector3(0,-1,0),K=p[k][h+1].clone(),x=p[k][h].clone(),D=new THREE.Vector2(x.x,1),this.faces.push(new THREE.Face3(r,t,s,[v,y,G])),this.faceVertexUvs[0].push([K,x,D]);this.computeFaceNormals()};THREE.CylinderGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.ExtrudeGeometry=function(a,b){\"undefined\"!==typeof a&&(THREE.Geometry.call(this),this.type=\"ExtrudeGeometry\",a=a instanceof Array?a:[a],this.addShapeList(a,b),this.computeFaceNormals())};THREE.ExtrudeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ExtrudeGeometry.prototype.addShapeList=function(a,b){for(var c=a.length,d=0;d<c;d++)this.addShape(a[d],b)};\nTHREE.ExtrudeGeometry.prototype.addShape=function(a,b){function c(a,b,c){b||console.log(\"die\");return b.clone().multiplyScalar(c).add(a)}function d(a,b,c){var d=1,d=a.x-b.x,e=a.y-b.y,f=c.x-a.x,g=c.y-a.y,h=d*d+e*e;if(1E-10<Math.abs(d*g-e*f)){var k=Math.sqrt(h),m=Math.sqrt(f*f+g*g),h=b.x-e/k;b=b.y+d/k;f=((c.x-g/m-h)*g-(c.y+f/m-b)*f)/(d*g-e*f);c=h+d*f-a.x;a=b+e*f-a.y;d=c*c+a*a;if(2>=d)return new THREE.Vector2(c,a);d=Math.sqrt(d/2)}else a=!1,1E-10<d?1E-10<f&&(a=!0):-1E-10>d?-1E-10>f&&(a=!0):Math.sign(e)==\nMath.sign(g)&&(a=!0),a?(c=-e,a=d,d=Math.sqrt(h)):(c=d,a=e,d=Math.sqrt(h/2));return new THREE.Vector2(c/d,a/d)}function e(a,b){var c,d;for(P=a.length;0<=--P;){c=P;d=P-1;0>d&&(d=a.length-1);for(var e=0,f=r+2*p,e=0;e<f;e++){var g=la*e,h=la*(e+1),k=b+c+g,g=b+d+g,m=b+d+h,h=b+c+h,k=k+R,g=g+R,m=m+R,h=h+R;F.faces.push(new THREE.Face3(k,g,h,null,null,y));F.faces.push(new THREE.Face3(g,m,h,null,null,y));k=G.generateSideWallUV(F,k,g,m,h);F.faceVertexUvs[0].push([k[0],k[1],k[3]]);F.faceVertexUvs[0].push([k[1],\nk[2],k[3]])}}}function f(a,b,c){F.vertices.push(new THREE.Vector3(a,b,c))}function g(a,b,c){a+=R;b+=R;c+=R;F.faces.push(new THREE.Face3(a,b,c,null,null,v));a=G.generateTopUV(F,a,b,c);F.faceVertexUvs[0].push(a)}var h=void 0!==b.amount?b.amount:100,k=void 0!==b.bevelThickness?b.bevelThickness:6,n=void 0!==b.bevelSize?b.bevelSize:k-2,p=void 0!==b.bevelSegments?b.bevelSegments:3,q=void 0!==b.bevelEnabled?b.bevelEnabled:!0,m=void 0!==b.curveSegments?b.curveSegments:12,r=void 0!==b.steps?b.steps:1,t=b.extrudePath,\ns,u=!1,v=b.material,y=b.extrudeMaterial,G=void 0!==b.UVGenerator?b.UVGenerator:THREE.ExtrudeGeometry.WorldUVGenerator,w,K,x,D;t&&(s=t.getSpacedPoints(r),u=!0,q=!1,w=void 0!==b.frames?b.frames:new THREE.TubeGeometry.FrenetFrames(t,r,!1),K=new THREE.Vector3,x=new THREE.Vector3,D=new THREE.Vector3);q||(n=k=p=0);var E,A,B,F=this,R=this.vertices.length,t=a.extractPoints(m),m=t.shape,H=t.holes;if(t=!THREE.Shape.Utils.isClockWise(m)){m=m.reverse();A=0;for(B=H.length;A<B;A++)E=H[A],THREE.Shape.Utils.isClockWise(E)&&\n(H[A]=E.reverse());t=!1}var C=THREE.Shape.Utils.triangulateShape(m,H),T=m;A=0;for(B=H.length;A<B;A++)E=H[A],m=m.concat(E);var Q,O,S,X,Y,la=m.length,ma,ya=C.length,t=[],P=0;S=T.length;Q=S-1;for(O=P+1;P<S;P++,Q++,O++)Q===S&&(Q=0),O===S&&(O=0),t[P]=d(T[P],T[Q],T[O]);var Ga=[],Fa,za=t.concat();A=0;for(B=H.length;A<B;A++){E=H[A];Fa=[];P=0;S=E.length;Q=S-1;for(O=P+1;P<S;P++,Q++,O++)Q===S&&(Q=0),O===S&&(O=0),Fa[P]=d(E[P],E[Q],E[O]);Ga.push(Fa);za=za.concat(Fa)}for(Q=0;Q<p;Q++){S=Q/p;X=k*(1-S);O=n*Math.sin(S*\nMath.PI/2);P=0;for(S=T.length;P<S;P++)Y=c(T[P],t[P],O),f(Y.x,Y.y,-X);A=0;for(B=H.length;A<B;A++)for(E=H[A],Fa=Ga[A],P=0,S=E.length;P<S;P++)Y=c(E[P],Fa[P],O),f(Y.x,Y.y,-X)}O=n;for(P=0;P<la;P++)Y=q?c(m[P],za[P],O):m[P],u?(x.copy(w.normals[0]).multiplyScalar(Y.x),K.copy(w.binormals[0]).multiplyScalar(Y.y),D.copy(s[0]).add(x).add(K),f(D.x,D.y,D.z)):f(Y.x,Y.y,0);for(S=1;S<=r;S++)for(P=0;P<la;P++)Y=q?c(m[P],za[P],O):m[P],u?(x.copy(w.normals[S]).multiplyScalar(Y.x),K.copy(w.binormals[S]).multiplyScalar(Y.y),\nD.copy(s[S]).add(x).add(K),f(D.x,D.y,D.z)):f(Y.x,Y.y,h/r*S);for(Q=p-1;0<=Q;Q--){S=Q/p;X=k*(1-S);O=n*Math.sin(S*Math.PI/2);P=0;for(S=T.length;P<S;P++)Y=c(T[P],t[P],O),f(Y.x,Y.y,h+X);A=0;for(B=H.length;A<B;A++)for(E=H[A],Fa=Ga[A],P=0,S=E.length;P<S;P++)Y=c(E[P],Fa[P],O),u?f(Y.x,Y.y+s[r-1].y,s[r-1].x+X):f(Y.x,Y.y,h+X)}(function(){if(q){var a;a=0*la;for(P=0;P<ya;P++)ma=C[P],g(ma[2]+a,ma[1]+a,ma[0]+a);a=r+2*p;a*=la;for(P=0;P<ya;P++)ma=C[P],g(ma[0]+a,ma[1]+a,ma[2]+a)}else{for(P=0;P<ya;P++)ma=C[P],g(ma[2],\nma[1],ma[0]);for(P=0;P<ya;P++)ma=C[P],g(ma[0]+la*r,ma[1]+la*r,ma[2]+la*r)}})();(function(){var a=0;e(T,a);a+=T.length;A=0;for(B=H.length;A<B;A++)E=H[A],e(E,a),a+=E.length})()};\nTHREE.ExtrudeGeometry.WorldUVGenerator={generateTopUV:function(a,b,c,d){a=a.vertices;b=a[b];c=a[c];d=a[d];return[new THREE.Vector2(b.x,b.y),new THREE.Vector2(c.x,c.y),new THREE.Vector2(d.x,d.y)]},generateSideWallUV:function(a,b,c,d,e){a=a.vertices;b=a[b];c=a[c];d=a[d];e=a[e];return.01>Math.abs(b.y-c.y)?[new THREE.Vector2(b.x,1-b.z),new THREE.Vector2(c.x,1-c.z),new THREE.Vector2(d.x,1-d.z),new THREE.Vector2(e.x,1-e.z)]:[new THREE.Vector2(b.y,1-b.z),new THREE.Vector2(c.y,1-c.z),new THREE.Vector2(d.y,\n1-d.z),new THREE.Vector2(e.y,1-e.z)]}};THREE.ShapeGeometry=function(a,b){THREE.Geometry.call(this);this.type=\"ShapeGeometry\";!1===a instanceof Array&&(a=[a]);this.addShapeList(a,b);this.computeFaceNormals()};THREE.ShapeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ShapeGeometry.prototype.addShapeList=function(a,b){for(var c=0,d=a.length;c<d;c++)this.addShape(a[c],b);return this};\nTHREE.ShapeGeometry.prototype.addShape=function(a,b){void 0===b&&(b={});var c=b.material,d=void 0===b.UVGenerator?THREE.ExtrudeGeometry.WorldUVGenerator:b.UVGenerator,e,f,g,h=this.vertices.length;e=a.extractPoints(void 0!==b.curveSegments?b.curveSegments:12);var k=e.shape,n=e.holes;if(!THREE.Shape.Utils.isClockWise(k))for(k=k.reverse(),e=0,f=n.length;e<f;e++)g=n[e],THREE.Shape.Utils.isClockWise(g)&&(n[e]=g.reverse());var p=THREE.Shape.Utils.triangulateShape(k,n);e=0;for(f=n.length;e<f;e++)g=n[e],\nk=k.concat(g);n=k.length;f=p.length;for(e=0;e<n;e++)g=k[e],this.vertices.push(new THREE.Vector3(g.x,g.y,0));for(e=0;e<f;e++)n=p[e],k=n[0]+h,g=n[1]+h,n=n[2]+h,this.faces.push(new THREE.Face3(k,g,n,null,null,c)),this.faceVertexUvs[0].push(d.generateTopUV(this,k,g,n))};\nTHREE.LatheGeometry=function(a,b,c,d){THREE.Geometry.call(this);this.type=\"LatheGeometry\";this.parameters={points:a,segments:b,phiStart:c,phiLength:d};b=b||12;c=c||0;d=d||2*Math.PI;for(var e=1/(a.length-1),f=1/b,g=0,h=b;g<=h;g++)for(var k=c+g*f*d,n=Math.cos(k),p=Math.sin(k),k=0,q=a.length;k<q;k++){var m=a[k],r=new THREE.Vector3;r.x=n*m.x-p*m.y;r.y=p*m.x+n*m.y;r.z=m.z;this.vertices.push(r)}c=a.length;g=0;for(h=b;g<h;g++)for(k=0,q=a.length-1;k<q;k++){b=p=k+c*g;d=p+c;var n=p+1+c,p=p+1,m=g*f,r=k*e,t=\nm+f,s=r+e;this.faces.push(new THREE.Face3(b,d,p));this.faceVertexUvs[0].push([new THREE.Vector2(m,r),new THREE.Vector2(t,r),new THREE.Vector2(m,s)]);this.faces.push(new THREE.Face3(d,n,p));this.faceVertexUvs[0].push([new THREE.Vector2(t,r),new THREE.Vector2(t,s),new THREE.Vector2(m,s)])}this.mergeVertices();this.computeFaceNormals();this.computeVertexNormals()};THREE.LatheGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.PlaneGeometry=function(a,b,c,d){console.info(\"THREE.PlaneGeometry: Consider using THREE.PlaneBufferGeometry for lower memory footprint.\");THREE.Geometry.call(this);this.type=\"PlaneGeometry\";this.parameters={width:a,height:b,widthSegments:c,heightSegments:d};this.fromBufferGeometry(new THREE.PlaneBufferGeometry(a,b,c,d))};THREE.PlaneGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.PlaneBufferGeometry=function(a,b,c,d){THREE.BufferGeometry.call(this);this.type=\"PlaneBufferGeometry\";this.parameters={width:a,height:b,widthSegments:c,heightSegments:d};var e=a/2,f=b/2;c=c||1;d=d||1;var g=c+1,h=d+1,k=a/c,n=b/d;b=new Float32Array(g*h*3);a=new Float32Array(g*h*3);for(var p=new Float32Array(g*h*2),q=0,m=0,r=0;r<h;r++)for(var t=r*n-f,s=0;s<g;s++)b[q]=s*k-e,b[q+1]=-t,a[q+2]=1,p[m]=s/c,p[m+1]=1-r/d,q+=3,m+=2;q=0;e=new (65535<b.length/3?Uint32Array:Uint16Array)(c*d*6);for(r=0;r<d;r++)for(s=\n0;s<c;s++)f=s+g*(r+1),h=s+1+g*(r+1),k=s+1+g*r,e[q]=s+g*r,e[q+1]=f,e[q+2]=k,e[q+3]=f,e[q+4]=h,e[q+5]=k,q+=6;this.addAttribute(\"index\",new THREE.BufferAttribute(e,1));this.addAttribute(\"position\",new THREE.BufferAttribute(b,3));this.addAttribute(\"normal\",new THREE.BufferAttribute(a,3));this.addAttribute(\"uv\",new THREE.BufferAttribute(p,2))};THREE.PlaneBufferGeometry.prototype=Object.create(THREE.BufferGeometry.prototype);\nTHREE.RingGeometry=function(a,b,c,d,e,f){THREE.Geometry.call(this);this.type=\"RingGeometry\";this.parameters={innerRadius:a,outerRadius:b,thetaSegments:c,phiSegments:d,thetaStart:e,thetaLength:f};a=a||0;b=b||50;e=void 0!==e?e:0;f=void 0!==f?f:2*Math.PI;c=void 0!==c?Math.max(3,c):8;d=void 0!==d?Math.max(1,d):8;var g,h=[],k=a,n=(b-a)/d;for(a=0;a<d+1;a++){for(g=0;g<c+1;g++){var p=new THREE.Vector3,q=e+g/c*f;p.x=k*Math.cos(q);p.y=k*Math.sin(q);this.vertices.push(p);h.push(new THREE.Vector2((p.x/b+1)/2,\n(p.y/b+1)/2))}k+=n}b=new THREE.Vector3(0,0,1);for(a=0;a<d;a++)for(e=a*(c+1),g=0;g<c;g++)f=q=g+e,n=q+c+1,p=q+c+2,this.faces.push(new THREE.Face3(f,n,p,[b.clone(),b.clone(),b.clone()])),this.faceVertexUvs[0].push([h[f].clone(),h[n].clone(),h[p].clone()]),f=q,n=q+c+2,p=q+1,this.faces.push(new THREE.Face3(f,n,p,[b.clone(),b.clone(),b.clone()])),this.faceVertexUvs[0].push([h[f].clone(),h[n].clone(),h[p].clone()]);this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,k)};\nTHREE.RingGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.SphereGeometry=function(a,b,c,d,e,f,g){THREE.Geometry.call(this);this.type=\"SphereGeometry\";this.parameters={radius:a,widthSegments:b,heightSegments:c,phiStart:d,phiLength:e,thetaStart:f,thetaLength:g};a=a||50;b=Math.max(3,Math.floor(b)||8);c=Math.max(2,Math.floor(c)||6);d=void 0!==d?d:0;e=void 0!==e?e:2*Math.PI;f=void 0!==f?f:0;g=void 0!==g?g:Math.PI;var h,k,n=[],p=[];for(k=0;k<=c;k++){var q=[],m=[];for(h=0;h<=b;h++){var r=h/b,t=k/c,s=new THREE.Vector3;s.x=-a*Math.cos(d+r*e)*Math.sin(f+t*g);\ns.y=a*Math.cos(f+t*g);s.z=a*Math.sin(d+r*e)*Math.sin(f+t*g);this.vertices.push(s);q.push(this.vertices.length-1);m.push(new THREE.Vector2(r,1-t))}n.push(q);p.push(m)}for(k=0;k<c;k++)for(h=0;h<b;h++){d=n[k][h+1];e=n[k][h];f=n[k+1][h];g=n[k+1][h+1];var q=this.vertices[d].clone().normalize(),m=this.vertices[e].clone().normalize(),r=this.vertices[f].clone().normalize(),t=this.vertices[g].clone().normalize(),s=p[k][h+1].clone(),u=p[k][h].clone(),v=p[k+1][h].clone(),y=p[k+1][h+1].clone();Math.abs(this.vertices[d].y)===\na?(s.x=(s.x+u.x)/2,this.faces.push(new THREE.Face3(d,f,g,[q,r,t])),this.faceVertexUvs[0].push([s,v,y])):Math.abs(this.vertices[f].y)===a?(v.x=(v.x+y.x)/2,this.faces.push(new THREE.Face3(d,e,f,[q,m,r])),this.faceVertexUvs[0].push([s,u,v])):(this.faces.push(new THREE.Face3(d,e,g,[q,m,t])),this.faceVertexUvs[0].push([s,u,y]),this.faces.push(new THREE.Face3(e,f,g,[m.clone(),r,t.clone()])),this.faceVertexUvs[0].push([u.clone(),v,y.clone()]))}this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,\na)};THREE.SphereGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.TextGeometry=function(a,b){b=b||{};var c=THREE.FontUtils.generateShapes(a,b);b.amount=void 0!==b.height?b.height:50;void 0===b.bevelThickness&&(b.bevelThickness=10);void 0===b.bevelSize&&(b.bevelSize=8);void 0===b.bevelEnabled&&(b.bevelEnabled=!1);THREE.ExtrudeGeometry.call(this,c,b);this.type=\"TextGeometry\"};THREE.TextGeometry.prototype=Object.create(THREE.ExtrudeGeometry.prototype);\nTHREE.TorusGeometry=function(a,b,c,d,e){THREE.Geometry.call(this);this.type=\"TorusGeometry\";this.parameters={radius:a,tube:b,radialSegments:c,tubularSegments:d,arc:e};a=a||100;b=b||40;c=c||8;d=d||6;e=e||2*Math.PI;for(var f=new THREE.Vector3,g=[],h=[],k=0;k<=c;k++)for(var n=0;n<=d;n++){var p=n/d*e,q=k/c*Math.PI*2;f.x=a*Math.cos(p);f.y=a*Math.sin(p);var m=new THREE.Vector3;m.x=(a+b*Math.cos(q))*Math.cos(p);m.y=(a+b*Math.cos(q))*Math.sin(p);m.z=b*Math.sin(q);this.vertices.push(m);g.push(new THREE.Vector2(n/\nd,k/c));h.push(m.clone().sub(f).normalize())}for(k=1;k<=c;k++)for(n=1;n<=d;n++)a=(d+1)*k+n-1,b=(d+1)*(k-1)+n-1,e=(d+1)*(k-1)+n,f=(d+1)*k+n,p=new THREE.Face3(a,b,f,[h[a].clone(),h[b].clone(),h[f].clone()]),this.faces.push(p),this.faceVertexUvs[0].push([g[a].clone(),g[b].clone(),g[f].clone()]),p=new THREE.Face3(b,e,f,[h[b].clone(),h[e].clone(),h[f].clone()]),this.faces.push(p),this.faceVertexUvs[0].push([g[b].clone(),g[e].clone(),g[f].clone()]);this.computeFaceNormals()};\nTHREE.TorusGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.TorusKnotGeometry=function(a,b,c,d,e,f,g){function h(a,b,c,d,e){var f=Math.cos(a),g=Math.sin(a);a*=b/c;b=Math.cos(a);f*=d*(2+b)*.5;g=d*(2+b)*g*.5;d=e*d*Math.sin(a)*.5;return new THREE.Vector3(f,g,d)}THREE.Geometry.call(this);this.type=\"TorusKnotGeometry\";this.parameters={radius:a,tube:b,radialSegments:c,tubularSegments:d,p:e,q:f,heightScale:g};a=a||100;b=b||40;c=c||64;d=d||8;e=e||2;f=f||3;g=g||1;for(var k=Array(c),n=new THREE.Vector3,p=new THREE.Vector3,q=new THREE.Vector3,m=0;m<c;++m){k[m]=\nArray(d);var r=m/c*2*e*Math.PI,t=h(r,f,e,a,g),r=h(r+.01,f,e,a,g);n.subVectors(r,t);p.addVectors(r,t);q.crossVectors(n,p);p.crossVectors(q,n);q.normalize();p.normalize();for(r=0;r<d;++r){var s=r/d*2*Math.PI,u=-b*Math.cos(s),s=b*Math.sin(s),v=new THREE.Vector3;v.x=t.x+u*p.x+s*q.x;v.y=t.y+u*p.y+s*q.y;v.z=t.z+u*p.z+s*q.z;k[m][r]=this.vertices.push(v)-1}}for(m=0;m<c;++m)for(r=0;r<d;++r)e=(m+1)%c,f=(r+1)%d,a=k[m][r],b=k[e][r],e=k[e][f],f=k[m][f],g=new THREE.Vector2(m/c,r/d),n=new THREE.Vector2((m+1)/c,\nr/d),p=new THREE.Vector2((m+1)/c,(r+1)/d),q=new THREE.Vector2(m/c,(r+1)/d),this.faces.push(new THREE.Face3(a,b,f)),this.faceVertexUvs[0].push([g,n,q]),this.faces.push(new THREE.Face3(b,e,f)),this.faceVertexUvs[0].push([n.clone(),p,q.clone()]);this.computeFaceNormals();this.computeVertexNormals()};THREE.TorusKnotGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.TubeGeometry=function(a,b,c,d,e){THREE.Geometry.call(this);this.type=\"TubeGeometry\";this.parameters={path:a,segments:b,radius:c,radialSegments:d,closed:e};b=b||64;c=c||1;d=d||8;e=e||!1;var f=[],g,h,k=b+1,n,p,q,m,r=new THREE.Vector3,t,s,u;t=new THREE.TubeGeometry.FrenetFrames(a,b,e);s=t.normals;u=t.binormals;this.tangents=t.tangents;this.normals=s;this.binormals=u;for(t=0;t<k;t++)for(f[t]=[],n=t/(k-1),m=a.getPointAt(n),g=s[t],h=u[t],n=0;n<d;n++)p=n/d*2*Math.PI,q=-c*Math.cos(p),p=c*Math.sin(p),\nr.copy(m),r.x+=q*g.x+p*h.x,r.y+=q*g.y+p*h.y,r.z+=q*g.z+p*h.z,f[t][n]=this.vertices.push(new THREE.Vector3(r.x,r.y,r.z))-1;for(t=0;t<b;t++)for(n=0;n<d;n++)k=e?(t+1)%b:t+1,r=(n+1)%d,a=f[t][n],c=f[k][n],k=f[k][r],r=f[t][r],s=new THREE.Vector2(t/b,n/d),u=new THREE.Vector2((t+1)/b,n/d),g=new THREE.Vector2((t+1)/b,(n+1)/d),h=new THREE.Vector2(t/b,(n+1)/d),this.faces.push(new THREE.Face3(a,c,r)),this.faceVertexUvs[0].push([s,u,h]),this.faces.push(new THREE.Face3(c,k,r)),this.faceVertexUvs[0].push([u.clone(),\ng,h.clone()]);this.computeFaceNormals();this.computeVertexNormals()};THREE.TubeGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.TubeGeometry.FrenetFrames=function(a,b,c){new THREE.Vector3;var d=new THREE.Vector3;new THREE.Vector3;var e=[],f=[],g=[],h=new THREE.Vector3,k=new THREE.Matrix4;b+=1;var n,p,q;this.tangents=e;this.normals=f;this.binormals=g;for(n=0;n<b;n++)p=n/(b-1),e[n]=a.getTangentAt(p),e[n].normalize();f[0]=new THREE.Vector3;g[0]=new THREE.Vector3;a=Number.MAX_VALUE;n=Math.abs(e[0].x);p=Math.abs(e[0].y);q=Math.abs(e[0].z);n<=a&&(a=n,d.set(1,0,0));p<=a&&(a=p,d.set(0,1,0));q<=a&&d.set(0,0,1);h.crossVectors(e[0],\nd).normalize();f[0].crossVectors(e[0],h);g[0].crossVectors(e[0],f[0]);for(n=1;n<b;n++)f[n]=f[n-1].clone(),g[n]=g[n-1].clone(),h.crossVectors(e[n-1],e[n]),1E-4<h.length()&&(h.normalize(),d=Math.acos(THREE.Math.clamp(e[n-1].dot(e[n]),-1,1)),f[n].applyMatrix4(k.makeRotationAxis(h,d))),g[n].crossVectors(e[n],f[n]);if(c)for(d=Math.acos(THREE.Math.clamp(f[0].dot(f[b-1]),-1,1)),d/=b-1,0<e[0].dot(h.crossVectors(f[0],f[b-1]))&&(d=-d),n=1;n<b;n++)f[n].applyMatrix4(k.makeRotationAxis(e[n],d*n)),g[n].crossVectors(e[n],\nf[n])};\nTHREE.PolyhedronGeometry=function(a,b,c,d){function e(a){var b=a.normalize().clone();b.index=k.vertices.push(b)-1;var c=Math.atan2(a.z,-a.x)/2/Math.PI+.5;a=Math.atan2(-a.y,Math.sqrt(a.x*a.x+a.z*a.z))/Math.PI+.5;b.uv=new THREE.Vector2(c,1-a);return b}function f(a,b,c){var d=new THREE.Face3(a.index,b.index,c.index,[a.clone(),b.clone(),c.clone()]);k.faces.push(d);u.copy(a).add(b).add(c).divideScalar(3);d=Math.atan2(u.z,-u.x);k.faceVertexUvs[0].push([h(a.uv,a,d),h(b.uv,b,d),h(c.uv,c,d)])}function g(a,b){var c=\nMath.pow(2,b);Math.pow(4,b);for(var d=e(k.vertices[a.a]),g=e(k.vertices[a.b]),h=e(k.vertices[a.c]),m=[],n=0;n<=c;n++){m[n]=[];for(var p=e(d.clone().lerp(h,n/c)),q=e(g.clone().lerp(h,n/c)),r=c-n,s=0;s<=r;s++)m[n][s]=0==s&&n==c?p:e(p.clone().lerp(q,s/r))}for(n=0;n<c;n++)for(s=0;s<2*(c-n)-1;s++)d=Math.floor(s/2),0==s%2?f(m[n][d+1],m[n+1][d],m[n][d]):f(m[n][d+1],m[n+1][d+1],m[n+1][d])}function h(a,b,c){0>c&&1===a.x&&(a=new THREE.Vector2(a.x-1,a.y));0===b.x&&0===b.z&&(a=new THREE.Vector2(c/2/Math.PI+.5,\na.y));return a.clone()}THREE.Geometry.call(this);this.type=\"PolyhedronGeometry\";this.parameters={vertices:a,indices:b,radius:c,detail:d};c=c||1;d=d||0;for(var k=this,n=0,p=a.length;n<p;n+=3)e(new THREE.Vector3(a[n],a[n+1],a[n+2]));a=this.vertices;for(var q=[],m=n=0,p=b.length;n<p;n+=3,m++){var r=a[b[n]],t=a[b[n+1]],s=a[b[n+2]];q[m]=new THREE.Face3(r.index,t.index,s.index,[r.clone(),t.clone(),s.clone()])}for(var u=new THREE.Vector3,n=0,p=q.length;n<p;n++)g(q[n],d);n=0;for(p=this.faceVertexUvs[0].length;n<\np;n++)b=this.faceVertexUvs[0][n],d=b[0].x,a=b[1].x,q=b[2].x,m=Math.max(d,Math.max(a,q)),r=Math.min(d,Math.min(a,q)),.9<m&&.1>r&&(.2>d&&(b[0].x+=1),.2>a&&(b[1].x+=1),.2>q&&(b[2].x+=1));n=0;for(p=this.vertices.length;n<p;n++)this.vertices[n].multiplyScalar(c);this.mergeVertices();this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,c)};THREE.PolyhedronGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.DodecahedronGeometry=function(a,b){this.parameters={radius:a,detail:b};var c=(1+Math.sqrt(5))/2,d=1/c;THREE.PolyhedronGeometry.call(this,[-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-d,-c,0,-d,c,0,d,-c,0,d,c,-d,-c,0,-d,c,0,d,-c,0,d,c,0,-c,0,-d,c,0,-d,-c,0,d,c,0,d],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,\n11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],a,b)};THREE.DodecahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.IcosahedronGeometry=function(a,b){var c=(1+Math.sqrt(5))/2;THREE.PolyhedronGeometry.call(this,[-1,c,0,1,c,0,-1,-c,0,1,-c,0,0,-1,c,0,1,c,0,-1,-c,0,1,-c,c,0,-1,c,0,1,-c,0,-1,-c,0,1],[0,11,5,0,5,1,0,1,7,0,7,10,0,10,11,1,5,9,5,11,4,11,10,2,10,7,6,7,1,8,3,9,4,3,4,2,3,2,6,3,6,8,3,8,9,4,9,5,2,4,11,6,2,10,8,6,7,9,8,1],a,b);this.type=\"IcosahedronGeometry\";this.parameters={radius:a,detail:b}};THREE.IcosahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.OctahedronGeometry=function(a,b){this.parameters={radius:a,detail:b};THREE.PolyhedronGeometry.call(this,[1,0,0,-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1],[0,2,4,0,4,3,0,3,5,0,5,2,1,2,5,1,5,3,1,3,4,1,4,2],a,b);this.type=\"OctahedronGeometry\";this.parameters={radius:a,detail:b}};THREE.OctahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.TetrahedronGeometry=function(a,b){THREE.PolyhedronGeometry.call(this,[1,1,1,-1,-1,1,-1,1,-1,1,-1,-1],[2,1,0,0,3,2,1,3,0,2,3,1],a,b);this.type=\"TetrahedronGeometry\";this.parameters={radius:a,detail:b}};THREE.TetrahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.ParametricGeometry=function(a,b,c){THREE.Geometry.call(this);this.type=\"ParametricGeometry\";this.parameters={func:a,slices:b,stacks:c};var d=this.vertices,e=this.faces,f=this.faceVertexUvs[0],g,h,k,n,p=b+1;for(g=0;g<=c;g++)for(n=g/c,h=0;h<=b;h++)k=h/b,k=a(k,n),d.push(k);var q,m,r,t;for(g=0;g<c;g++)for(h=0;h<b;h++)a=g*p+h,d=g*p+h+1,n=(g+1)*p+h+1,k=(g+1)*p+h,q=new THREE.Vector2(h/b,g/c),m=new THREE.Vector2((h+1)/b,g/c),r=new THREE.Vector2((h+1)/b,(g+1)/c),t=new THREE.Vector2(h/b,(g+1)/c),e.push(new THREE.Face3(a,\nd,k)),f.push([q,m,t]),e.push(new THREE.Face3(d,n,k)),f.push([m.clone(),r,t.clone()]);this.computeFaceNormals();this.computeVertexNormals()};THREE.ParametricGeometry.prototype=Object.create(THREE.Geometry.prototype);\nTHREE.AxisHelper=function(a){a=a||1;var b=new Float32Array([0,0,0,a,0,0,0,0,0,0,a,0,0,0,0,0,0,a]),c=new Float32Array([1,0,0,1,.6,0,0,1,0,.6,1,0,0,0,1,0,.6,1]);a=new THREE.BufferGeometry;a.addAttribute(\"position\",new THREE.BufferAttribute(b,3));a.addAttribute(\"color\",new THREE.BufferAttribute(c,3));b=new THREE.LineBasicMaterial({vertexColors:THREE.VertexColors});THREE.Line.call(this,a,b,THREE.LinePieces)};THREE.AxisHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.ArrowHelper=function(){var a=new THREE.Geometry;a.vertices.push(new THREE.Vector3(0,0,0),new THREE.Vector3(0,1,0));var b=new THREE.CylinderGeometry(0,.5,1,5,1);b.applyMatrix((new THREE.Matrix4).makeTranslation(0,-.5,0));return function(c,d,e,f,g,h){THREE.Object3D.call(this);void 0===f&&(f=16776960);void 0===e&&(e=1);void 0===g&&(g=.2*e);void 0===h&&(h=.2*g);this.position.copy(d);this.line=new THREE.Line(a,new THREE.LineBasicMaterial({color:f}));this.line.matrixAutoUpdate=!1;this.add(this.line);\nthis.cone=new THREE.Mesh(b,new THREE.MeshBasicMaterial({color:f}));this.cone.matrixAutoUpdate=!1;this.add(this.cone);this.setDirection(c);this.setLength(e,g,h)}}();THREE.ArrowHelper.prototype=Object.create(THREE.Object3D.prototype);THREE.ArrowHelper.prototype.setDirection=function(){var a=new THREE.Vector3,b;return function(c){.99999<c.y?this.quaternion.set(0,0,0,1):-.99999>c.y?this.quaternion.set(1,0,0,0):(a.set(c.z,0,-c.x).normalize(),b=Math.acos(c.y),this.quaternion.setFromAxisAngle(a,b))}}();\nTHREE.ArrowHelper.prototype.setLength=function(a,b,c){void 0===b&&(b=.2*a);void 0===c&&(c=.2*b);this.line.scale.set(1,a,1);this.line.updateMatrix();this.cone.scale.set(c,b,c);this.cone.position.y=a;this.cone.updateMatrix()};THREE.ArrowHelper.prototype.setColor=function(a){this.line.material.color.set(a);this.cone.material.color.set(a)};\nTHREE.BoxHelper=function(a){var b=new THREE.BufferGeometry;b.addAttribute(\"position\",new THREE.BufferAttribute(new Float32Array(72),3));THREE.Line.call(this,b,new THREE.LineBasicMaterial({color:16776960}),THREE.LinePieces);void 0!==a&&this.update(a)};THREE.BoxHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.BoxHelper.prototype.update=function(a){var b=a.geometry;null===b.boundingBox&&b.computeBoundingBox();var c=b.boundingBox.min,b=b.boundingBox.max,d=this.geometry.attributes.position.array;d[0]=b.x;d[1]=b.y;d[2]=b.z;d[3]=c.x;d[4]=b.y;d[5]=b.z;d[6]=c.x;d[7]=b.y;d[8]=b.z;d[9]=c.x;d[10]=c.y;d[11]=b.z;d[12]=c.x;d[13]=c.y;d[14]=b.z;d[15]=b.x;d[16]=c.y;d[17]=b.z;d[18]=b.x;d[19]=c.y;d[20]=b.z;d[21]=b.x;d[22]=b.y;d[23]=b.z;d[24]=b.x;d[25]=b.y;d[26]=c.z;d[27]=c.x;d[28]=b.y;d[29]=c.z;d[30]=c.x;d[31]=b.y;\nd[32]=c.z;d[33]=c.x;d[34]=c.y;d[35]=c.z;d[36]=c.x;d[37]=c.y;d[38]=c.z;d[39]=b.x;d[40]=c.y;d[41]=c.z;d[42]=b.x;d[43]=c.y;d[44]=c.z;d[45]=b.x;d[46]=b.y;d[47]=c.z;d[48]=b.x;d[49]=b.y;d[50]=b.z;d[51]=b.x;d[52]=b.y;d[53]=c.z;d[54]=c.x;d[55]=b.y;d[56]=b.z;d[57]=c.x;d[58]=b.y;d[59]=c.z;d[60]=c.x;d[61]=c.y;d[62]=b.z;d[63]=c.x;d[64]=c.y;d[65]=c.z;d[66]=b.x;d[67]=c.y;d[68]=b.z;d[69]=b.x;d[70]=c.y;d[71]=c.z;this.geometry.attributes.position.needsUpdate=!0;this.geometry.computeBoundingSphere();this.matrix=a.matrixWorld;\nthis.matrixAutoUpdate=!1};THREE.BoundingBoxHelper=function(a,b){var c=void 0!==b?b:8947848;this.object=a;this.box=new THREE.Box3;THREE.Mesh.call(this,new THREE.BoxGeometry(1,1,1),new THREE.MeshBasicMaterial({color:c,wireframe:!0}))};THREE.BoundingBoxHelper.prototype=Object.create(THREE.Mesh.prototype);THREE.BoundingBoxHelper.prototype.update=function(){this.box.setFromObject(this.object);this.box.size(this.scale);this.box.center(this.position)};\nTHREE.CameraHelper=function(a){function b(a,b,d){c(a,d);c(b,d)}function c(a,b){d.vertices.push(new THREE.Vector3);d.colors.push(new THREE.Color(b));void 0===f[a]&&(f[a]=[]);f[a].push(d.vertices.length-1)}var d=new THREE.Geometry,e=new THREE.LineBasicMaterial({color:16777215,vertexColors:THREE.FaceColors}),f={};b(\"n1\",\"n2\",16755200);b(\"n2\",\"n4\",16755200);b(\"n4\",\"n3\",16755200);b(\"n3\",\"n1\",16755200);b(\"f1\",\"f2\",16755200);b(\"f2\",\"f4\",16755200);b(\"f4\",\"f3\",16755200);b(\"f3\",\"f1\",16755200);b(\"n1\",\"f1\",16755200);\nb(\"n2\",\"f2\",16755200);b(\"n3\",\"f3\",16755200);b(\"n4\",\"f4\",16755200);b(\"p\",\"n1\",16711680);b(\"p\",\"n2\",16711680);b(\"p\",\"n3\",16711680);b(\"p\",\"n4\",16711680);b(\"u1\",\"u2\",43775);b(\"u2\",\"u3\",43775);b(\"u3\",\"u1\",43775);b(\"c\",\"t\",16777215);b(\"p\",\"c\",3355443);b(\"cn1\",\"cn2\",3355443);b(\"cn3\",\"cn4\",3355443);b(\"cf1\",\"cf2\",3355443);b(\"cf3\",\"cf4\",3355443);THREE.Line.call(this,d,e,THREE.LinePieces);this.camera=a;this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1;this.pointMap=f;this.update()};\nTHREE.CameraHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.CameraHelper.prototype.update=function(){var a,b,c=new THREE.Vector3,d=new THREE.Camera,e=function(e,g,h,k){c.set(g,h,k).unproject(d);e=b[e];if(void 0!==e)for(g=0,h=e.length;g<h;g++)a.vertices[e[g]].copy(c)};return function(){a=this.geometry;b=this.pointMap;d.projectionMatrix.copy(this.camera.projectionMatrix);e(\"c\",0,0,-1);e(\"t\",0,0,1);e(\"n1\",-1,-1,-1);e(\"n2\",1,-1,-1);e(\"n3\",-1,1,-1);e(\"n4\",1,1,-1);e(\"f1\",-1,-1,1);e(\"f2\",1,-1,1);e(\"f3\",-1,1,1);e(\"f4\",1,1,1);e(\"u1\",.7,1.1,-1);e(\"u2\",-.7,1.1,\n-1);e(\"u3\",0,2,-1);e(\"cf1\",-1,0,1);e(\"cf2\",1,0,1);e(\"cf3\",0,-1,1);e(\"cf4\",0,1,1);e(\"cn1\",-1,0,-1);e(\"cn2\",1,0,-1);e(\"cn3\",0,-1,-1);e(\"cn4\",0,1,-1);a.verticesNeedUpdate=!0}}();\nTHREE.DirectionalLightHelper=function(a,b){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1;b=b||1;var c=new THREE.Geometry;c.vertices.push(new THREE.Vector3(-b,b,0),new THREE.Vector3(b,b,0),new THREE.Vector3(b,-b,0),new THREE.Vector3(-b,-b,0),new THREE.Vector3(-b,b,0));var d=new THREE.LineBasicMaterial({fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);this.lightPlane=new THREE.Line(c,d);this.add(this.lightPlane);\nc=new THREE.Geometry;c.vertices.push(new THREE.Vector3,new THREE.Vector3);d=new THREE.LineBasicMaterial({fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);this.targetLine=new THREE.Line(c,d);this.add(this.targetLine);this.update()};THREE.DirectionalLightHelper.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.DirectionalLightHelper.prototype.dispose=function(){this.lightPlane.geometry.dispose();this.lightPlane.material.dispose();this.targetLine.geometry.dispose();this.targetLine.material.dispose()};\nTHREE.DirectionalLightHelper.prototype.update=function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3;return function(){a.setFromMatrixPosition(this.light.matrixWorld);b.setFromMatrixPosition(this.light.target.matrixWorld);c.subVectors(b,a);this.lightPlane.lookAt(c);this.lightPlane.material.color.copy(this.light.color).multiplyScalar(this.light.intensity);this.targetLine.geometry.vertices[1].copy(c);this.targetLine.geometry.verticesNeedUpdate=!0;this.targetLine.material.color.copy(this.lightPlane.material.color)}}();\nTHREE.EdgesHelper=function(a,b){var c=void 0!==b?b:16777215,d=[0,0],e={},f=function(a,b){return a-b},g=[\"a\",\"b\",\"c\"],h=new THREE.BufferGeometry,k=a.geometry.clone();k.mergeVertices();k.computeFaceNormals();for(var n=k.vertices,k=k.faces,p=0,q=0,m=k.length;q<m;q++)for(var r=k[q],t=0;3>t;t++){d[0]=r[g[t]];d[1]=r[g[(t+1)%3]];d.sort(f);var s=d.toString();void 0===e[s]?(e[s]={vert1:d[0],vert2:d[1],face1:q,face2:void 0},p++):e[s].face2=q}d=new Float32Array(6*p);f=0;for(s in e)if(g=e[s],void 0===g.face2||\n.9999>k[g.face1].normal.dot(k[g.face2].normal))p=n[g.vert1],d[f++]=p.x,d[f++]=p.y,d[f++]=p.z,p=n[g.vert2],d[f++]=p.x,d[f++]=p.y,d[f++]=p.z;h.addAttribute(\"position\",new THREE.BufferAttribute(d,3));THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1};THREE.EdgesHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.FaceNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;a=void 0!==c?c:16776960;d=void 0!==d?d:1;b=new THREE.Geometry;c=0;for(var e=this.object.geometry.faces.length;c<e;c++)b.vertices.push(new THREE.Vector3,new THREE.Vector3);THREE.Line.call(this,b,new THREE.LineBasicMaterial({color:a,linewidth:d}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.normalMatrix=new THREE.Matrix3;this.update()};THREE.FaceNormalsHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.FaceNormalsHelper.prototype.update=function(){var a=this.geometry.vertices,b=this.object,c=b.geometry.vertices,d=b.geometry.faces,e=b.matrixWorld;b.updateMatrixWorld(!0);this.normalMatrix.getNormalMatrix(e);for(var f=b=0,g=d.length;b<g;b++,f+=2){var h=d[b];a[f].copy(c[h.a]).add(c[h.b]).add(c[h.c]).divideScalar(3).applyMatrix4(e);a[f+1].copy(h.normal).applyMatrix3(this.normalMatrix).normalize().multiplyScalar(this.size).add(a[f])}this.geometry.verticesNeedUpdate=!0;return this};\nTHREE.GridHelper=function(a,b){var c=new THREE.Geometry,d=new THREE.LineBasicMaterial({vertexColors:THREE.VertexColors});this.color1=new THREE.Color(4473924);this.color2=new THREE.Color(8947848);for(var e=-a;e<=a;e+=b){c.vertices.push(new THREE.Vector3(-a,0,e),new THREE.Vector3(a,0,e),new THREE.Vector3(e,0,-a),new THREE.Vector3(e,0,a));var f=0===e?this.color1:this.color2;c.colors.push(f,f,f,f)}THREE.Line.call(this,c,d,THREE.LinePieces)};THREE.GridHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.GridHelper.prototype.setColors=function(a,b){this.color1.set(a);this.color2.set(b);this.geometry.colorsNeedUpdate=!0};\nTHREE.HemisphereLightHelper=function(a,b,c,d){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1;this.colors=[new THREE.Color,new THREE.Color];a=new THREE.SphereGeometry(b,4,2);a.applyMatrix((new THREE.Matrix4).makeRotationX(-Math.PI/2));for(b=0;8>b;b++)a.faces[b].color=this.colors[4>b?0:1];b=new THREE.MeshBasicMaterial({vertexColors:THREE.FaceColors,wireframe:!0});this.lightSphere=new THREE.Mesh(a,b);this.add(this.lightSphere);\nthis.update()};THREE.HemisphereLightHelper.prototype=Object.create(THREE.Object3D.prototype);THREE.HemisphereLightHelper.prototype.dispose=function(){this.lightSphere.geometry.dispose();this.lightSphere.material.dispose()};\nTHREE.HemisphereLightHelper.prototype.update=function(){var a=new THREE.Vector3;return function(){this.colors[0].copy(this.light.color).multiplyScalar(this.light.intensity);this.colors[1].copy(this.light.groundColor).multiplyScalar(this.light.intensity);this.lightSphere.lookAt(a.setFromMatrixPosition(this.light.matrixWorld).negate());this.lightSphere.geometry.colorsNeedUpdate=!0}}();\nTHREE.PointLightHelper=function(a,b){this.light=a;this.light.updateMatrixWorld();var c=new THREE.SphereGeometry(b,4,2),d=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);THREE.Mesh.call(this,c,d);this.matrix=this.light.matrixWorld;this.matrixAutoUpdate=!1};THREE.PointLightHelper.prototype=Object.create(THREE.Mesh.prototype);THREE.PointLightHelper.prototype.dispose=function(){this.geometry.dispose();this.material.dispose()};\nTHREE.PointLightHelper.prototype.update=function(){this.material.color.copy(this.light.color).multiplyScalar(this.light.intensity)};\nTHREE.SkeletonHelper=function(a){this.bones=this.getBoneList(a);for(var b=new THREE.Geometry,c=0;c<this.bones.length;c++)this.bones[c].parent instanceof THREE.Bone&&(b.vertices.push(new THREE.Vector3),b.vertices.push(new THREE.Vector3),b.colors.push(new THREE.Color(0,0,1)),b.colors.push(new THREE.Color(0,1,0)));c=new THREE.LineBasicMaterial({vertexColors:THREE.VertexColors,depthTest:!1,depthWrite:!1,transparent:!0});THREE.Line.call(this,b,c,THREE.LinePieces);this.root=a;this.matrix=a.matrixWorld;\nthis.matrixAutoUpdate=!1;this.update()};THREE.SkeletonHelper.prototype=Object.create(THREE.Line.prototype);THREE.SkeletonHelper.prototype.getBoneList=function(a){var b=[];a instanceof THREE.Bone&&b.push(a);for(var c=0;c<a.children.length;c++)b.push.apply(b,this.getBoneList(a.children[c]));return b};\nTHREE.SkeletonHelper.prototype.update=function(){for(var a=this.geometry,b=(new THREE.Matrix4).getInverse(this.root.matrixWorld),c=new THREE.Matrix4,d=0,e=0;e<this.bones.length;e++){var f=this.bones[e];f.parent instanceof THREE.Bone&&(c.multiplyMatrices(b,f.matrixWorld),a.vertices[d].setFromMatrixPosition(c),c.multiplyMatrices(b,f.parent.matrixWorld),a.vertices[d+1].setFromMatrixPosition(c),d+=2)}a.verticesNeedUpdate=!0;a.computeBoundingSphere()};\nTHREE.SpotLightHelper=function(a){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1;a=new THREE.CylinderGeometry(0,1,1,8,1,!0);a.applyMatrix((new THREE.Matrix4).makeTranslation(0,-.5,0));a.applyMatrix((new THREE.Matrix4).makeRotationX(-Math.PI/2));var b=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});this.cone=new THREE.Mesh(a,b);this.add(this.cone);this.update()};THREE.SpotLightHelper.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.SpotLightHelper.prototype.dispose=function(){this.cone.geometry.dispose();this.cone.material.dispose()};THREE.SpotLightHelper.prototype.update=function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(){var c=this.light.distance?this.light.distance:1E4,d=c*Math.tan(this.light.angle);this.cone.scale.set(d,d,c);a.setFromMatrixPosition(this.light.matrixWorld);b.setFromMatrixPosition(this.light.target.matrixWorld);this.cone.lookAt(b.sub(a));this.cone.material.color.copy(this.light.color).multiplyScalar(this.light.intensity)}}();\nTHREE.VertexNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;b=void 0!==c?c:16711680;d=void 0!==d?d:1;c=new THREE.Geometry;a=a.geometry.faces;for(var e=0,f=a.length;e<f;e++)for(var g=0,h=a[e].vertexNormals.length;g<h;g++)c.vertices.push(new THREE.Vector3,new THREE.Vector3);THREE.Line.call(this,c,new THREE.LineBasicMaterial({color:b,linewidth:d}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.normalMatrix=new THREE.Matrix3;this.update()};THREE.VertexNormalsHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.VertexNormalsHelper.prototype.update=function(a){var b=new THREE.Vector3;return function(a){a=[\"a\",\"b\",\"c\",\"d\"];this.object.updateMatrixWorld(!0);this.normalMatrix.getNormalMatrix(this.object.matrixWorld);for(var d=this.geometry.vertices,e=this.object.geometry.vertices,f=this.object.geometry.faces,g=this.object.matrixWorld,h=0,k=0,n=f.length;k<n;k++)for(var p=f[k],q=0,m=p.vertexNormals.length;q<m;q++){var r=p.vertexNormals[q];d[h].copy(e[p[a[q]]]).applyMatrix4(g);b.copy(r).applyMatrix3(this.normalMatrix).normalize().multiplyScalar(this.size);\nb.add(d[h]);h+=1;d[h].copy(b);h+=1}this.geometry.verticesNeedUpdate=!0;return this}}();\nTHREE.VertexTangentsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;b=void 0!==c?c:255;d=void 0!==d?d:1;c=new THREE.Geometry;a=a.geometry.faces;for(var e=0,f=a.length;e<f;e++)for(var g=0,h=a[e].vertexTangents.length;g<h;g++)c.vertices.push(new THREE.Vector3),c.vertices.push(new THREE.Vector3);THREE.Line.call(this,c,new THREE.LineBasicMaterial({color:b,linewidth:d}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.update()};THREE.VertexTangentsHelper.prototype=Object.create(THREE.Line.prototype);\nTHREE.VertexTangentsHelper.prototype.update=function(a){var b=new THREE.Vector3;return function(a){a=[\"a\",\"b\",\"c\",\"d\"];this.object.updateMatrixWorld(!0);for(var d=this.geometry.vertices,e=this.object.geometry.vertices,f=this.object.geometry.faces,g=this.object.matrixWorld,h=0,k=0,n=f.length;k<n;k++)for(var p=f[k],q=0,m=p.vertexTangents.length;q<m;q++){var r=p.vertexTangents[q];d[h].copy(e[p[a[q]]]).applyMatrix4(g);b.copy(r).transformDirection(g).multiplyScalar(this.size);b.add(d[h]);h+=1;d[h].copy(b);\nh+=1}this.geometry.verticesNeedUpdate=!0;return this}}();\nTHREE.WireframeHelper=function(a,b){var c=void 0!==b?b:16777215,d=[0,0],e={},f=function(a,b){return a-b},g=[\"a\",\"b\",\"c\"],h=new THREE.BufferGeometry;if(a.geometry instanceof THREE.Geometry){for(var k=a.geometry.vertices,n=a.geometry.faces,p=0,q=new Uint32Array(6*n.length),m=0,r=n.length;m<r;m++)for(var t=n[m],s=0;3>s;s++){d[0]=t[g[s]];d[1]=t[g[(s+1)%3]];d.sort(f);var u=d.toString();void 0===e[u]&&(q[2*p]=d[0],q[2*p+1]=d[1],e[u]=!0,p++)}d=new Float32Array(6*p);m=0;for(r=p;m<r;m++)for(s=0;2>s;s++)p=\nk[q[2*m+s]],g=6*m+3*s,d[g+0]=p.x,d[g+1]=p.y,d[g+2]=p.z;h.addAttribute(\"position\",new THREE.BufferAttribute(d,3))}else if(a.geometry instanceof THREE.BufferGeometry){if(void 0!==a.geometry.attributes.index){k=a.geometry.attributes.position.array;r=a.geometry.attributes.index.array;n=a.geometry.drawcalls;p=0;0===n.length&&(n=[{count:r.length,index:0,start:0}]);for(var q=new Uint32Array(2*r.length),t=0,v=n.length;t<v;++t)for(var s=n[t].start,u=n[t].count,g=n[t].index,m=s,y=s+u;m<y;m+=3)for(s=0;3>s;s++)d[0]=\ng+r[m+s],d[1]=g+r[m+(s+1)%3],d.sort(f),u=d.toString(),void 0===e[u]&&(q[2*p]=d[0],q[2*p+1]=d[1],e[u]=!0,p++);d=new Float32Array(6*p);m=0;for(r=p;m<r;m++)for(s=0;2>s;s++)g=6*m+3*s,p=3*q[2*m+s],d[g+0]=k[p],d[g+1]=k[p+1],d[g+2]=k[p+2]}else for(k=a.geometry.attributes.position.array,p=k.length/3,q=p/3,d=new Float32Array(6*p),m=0,r=q;m<r;m++)for(s=0;3>s;s++)g=18*m+6*s,q=9*m+3*s,d[g+0]=k[q],d[g+1]=k[q+1],d[g+2]=k[q+2],p=9*m+(s+1)%3*3,d[g+3]=k[p],d[g+4]=k[p+1],d[g+5]=k[p+2];h.addAttribute(\"position\",new THREE.BufferAttribute(d,\n3))}THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrix=a.matrixWorld;this.matrixAutoUpdate=!1};THREE.WireframeHelper.prototype=Object.create(THREE.Line.prototype);THREE.ImmediateRenderObject=function(){THREE.Object3D.call(this);this.render=function(a){}};THREE.ImmediateRenderObject.prototype=Object.create(THREE.Object3D.prototype);\nTHREE.MorphBlendMesh=function(a,b){THREE.Mesh.call(this,a,b);this.animationsMap={};this.animationsList=[];var c=this.geometry.morphTargets.length;this.createAnimation(\"__default\",0,c-1,c/1);this.setAnimationWeight(\"__default\",1)};THREE.MorphBlendMesh.prototype=Object.create(THREE.Mesh.prototype);\nTHREE.MorphBlendMesh.prototype.createAnimation=function(a,b,c,d){b={startFrame:b,endFrame:c,length:c-b+1,fps:d,duration:(c-b)/d,lastFrame:0,currentFrame:0,active:!1,time:0,direction:1,weight:1,directionBackwards:!1,mirroredLoop:!1};this.animationsMap[a]=b;this.animationsList.push(b)};\nTHREE.MorphBlendMesh.prototype.autoCreateAnimations=function(a){for(var b=/([a-z]+)_?(\\d+)/,c,d={},e=this.geometry,f=0,g=e.morphTargets.length;f<g;f++){var h=e.morphTargets[f].name.match(b);if(h&&1<h.length){var k=h[1];d[k]||(d[k]={start:Infinity,end:-Infinity});h=d[k];f<h.start&&(h.start=f);f>h.end&&(h.end=f);c||(c=k)}}for(k in d)h=d[k],this.createAnimation(k,h.start,h.end,a);this.firstAnimation=c};\nTHREE.MorphBlendMesh.prototype.setAnimationDirectionForward=function(a){if(a=this.animationsMap[a])a.direction=1,a.directionBackwards=!1};THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward=function(a){if(a=this.animationsMap[a])a.direction=-1,a.directionBackwards=!0};THREE.MorphBlendMesh.prototype.setAnimationFPS=function(a,b){var c=this.animationsMap[a];c&&(c.fps=b,c.duration=(c.end-c.start)/c.fps)};\nTHREE.MorphBlendMesh.prototype.setAnimationDuration=function(a,b){var c=this.animationsMap[a];c&&(c.duration=b,c.fps=(c.end-c.start)/c.duration)};THREE.MorphBlendMesh.prototype.setAnimationWeight=function(a,b){var c=this.animationsMap[a];c&&(c.weight=b)};THREE.MorphBlendMesh.prototype.setAnimationTime=function(a,b){var c=this.animationsMap[a];c&&(c.time=b)};THREE.MorphBlendMesh.prototype.getAnimationTime=function(a){var b=0;if(a=this.animationsMap[a])b=a.time;return b};\nTHREE.MorphBlendMesh.prototype.getAnimationDuration=function(a){var b=-1;if(a=this.animationsMap[a])b=a.duration;return b};THREE.MorphBlendMesh.prototype.playAnimation=function(a){var b=this.animationsMap[a];b?(b.time=0,b.active=!0):console.warn(\"animation[\"+a+\"] undefined\")};THREE.MorphBlendMesh.prototype.stopAnimation=function(a){if(a=this.animationsMap[a])a.active=!1};\nTHREE.MorphBlendMesh.prototype.update=function(a){for(var b=0,c=this.animationsList.length;b<c;b++){var d=this.animationsList[b];if(d.active){var e=d.duration/d.length;d.time+=d.direction*a;if(d.mirroredLoop){if(d.time>d.duration||0>d.time)d.direction*=-1,d.time>d.duration&&(d.time=d.duration,d.directionBackwards=!0),0>d.time&&(d.time=0,d.directionBackwards=!1)}else d.time%=d.duration,0>d.time&&(d.time+=d.duration);var f=d.startFrame+THREE.Math.clamp(Math.floor(d.time/e),0,d.length-1),g=d.weight;\nf!==d.currentFrame&&(this.morphTargetInfluences[d.lastFrame]=0,this.morphTargetInfluences[d.currentFrame]=1*g,this.morphTargetInfluences[f]=0,d.lastFrame=d.currentFrame,d.currentFrame=f);e=d.time%e/e;d.directionBackwards&&(e=1-e);this.morphTargetInfluences[d.currentFrame]=e*g;this.morphTargetInfluences[d.lastFrame]=(1-e)*g}}};\n"
  },
  {
    "path": "plugins/Sidebar/media_globe/globe.js",
    "content": "/**\n * dat.globe Javascript WebGL Globe Toolkit\n * http://dataarts.github.com/dat.globe\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\nvar DAT = DAT || {};\n\nDAT.Globe = function(container, opts) {\n  opts = opts || {};\n\n  var colorFn = opts.colorFn || function(x) {\n    var c = new THREE.Color();\n    c.setHSL( ( 0.5 - (x * 2) ), Math.max(0.8, 1.0 - (x * 3)), 0.5 );\n    return c;\n  };\n  var imgDir = opts.imgDir || '/globe/';\n\n  var Shaders = {\n    'earth' : {\n      uniforms: {\n        'texture': { type: 't', value: null }\n      },\n      vertexShader: [\n        'varying vec3 vNormal;',\n        'varying vec2 vUv;',\n        'void main() {',\n          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',\n          'vNormal = normalize( normalMatrix * normal );',\n          'vUv = uv;',\n        '}'\n      ].join('\\n'),\n      fragmentShader: [\n        'uniform sampler2D texture;',\n        'varying vec3 vNormal;',\n        'varying vec2 vUv;',\n        'void main() {',\n          'vec3 diffuse = texture2D( texture, vUv ).xyz;',\n          'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );',\n          'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );',\n          'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );',\n        '}'\n      ].join('\\n')\n    },\n    'atmosphere' : {\n      uniforms: {},\n      vertexShader: [\n        'varying vec3 vNormal;',\n        'void main() {',\n          'vNormal = normalize( normalMatrix * normal );',\n          'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',\n        '}'\n      ].join('\\n'),\n      fragmentShader: [\n        'varying vec3 vNormal;',\n        'void main() {',\n          'float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 12.0 );',\n          'gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity;',\n        '}'\n      ].join('\\n')\n    }\n  };\n\n  var camera, scene, renderer, w, h;\n  var mesh, atmosphere, point, running;\n\n  var overRenderer;\n  var running = true;\n\n  var curZoomSpeed = 0;\n  var zoomSpeed = 50;\n\n  var mouse = { x: 0, y: 0 }, mouseOnDown = { x: 0, y: 0 };\n  var rotation = { x: 0, y: 0 },\n      target = { x: Math.PI*3/2, y: Math.PI / 6.0 },\n      targetOnDown = { x: 0, y: 0 };\n\n  var distance = 100000, distanceTarget = 100000;\n  var padding = 10;\n  var PI_HALF = Math.PI / 2;\n\n  function init() {\n\n    container.style.color = '#fff';\n    container.style.font = '13px/20px Arial, sans-serif';\n\n    var shader, uniforms, material;\n    w = container.offsetWidth || window.innerWidth;\n    h = container.offsetHeight || window.innerHeight;\n\n    camera = new THREE.PerspectiveCamera(30, w / h, 1, 10000);\n    camera.position.z = distance;\n\n    scene = new THREE.Scene();\n\n    var geometry = new THREE.SphereGeometry(200, 40, 30);\n\n    shader = Shaders['earth'];\n    uniforms = THREE.UniformsUtils.clone(shader.uniforms);\n\n    uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir+'world.jpg');\n\n    material = new THREE.ShaderMaterial({\n\n          uniforms: uniforms,\n          vertexShader: shader.vertexShader,\n          fragmentShader: shader.fragmentShader\n\n        });\n\n    mesh = new THREE.Mesh(geometry, material);\n    mesh.rotation.y = Math.PI;\n    scene.add(mesh);\n\n    shader = Shaders['atmosphere'];\n    uniforms = THREE.UniformsUtils.clone(shader.uniforms);\n\n    material = new THREE.ShaderMaterial({\n\n          uniforms: uniforms,\n          vertexShader: shader.vertexShader,\n          fragmentShader: shader.fragmentShader,\n          side: THREE.BackSide,\n          blending: THREE.AdditiveBlending,\n          transparent: true\n\n        });\n\n    mesh = new THREE.Mesh(geometry, material);\n    mesh.scale.set( 1.1, 1.1, 1.1 );\n    scene.add(mesh);\n\n    geometry = new THREE.BoxGeometry(2.75, 2.75, 1);\n    geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,-0.5));\n\n    point = new THREE.Mesh(geometry);\n\n    renderer = new THREE.WebGLRenderer({antialias: true});\n    renderer.setSize(w, h);\n    renderer.setClearColor( 0x212121, 1 );\n\n    renderer.domElement.style.position = 'relative';\n\n    container.appendChild(renderer.domElement);\n\n    container.addEventListener('mousedown', onMouseDown, false);\n\n    if ('onwheel' in document) {\n      container.addEventListener('wheel', onMouseWheel, false);\n    } else {\n      container.addEventListener('mousewheel', onMouseWheel, false);\n    }\n\n    document.addEventListener('keydown', onDocumentKeyDown, false);\n\n    window.addEventListener('resize', onWindowResize, false);\n\n    container.addEventListener('mouseover', function() {\n      overRenderer = true;\n    }, false);\n\n    container.addEventListener('mouseout', function() {\n      overRenderer = false;\n    }, false);\n  }\n\n  function addData(data, opts) {\n    var lat, lng, size, color, i, step, colorFnWrapper;\n\n    opts.animated = opts.animated || false;\n    this.is_animated = opts.animated;\n    opts.format = opts.format || 'magnitude'; // other option is 'legend'\n    if (opts.format === 'magnitude') {\n      step = 3;\n      colorFnWrapper = function(data, i) { return colorFn(data[i+2]); }\n    } else if (opts.format === 'legend') {\n      step = 4;\n      colorFnWrapper = function(data, i) { return colorFn(data[i+3]); }\n    } else if (opts.format === 'peer') {\n      colorFnWrapper = function(data, i) { return colorFn(data[i+2]); }\n    } else {\n      throw('error: format not supported: '+opts.format);\n    }\n\n    if (opts.animated) {\n      if (this._baseGeometry === undefined) {\n        this._baseGeometry = new THREE.Geometry();\n        for (i = 0; i < data.length; i += step) {\n          lat = data[i];\n          lng = data[i + 1];\n//        size = data[i + 2];\n          color = colorFnWrapper(data,i);\n          size = 0;\n          addPoint(lat, lng, size, color, this._baseGeometry);\n        }\n      }\n      if(this._morphTargetId === undefined) {\n        this._morphTargetId = 0;\n      } else {\n        this._morphTargetId += 1;\n      }\n      opts.name = opts.name || 'morphTarget'+this._morphTargetId;\n    }\n    var subgeo = new THREE.Geometry();\n    for (i = 0; i < data.length; i += step) {\n      lat = data[i];\n      lng = data[i + 1];\n      color = colorFnWrapper(data,i);\n      size = data[i + 2];\n      size = size*200;\n      addPoint(lat, lng, size, color, subgeo);\n    }\n    if (opts.animated) {\n      this._baseGeometry.morphTargets.push({'name': opts.name, vertices: subgeo.vertices});\n    } else {\n      this._baseGeometry = subgeo;\n    }\n\n  };\n\n  function createPoints() {\n    if (this._baseGeometry !== undefined) {\n      if (this.is_animated === false) {\n        this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({\n              color: 0xffffff,\n              vertexColors: THREE.FaceColors,\n              morphTargets: false\n            }));\n      } else {\n        if (this._baseGeometry.morphTargets.length < 8) {\n          console.log('t l',this._baseGeometry.morphTargets.length);\n          var padding = 8-this._baseGeometry.morphTargets.length;\n          console.log('padding', padding);\n          for(var i=0; i<=padding; i++) {\n            console.log('padding',i);\n            this._baseGeometry.morphTargets.push({'name': 'morphPadding'+i, vertices: this._baseGeometry.vertices});\n          }\n        }\n        this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({\n              color: 0xffffff,\n              vertexColors: THREE.FaceColors,\n              morphTargets: true\n            }));\n      }\n      scene.add(this.points);\n    }\n  }\n\n  function addPoint(lat, lng, size, color, subgeo) {\n\n    var phi = (90 - lat) * Math.PI / 180;\n    var theta = (180 - lng) * Math.PI / 180;\n\n    point.position.x = 200 * Math.sin(phi) * Math.cos(theta);\n    point.position.y = 200 * Math.cos(phi);\n    point.position.z = 200 * Math.sin(phi) * Math.sin(theta);\n\n    point.lookAt(mesh.position);\n\n    point.scale.z = Math.max( size, 0.1 ); // avoid non-invertible matrix\n    point.updateMatrix();\n\n    for (var i = 0; i < point.geometry.faces.length; i++) {\n\n      point.geometry.faces[i].color = color;\n\n    }\n    if(point.matrixAutoUpdate){\n      point.updateMatrix();\n    }\n    subgeo.merge(point.geometry, point.matrix);\n  }\n\n  function onMouseDown(event) {\n    event.preventDefault();\n\n    container.addEventListener('mousemove', onMouseMove, false);\n    container.addEventListener('mouseup', onMouseUp, false);\n    container.addEventListener('mouseout', onMouseOut, false);\n\n    mouseOnDown.x = - event.clientX;\n    mouseOnDown.y = event.clientY;\n\n    targetOnDown.x = target.x;\n    targetOnDown.y = target.y;\n\n    container.style.cursor = 'move';\n  }\n\n  function onMouseMove(event) {\n    mouse.x = - event.clientX;\n    mouse.y = event.clientY;\n\n    var zoomDamp = distance/1000;\n\n    target.x = targetOnDown.x + (mouse.x - mouseOnDown.x) * 0.005 * zoomDamp;\n    target.y = targetOnDown.y + (mouse.y - mouseOnDown.y) * 0.005 * zoomDamp;\n\n    target.y = target.y > PI_HALF ? PI_HALF : target.y;\n    target.y = target.y < - PI_HALF ? - PI_HALF : target.y;\n  }\n\n  function onMouseUp(event) {\n    container.removeEventListener('mousemove', onMouseMove, false);\n    container.removeEventListener('mouseup', onMouseUp, false);\n    container.removeEventListener('mouseout', onMouseOut, false);\n    container.style.cursor = 'auto';\n  }\n\n  function onMouseOut(event) {\n    container.removeEventListener('mousemove', onMouseMove, false);\n    container.removeEventListener('mouseup', onMouseUp, false);\n    container.removeEventListener('mouseout', onMouseOut, false);\n  }\n\n  function onMouseWheel(event) {\n    if (container.style.cursor != \"move\") return false;\n    event.preventDefault();\n    if (overRenderer) {\n      if (event.deltaY) {\n        zoom(-event.deltaY * (event.deltaMode == 0 ? 1 : 50));\n      } else {\n        zoom(event.wheelDeltaY * 0.3);\n      }\n    }\n    return false;\n  }\n\n  function onDocumentKeyDown(event) {\n    switch (event.keyCode) {\n      case 38:\n        zoom(100);\n        event.preventDefault();\n        break;\n      case 40:\n        zoom(-100);\n        event.preventDefault();\n        break;\n    }\n  }\n\n  function onWindowResize( event ) {\n    camera.aspect = container.offsetWidth / container.offsetHeight;\n    camera.updateProjectionMatrix();\n    renderer.setSize( container.offsetWidth, container.offsetHeight );\n  }\n\n  function zoom(delta) {\n    distanceTarget -= delta;\n    distanceTarget = distanceTarget > 855 ? 855 : distanceTarget;\n    distanceTarget = distanceTarget < 350 ? 350 : distanceTarget;\n  }\n\n  function animate() {\n    if (!running) return\n    requestAnimationFrame(animate);\n    render();\n  }\n\n  function render() {\n    zoom(curZoomSpeed);\n\n    rotation.x += (target.x - rotation.x) * 0.1;\n    rotation.y += (target.y - rotation.y) * 0.1;\n    distance += (distanceTarget - distance) * 0.3;\n\n    camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y);\n    camera.position.y = distance * Math.sin(rotation.y);\n    camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y);\n\n    camera.lookAt(mesh.position);\n\n    renderer.render(scene, camera);\n  }\n\n  function unload() {\n    running = false\n    container.removeEventListener('mousedown', onMouseDown, false);\n    if ('onwheel' in document) {\n      container.removeEventListener('wheel', onMouseWheel, false);\n    } else {\n      container.removeEventListener('mousewheel', onMouseWheel, false);\n    }\n    document.removeEventListener('keydown', onDocumentKeyDown, false);\n    window.removeEventListener('resize', onWindowResize, false);\n\n  }\n\n  init();\n  this.animate = animate;\n  this.unload = unload;\n\n\n  this.__defineGetter__('time', function() {\n    return this._time || 0;\n  });\n\n  this.__defineSetter__('time', function(t) {\n    var validMorphs = [];\n    var morphDict = this.points.morphTargetDictionary;\n    for(var k in morphDict) {\n      if(k.indexOf('morphPadding') < 0) {\n        validMorphs.push(morphDict[k]);\n      }\n    }\n    validMorphs.sort();\n    var l = validMorphs.length-1;\n    var scaledt = t*l+1;\n    var index = Math.floor(scaledt);\n    for (i=0;i<validMorphs.length;i++) {\n      this.points.morphTargetInfluences[validMorphs[i]] = 0;\n    }\n    var lastIndex = index - 1;\n    var leftover = scaledt - index;\n    if (lastIndex >= 0) {\n      this.points.morphTargetInfluences[lastIndex] = 1 - leftover;\n    }\n    this.points.morphTargetInfluences[index] = leftover;\n    this._time = t;\n  });\n\n  this.addData = addData;\n  this.createPoints = createPoints;\n  this.renderer = renderer;\n  this.scene = scene;\n\n  return this;\n\n};\n"
  },
  {
    "path": "plugins/Sidebar/plugin_info.json",
    "content": "{\n\t\"name\": \"Sidebar\",\n\t\"description\": \"Access site management sidebar and console by dragging top-right 0 button to left or down.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/Stats/StatsPlugin.py",
    "content": "import time\nimport html\nimport os\nimport json\nimport sys\nimport itertools\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom util import helper\nfrom Debug import Debug\nfrom Db import Db\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n\n    def formatTableRow(self, row, class_name=\"\"):\n        back = []\n        for format, val in row:\n            if val is None:\n                formatted = \"n/a\"\n            elif format == \"since\":\n                if val:\n                    formatted = \"%.0f\" % (time.time() - val)\n                else:\n                    formatted = \"n/a\"\n            else:\n                formatted = format % val\n            back.append(\"<td>%s</td>\" % formatted)\n        return \"<tr class='%s'>%s</tr>\" % (class_name, \"\".join(back))\n\n    def getObjSize(self, obj, hpy=None):\n        if hpy:\n            return float(hpy.iso(obj).domisize) / 1024\n        else:\n            return 0\n\n    def renderHead(self):\n        import main\n        from Crypt import CryptConnection\n\n        # Memory\n        yield \"rev%s | \" % config.rev\n        yield \"%s | \" % main.file_server.ip_external_list\n        yield \"Port: %s | \" % main.file_server.port\n        yield \"Network: %s | \" % main.file_server.supported_ip_types\n        yield \"Opened: %s | \" % main.file_server.port_opened\n        yield \"Crypt: %s, TLSv1.3: %s | \" % (CryptConnection.manager.crypt_supported, CryptConnection.ssl.HAS_TLSv1_3)\n        yield \"In: %.2fMB, Out: %.2fMB  | \" % (\n            float(main.file_server.bytes_recv) / 1024 / 1024,\n            float(main.file_server.bytes_sent) / 1024 / 1024\n        )\n        yield \"Peerid: %s  | \" % main.file_server.peer_id\n        yield \"Time: %.2fs | \" % main.file_server.getTimecorrection()\n        yield \"Blocks: %s\" % Debug.num_block\n\n        try:\n            import psutil\n            process = psutil.Process(os.getpid())\n            mem = process.get_memory_info()[0] / float(2 ** 20)\n            yield \"Mem: %.2fMB | \" % mem\n            yield \"Threads: %s | \" % len(process.threads())\n            yield \"CPU: usr %.2fs sys %.2fs | \" % process.cpu_times()\n            yield \"Files: %s | \" % len(process.open_files())\n            yield \"Sockets: %s | \" % len(process.connections())\n            yield \"Calc size <a href='?size=1'>on</a> <a href='?size=0'>off</a>\"\n        except Exception:\n            pass\n        yield \"<br>\"\n\n    def renderConnectionsTable(self):\n        import main\n\n        # Connections\n        yield \"<b>Connections</b> (%s, total made: %s, in: %s, out: %s):<br>\" % (\n            len(main.file_server.connections), main.file_server.last_connection_id,\n            main.file_server.num_incoming, main.file_server.num_outgoing\n        )\n        yield \"<table class='connections'><tr> <th>id</th> <th>type</th> <th>ip</th> <th>open</th> <th>crypt</th> <th>ping</th>\"\n        yield \"<th>buff</th> <th>bad</th> <th>idle</th> <th>open</th> <th>delay</th> <th>cpu</th> <th>out</th> <th>in</th> <th>last sent</th>\"\n        yield \"<th>wait</th> <th>version</th> <th>time</th> <th>sites</th> </tr>\"\n        for connection in main.file_server.connections:\n            if \"cipher\" in dir(connection.sock):\n                cipher = connection.sock.cipher()[0]\n                tls_version = connection.sock.version()\n            else:\n                cipher = connection.crypt\n                tls_version = \"\"\n            if \"time\" in connection.handshake and connection.last_ping_delay:\n                time_correction = connection.handshake[\"time\"] - connection.handshake_time - connection.last_ping_delay\n            else:\n                time_correction = 0.0\n            yield self.formatTableRow([\n                (\"%3d\", connection.id),\n                (\"%s\", connection.type),\n                (\"%s:%s\", (connection.ip, connection.port)),\n                (\"%s\", connection.handshake.get(\"port_opened\")),\n                (\"<span title='%s %s'>%s</span>\", (cipher, tls_version, connection.crypt)),\n                (\"%6.3f\", connection.last_ping_delay),\n                (\"%s\", connection.incomplete_buff_recv),\n                (\"%s\", connection.bad_actions),\n                (\"since\", max(connection.last_send_time, connection.last_recv_time)),\n                (\"since\", connection.start_time),\n                (\"%.3f\", max(-1, connection.last_sent_time - connection.last_send_time)),\n                (\"%.3f\", connection.cpu_time),\n                (\"%.0fk\", connection.bytes_sent / 1024),\n                (\"%.0fk\", connection.bytes_recv / 1024),\n                (\"<span title='Recv: %s'>%s</span>\", (connection.last_cmd_recv, connection.last_cmd_sent)),\n                (\"%s\", list(connection.waiting_requests.keys())),\n                (\"%s r%s\", (connection.handshake.get(\"version\"), connection.handshake.get(\"rev\", \"?\"))),\n                (\"%.2fs\", time_correction),\n                (\"%s\", connection.sites)\n            ])\n        yield \"</table>\"\n\n    def renderTrackers(self):\n        # Trackers\n        yield \"<br><br><b>Trackers:</b><br>\"\n        yield \"<table class='trackers'><tr> <th>address</th> <th>request</th> <th>successive errors</th> <th>last_request</th></tr>\"\n        from Site import SiteAnnouncer  # importing at the top of the file breaks plugins\n        for tracker_address, tracker_stat in sorted(SiteAnnouncer.global_stats.items()):\n            yield self.formatTableRow([\n                (\"%s\", tracker_address),\n                (\"%s\", tracker_stat[\"num_request\"]),\n                (\"%s\", tracker_stat[\"num_error\"]),\n                (\"%.0f min ago\", min(999, (time.time() - tracker_stat[\"time_request\"]) / 60))\n            ])\n        yield \"</table>\"\n\n        if \"AnnounceShare\" in PluginManager.plugin_manager.plugin_names:\n            yield \"<br><br><b>Shared trackers:</b><br>\"\n            yield \"<table class='trackers'><tr> <th>address</th> <th>added</th> <th>found</th> <th>latency</th> <th>successive errors</th> <th>last_success</th></tr>\"\n            from AnnounceShare import AnnounceSharePlugin\n            for tracker_address, tracker_stat in sorted(AnnounceSharePlugin.tracker_storage.getTrackers().items()):\n                yield self.formatTableRow([\n                    (\"%s\", tracker_address),\n                    (\"%.0f min ago\", min(999, (time.time() - tracker_stat[\"time_added\"]) / 60)),\n                    (\"%.0f min ago\", min(999, (time.time() - tracker_stat.get(\"time_found\", 0)) / 60)),\n                    (\"%.3fs\", tracker_stat[\"latency\"]),\n                    (\"%s\", tracker_stat[\"num_error\"]),\n                    (\"%.0f min ago\", min(999, (time.time() - tracker_stat[\"time_success\"]) / 60)),\n                ])\n            yield \"</table>\"\n\n    def renderTor(self):\n        import main\n        yield \"<br><br><b>Tor hidden services (status: %s):</b><br>\" % main.file_server.tor_manager.status\n        for site_address, onion in list(main.file_server.tor_manager.site_onions.items()):\n            yield \"- %-34s: %s<br>\" % (site_address, onion)\n\n    def renderDbStats(self):\n        yield \"<br><br><b>Db</b>:<br>\"\n        for db in Db.opened_dbs:\n            tables = [row[\"name\"] for row in db.execute(\"SELECT name FROM sqlite_master WHERE type = 'table'\").fetchall()]\n            table_rows = {}\n            for table in tables:\n                table_rows[table] = db.execute(\"SELECT COUNT(*) AS c FROM %s\" % table).fetchone()[\"c\"]\n            db_size = os.path.getsize(db.db_path) / 1024.0 / 1024.0\n            yield \"- %.3fs: %s %.3fMB, table rows: %s<br>\" % (\n                time.time() - db.last_query_time, db.db_path, db_size, json.dumps(table_rows, sort_keys=True)\n            )\n\n    def renderSites(self):\n        yield \"<br><br><b>Sites</b>:\"\n        yield \"<table>\"\n        yield \"<tr><th>address</th> <th>connected</th> <th title='connected/good/total'>peers</th> <th>content.json</th> <th>out</th> <th>in</th>  </tr>\"\n        for site in list(self.server.sites.values()):\n            yield self.formatTableRow([\n                (\n                    \"\"\"<a href='#' onclick='document.getElementById(\"peers_%s\").style.display=\"initial\"; return false'>%s</a>\"\"\",\n                    (site.address, site.address)\n                ),\n                (\"%s\", [peer.connection.id for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]),\n                (\"%s/%s/%s\", (\n                    len([peer for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]),\n                    len(site.getConnectablePeers(100)),\n                    len(site.peers)\n                )),\n                (\"%s (loaded: %s)\", (\n                    len(site.content_manager.contents),\n                    len([key for key, val in dict(site.content_manager.contents).items() if val])\n                )),\n                (\"%.0fk\", site.settings.get(\"bytes_sent\", 0) / 1024),\n                (\"%.0fk\", site.settings.get(\"bytes_recv\", 0) / 1024),\n            ], \"serving-%s\" % site.settings[\"serving\"])\n            yield \"<tr><td id='peers_%s' style='display: none; white-space: pre' colspan=6>\" % site.address\n            for key, peer in list(site.peers.items()):\n                if peer.time_found:\n                    time_found = int(time.time() - peer.time_found) / 60\n                else:\n                    time_found = \"--\"\n                if peer.connection:\n                    connection_id = peer.connection.id\n                else:\n                    connection_id = None\n                if site.content_manager.has_optional_files:\n                    yield \"Optional files: %4s \" % len(peer.hashfield)\n                time_added = (time.time() - peer.time_added) / (60 * 60 * 24)\n                yield \"(#%4s, rep: %2s, err: %s, found: %.1fs min, add: %.1f day) %30s -<br>\" % (connection_id, peer.reputation, peer.connection_error, time_found, time_added, key)\n            yield \"<br></td></tr>\"\n        yield \"</table>\"\n\n    def renderBigfiles(self):\n        yield \"<br><br><b>Big files</b>:<br>\"\n        for site in list(self.server.sites.values()):\n            if not site.settings.get(\"has_bigfile\"):\n                continue\n            bigfiles = {}\n            yield \"\"\"<a href=\"#\" onclick='document.getElementById(\"bigfiles_%s\").style.display=\"initial\"; return false'>%s</a><br>\"\"\" % (site.address, site.address)\n            for peer in list(site.peers.values()):\n                if not peer.time_piecefields_updated:\n                    continue\n                for sha512, piecefield in peer.piecefields.items():\n                    if sha512 not in bigfiles:\n                        bigfiles[sha512] = []\n                    bigfiles[sha512].append(peer)\n\n            yield \"<div id='bigfiles_%s' style='display: none'>\" % site.address\n            for sha512, peers in bigfiles.items():\n                yield \"<br> - \" + sha512 + \" (hash id: %s)<br>\" % site.content_manager.hashfield.getHashId(sha512)\n                yield \"<table>\"\n                for peer in peers:\n                    yield \"<tr><td>\" + peer.key + \"</td><td>\" + peer.piecefields[sha512].tostring() + \"</td></tr>\"\n                yield \"</table>\"\n            yield \"</div>\"\n\n    def renderRequests(self):\n        import main\n        yield \"<div style='float: left'>\"\n        yield \"<br><br><b>Sent commands</b>:<br>\"\n        yield \"<table>\"\n        for stat_key, stat in sorted(main.file_server.stat_sent.items(), key=lambda i: i[1][\"bytes\"], reverse=True):\n            yield \"<tr><td>%s</td><td style='white-space: nowrap'>x %s =</td><td>%.0fkB</td></tr>\" % (stat_key, stat[\"num\"], stat[\"bytes\"] / 1024)\n        yield \"</table>\"\n        yield \"</div>\"\n\n        yield \"<div style='float: left; margin-left: 20%; max-width: 50%'>\"\n        yield \"<br><br><b>Received commands</b>:<br>\"\n        yield \"<table>\"\n        for stat_key, stat in sorted(main.file_server.stat_recv.items(), key=lambda i: i[1][\"bytes\"], reverse=True):\n            yield \"<tr><td>%s</td><td style='white-space: nowrap'>x %s =</td><td>%.0fkB</td></tr>\" % (stat_key, stat[\"num\"], stat[\"bytes\"] / 1024)\n        yield \"</table>\"\n        yield \"</div>\"\n        yield \"<div style='clear: both'></div>\"\n\n    def renderMemory(self):\n        import gc\n        from Ui import UiRequest\n\n        hpy = None\n        if self.get.get(\"size\") == \"1\":  # Calc obj size\n            try:\n                import guppy\n                hpy = guppy.hpy()\n            except Exception:\n                pass\n        self.sendHeader()\n\n        # Object types\n\n        obj_count = {}\n        for obj in gc.get_objects():\n            obj_type = str(type(obj))\n            if obj_type not in obj_count:\n                obj_count[obj_type] = [0, 0]\n            obj_count[obj_type][0] += 1  # Count\n            obj_count[obj_type][1] += float(sys.getsizeof(obj)) / 1024  # Size\n\n        yield \"<br><br><b>Objects in memory (types: %s, total: %s, %.2fkb):</b><br>\" % (\n            len(obj_count),\n            sum([stat[0] for stat in list(obj_count.values())]),\n            sum([stat[1] for stat in list(obj_count.values())])\n        )\n\n        for obj, stat in sorted(list(obj_count.items()), key=lambda x: x[1][0], reverse=True):  # Sorted by count\n            yield \" - %.1fkb = %s x <a href=\\\"/Listobj?type=%s\\\">%s</a><br>\" % (stat[1], stat[0], obj, html.escape(obj))\n\n        # Classes\n\n        class_count = {}\n        for obj in gc.get_objects():\n            obj_type = str(type(obj))\n            if obj_type != \"<type 'instance'>\":\n                continue\n            class_name = obj.__class__.__name__\n            if class_name not in class_count:\n                class_count[class_name] = [0, 0]\n            class_count[class_name][0] += 1  # Count\n            class_count[class_name][1] += float(sys.getsizeof(obj)) / 1024  # Size\n\n        yield \"<br><br><b>Classes in memory (types: %s, total: %s, %.2fkb):</b><br>\" % (\n            len(class_count),\n            sum([stat[0] for stat in list(class_count.values())]),\n            sum([stat[1] for stat in list(class_count.values())])\n        )\n\n        for obj, stat in sorted(list(class_count.items()), key=lambda x: x[1][0], reverse=True):  # Sorted by count\n            yield \" - %.1fkb = %s x <a href=\\\"/Dumpobj?class=%s\\\">%s</a><br>\" % (stat[1], stat[0], obj, html.escape(obj))\n\n        from greenlet import greenlet\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)]\n        yield \"<br>Greenlets (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        from Worker import Worker\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)]\n        yield \"<br>Workers (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        from Connection import Connection\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, Connection)]\n        yield \"<br>Connections (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        from socket import socket\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, socket)]\n        yield \"<br>Sockets (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        from msgpack import Unpacker\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, Unpacker)]\n        yield \"<br>Msgpack unpacker (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        from Site.Site import Site\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, Site)]\n        yield \"<br>Sites (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, self.server.log.__class__)]\n        yield \"<br>Loggers (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj.name)))\n\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, UiRequest)]\n        yield \"<br>UiRequests (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        from Peer import Peer\n        objs = [obj for obj in gc.get_objects() if isinstance(obj, Peer)]\n        yield \"<br>Peers (%s):<br>\" % len(objs)\n        for obj in objs:\n            yield \" - %.1fkb: %s<br>\" % (self.getObjSize(obj, hpy), html.escape(repr(obj)))\n\n        objs = [(key, val) for key, val in sys.modules.items() if val is not None]\n        objs.sort()\n        yield \"<br>Modules (%s):<br>\" % len(objs)\n        for module_name, module in objs:\n            yield \" - %.3fkb: %s %s<br>\" % (self.getObjSize(module, hpy), module_name, html.escape(repr(module)))\n\n    # /Stats entry point\n    @helper.encodeResponse\n    def actionStats(self):\n        import gc\n\n        self.sendHeader()\n\n        if \"Multiuser\" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:\n            yield \"This function is disabled on this proxy\"\n            return\n\n        s = time.time()\n\n        # Style\n        yield \"\"\"\n        <style>\n         * { font-family: monospace }\n         table td, table th { text-align: right; padding: 0px 10px }\n         .connections td { white-space: nowrap }\n         .serving-False { opacity: 0.3 }\n        </style>\n        \"\"\"\n\n        renderers = [\n            self.renderHead(),\n            self.renderConnectionsTable(),\n            self.renderTrackers(),\n            self.renderTor(),\n            self.renderDbStats(),\n            self.renderSites(),\n            self.renderBigfiles(),\n            self.renderRequests()\n\n        ]\n\n        for part in itertools.chain(*renderers):\n            yield part\n\n        if config.debug:\n            for part in self.renderMemory():\n                yield part\n\n        gc.collect()  # Implicit grabage collection\n        yield \"Done in %.1f\" % (time.time() - s)\n\n    @helper.encodeResponse\n    def actionDumpobj(self):\n\n        import gc\n        import sys\n\n        self.sendHeader()\n\n        if \"Multiuser\" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:\n            yield \"This function is disabled on this proxy\"\n            return\n\n        # No more if not in debug mode\n        if not config.debug:\n            yield \"Not in debug mode\"\n            return\n\n        class_filter = self.get.get(\"class\")\n\n        yield \"\"\"\n        <style>\n         * { font-family: monospace; white-space: pre }\n         table * { text-align: right; padding: 0px 10px }\n        </style>\n        \"\"\"\n\n        objs = gc.get_objects()\n        for obj in objs:\n            obj_type = str(type(obj))\n            if obj_type != \"<type 'instance'>\" or obj.__class__.__name__ != class_filter:\n                continue\n            yield \"%.1fkb %s... \" % (float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)))\n            for attr in dir(obj):\n                yield \"- %s: %s<br>\" % (attr, html.escape(str(getattr(obj, attr))))\n            yield \"<br>\"\n\n        gc.collect()  # Implicit grabage collection\n\n    @helper.encodeResponse\n    def actionListobj(self):\n\n        import gc\n        import sys\n\n        self.sendHeader()\n\n        if \"Multiuser\" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:\n            yield \"This function is disabled on this proxy\"\n            return\n\n        # No more if not in debug mode\n        if not config.debug:\n            yield \"Not in debug mode\"\n            return\n\n        type_filter = self.get.get(\"type\")\n\n        yield \"\"\"\n        <style>\n         * { font-family: monospace; white-space: pre }\n         table * { text-align: right; padding: 0px 10px }\n        </style>\n        \"\"\"\n\n        yield \"Listing all %s objects in memory...<br>\" % html.escape(type_filter)\n\n        ref_count = {}\n        objs = gc.get_objects()\n        for obj in objs:\n            obj_type = str(type(obj))\n            if obj_type != type_filter:\n                continue\n            refs = [\n                ref for ref in gc.get_referrers(obj)\n                if hasattr(ref, \"__class__\") and\n                ref.__class__.__name__ not in [\"list\", \"dict\", \"function\", \"type\", \"frame\", \"WeakSet\", \"tuple\"]\n            ]\n            if not refs:\n                continue\n            try:\n                yield \"%.1fkb <span title=\\\"%s\\\">%s</span>... \" % (\n                    float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)), html.escape(str(obj)[0:100].ljust(100))\n                )\n            except Exception:\n                continue\n            for ref in refs:\n                yield \" [\"\n                if \"object at\" in str(ref) or len(str(ref)) > 100:\n                    yield str(ref.__class__.__name__)\n                else:\n                    yield str(ref.__class__.__name__) + \":\" + html.escape(str(ref))\n                yield \"] \"\n                ref_type = ref.__class__.__name__\n                if ref_type not in ref_count:\n                    ref_count[ref_type] = [0, 0]\n                ref_count[ref_type][0] += 1  # Count\n                ref_count[ref_type][1] += float(sys.getsizeof(obj)) / 1024  # Size\n            yield \"<br>\"\n\n        yield \"<br>Object referrer (total: %s, %.2fkb):<br>\" % (len(ref_count), sum([stat[1] for stat in list(ref_count.values())]))\n\n        for obj, stat in sorted(list(ref_count.items()), key=lambda x: x[1][0], reverse=True)[0:30]:  # Sorted by count\n            yield \" - %.1fkb = %s x %s<br>\" % (stat[1], stat[0], html.escape(str(obj)))\n\n        gc.collect()  # Implicit grabage collection\n\n    @helper.encodeResponse\n    def actionGcCollect(self):\n        import gc\n        self.sendHeader()\n        yield str(gc.collect())\n\n    # /About entry point\n    @helper.encodeResponse\n    def actionEnv(self):\n        import main\n\n        self.sendHeader()\n\n        yield \"\"\"\n        <style>\n         * { font-family: monospace; white-space: pre; }\n         h2 { font-size: 100%; margin-bottom: 0px; }\n         small { opacity: 0.5; }\n         table { border-collapse: collapse; }\n         td { padding-right: 10px; }\n        </style>\n        \"\"\"\n\n        if \"Multiuser\" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:\n            yield \"This function is disabled on this proxy\"\n            return\n\n        yield from main.actions.testEnv(format=\"html\")\n\n\n@PluginManager.registerTo(\"Actions\")\nclass ActionsPlugin:\n    def formatTable(self, *rows, format=\"text\"):\n        if format == \"html\":\n            return self.formatTableHtml(*rows)\n        else:\n            return self.formatTableText(*rows)\n\n    def formatHead(self, title, format=\"text\"):\n        if format == \"html\":\n            return \"<h2>%s</h2>\" % title\n        else:\n            return \"\\n* %s\\n\" % title\n\n    def formatTableHtml(self, *rows):\n        yield \"<table>\"\n        for row in rows:\n            yield \"<tr>\"\n            for col in row:\n                yield \"<td>%s</td>\" % html.escape(str(col))\n            yield \"</tr>\"\n        yield \"</table>\"\n\n    def formatTableText(self, *rows):\n        for row in rows:\n            yield \" \"\n            for col in row:\n                yield \" \" + str(col)\n            yield \"\\n\"\n\n    def testEnv(self, format=\"text\"):\n        import gevent\n        import msgpack\n        import pkg_resources\n        import importlib\n        import coincurve\n        import sqlite3\n        from Crypt import CryptBitcoin\n\n        yield \"\\n\"\n\n        yield from self.formatTable(\n            [\"ZeroNet version:\", \"%s rev%s\" % (config.version, config.rev)],\n            [\"Python:\", \"%s\" % sys.version],\n            [\"Platform:\", \"%s\" % sys.platform],\n            [\"Crypt verify lib:\", \"%s\" % CryptBitcoin.lib_verify_best],\n            [\"OpenSSL:\", \"%s\" % CryptBitcoin.sslcrypto.ecc.get_backend()],\n            [\"Libsecp256k1:\", \"%s\" % type(coincurve._libsecp256k1.lib).__name__],\n            [\"SQLite:\", \"%s, API: %s\" % (sqlite3.sqlite_version, sqlite3.version)],\n            format=format\n        )\n\n\n        yield self.formatHead(\"Libraries:\")\n        rows = []\n        for lib_name in [\"gevent\", \"greenlet\", \"msgpack\", \"base58\", \"merkletools\", \"rsa\", \"socks\", \"pyasn1\", \"gevent_ws\", \"websocket\", \"maxminddb\"]:\n            try:\n                module = importlib.import_module(lib_name)\n                if \"__version__\" in dir(module):\n                    version = module.__version__\n                elif \"version\" in dir(module):\n                    version = module.version\n                else:\n                    version = \"unknown version\"\n\n                if type(version) is tuple:\n                    version = \".\".join(map(str, version))\n\n                rows.append([\"- %s:\" % lib_name, version, \"at \" + module.__file__])\n            except Exception as err:\n                rows.append([\"! Error importing %s:\", repr(err)])\n\n            \"\"\"\n            try:\n                yield \" - %s<br>\" % html.escape(repr(pkg_resources.get_distribution(lib_name)))\n            except Exception as err:\n                yield \" ! %s<br>\" % html.escape(repr(err))\n            \"\"\"\n\n        yield from self.formatTable(*rows, format=format)\n\n        yield self.formatHead(\"Library config:\", format=format)\n\n        yield from self.formatTable(\n            [\"- gevent:\", gevent.config.loop.__module__],\n            [\"- msgpack unpacker:\", msgpack.Unpacker.__module__],\n            format=format\n        )\n"
  },
  {
    "path": "plugins/Stats/__init__.py",
    "content": "from . import StatsPlugin"
  },
  {
    "path": "plugins/Stats/plugin_info.json",
    "content": "{\n\t\"name\": \"Stats\",\n\t\"description\": \"/Stats and /Benchmark pages.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/TranslateSite/TranslateSitePlugin.py",
    "content": "import time\n\nfrom Plugin import PluginManager\nfrom Translate import translate\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def actionSiteMedia(self, path, **kwargs):\n        file_name = path.split(\"/\")[-1].lower()\n        if not file_name:  # Path ends with /\n            file_name = \"index.html\"\n        extension = file_name.split(\".\")[-1]\n\n        if extension == \"html\":  # Always replace translate variables in html files\n            should_translate = True\n        elif extension == \"js\" and translate.lang != \"en\":\n            should_translate = True\n        else:\n            should_translate = False\n\n        if should_translate:\n            path_parts = self.parsePath(path)\n            kwargs[\"header_length\"] = False\n            file_generator = super(UiRequestPlugin, self).actionSiteMedia(path, **kwargs)\n            if \"__next__\" in dir(file_generator):  # File found and generator returned\n                site = self.server.sites.get(path_parts[\"address\"])\n                if not site or not site.content_manager.contents.get(\"content.json\"):\n                    return file_generator\n                return self.actionPatchFile(site, path_parts[\"inner_path\"], file_generator)\n            else:\n                return file_generator\n\n        else:\n            return super(UiRequestPlugin, self).actionSiteMedia(path, **kwargs)\n\n    def actionUiMedia(self, path):\n        file_generator = super(UiRequestPlugin, self).actionUiMedia(path)\n        if translate.lang != \"en\" and path.endswith(\".js\"):\n            s = time.time()\n            data = b\"\".join(list(file_generator))\n            data = translate.translateData(data.decode(\"utf8\"))\n            self.log.debug(\"Patched %s (%s bytes) in %.3fs\" % (path, len(data), time.time() - s))\n            return iter([data.encode(\"utf8\")])\n        else:\n            return file_generator\n\n    def actionPatchFile(self, site, inner_path, file_generator):\n        content_json = site.content_manager.contents.get(\"content.json\")\n        lang_file = \"languages/%s.json\" % translate.lang\n        lang_file_exist = False\n        if site.settings.get(\"own\"):  # My site, check if the file is exist (allow to add new lang without signing)\n            if site.storage.isFile(lang_file):\n                lang_file_exist = True\n        else:  # Not my site the reference in content.json is enough (will wait for download later)\n            if lang_file in content_json.get(\"files\", {}):\n                lang_file_exist = True\n\n        if not lang_file_exist or inner_path not in content_json.get(\"translate\", []):\n            for part in file_generator:\n                if inner_path.endswith(\".html\"):\n                    yield part.replace(b\"lang={lang}\", b\"lang=\" + translate.lang.encode(\"utf8\"))  # lang get parameter to .js file to avoid cache\n                else:\n                    yield part\n        else:\n            s = time.time()\n            data = b\"\".join(list(file_generator)).decode(\"utf8\")\n\n            # if site.content_manager.contents[\"content.json\"][\"files\"].get(lang_file):\n            site.needFile(lang_file, priority=10)\n            try:\n                if inner_path.endswith(\"js\"):\n                    data = translate.translateData(data, site.storage.loadJson(lang_file), \"js\")\n                else:\n                    data = translate.translateData(data, site.storage.loadJson(lang_file), \"html\")\n            except Exception as err:\n                site.log.error(\"Error loading translation file %s: %s\" % (lang_file, err))\n\n            self.log.debug(\"Patched %s (%s bytes) in %.3fs\" % (inner_path, len(data), time.time() - s))\n            yield data.encode(\"utf8\")\n"
  },
  {
    "path": "plugins/TranslateSite/__init__.py",
    "content": "from . import TranslateSitePlugin\n"
  },
  {
    "path": "plugins/TranslateSite/plugin_info.json",
    "content": "{\n\t\"name\": \"TranslateSite\",\n\t\"description\": \"Transparent support translation of site javascript and html files.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/Trayicon/TrayiconPlugin.py",
    "content": "import os\nimport sys\nimport atexit\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Translate import Translate\n\nallow_reload = False  # No source reload supported in this plugin\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n@PluginManager.registerTo(\"Actions\")\nclass ActionsPlugin(object):\n\n    def main(self):\n        global notificationicon, winfolders\n        from .lib import notificationicon, winfolders\n        import gevent.threadpool\n        import main\n\n        self.main = main\n\n        icon = notificationicon.NotificationIcon(\n            os.path.join(os.path.dirname(os.path.abspath(__file__)), 'trayicon.ico'),\n            \"ZeroNet %s\" % config.version\n        )\n        self.icon = icon\n\n        self.console = False\n\n        @atexit.register\n        def hideIcon():\n            try:\n                icon.die()\n            except Exception as err:\n                print(\"Error removing trayicon: %s\" % err)\n\n        ui_ip = config.ui_ip if config.ui_ip != \"*\" else \"127.0.0.1\"\n\n        if \":\" in ui_ip:\n            ui_ip = \"[\" + ui_ip + \"]\"\n\n        icon.items = [\n            (self.titleIp, False),\n            (self.titleConnections, False),\n            (self.titleTransfer, False),\n            (self.titleConsole, self.toggleConsole),\n            (self.titleAutorun, self.toggleAutorun),\n            \"--\",\n            (_[\"ZeroNet Twitter\"], lambda: self.opensite(\"https://twitter.com/HelloZeroNet\")),\n            (_[\"ZeroNet Reddit\"], lambda: self.opensite(\"http://www.reddit.com/r/zeronet/\")),\n            (_[\"ZeroNet Github\"], lambda: self.opensite(\"https://github.com/HelloZeroNet/ZeroNet\")),\n            (_[\"Report bug/request feature\"], lambda: self.opensite(\"https://github.com/HelloZeroNet/ZeroNet/issues\")),\n            \"--\",\n            (_[\"!Open ZeroNet\"], lambda: self.opensite(\"http://%s:%s/%s\" % (ui_ip, config.ui_port, config.homepage))),\n            \"--\",\n            (_[\"Quit\"], self.quit),\n        ]\n\n        if not notificationicon.hasConsole():\n            del icon.items[3]\n\n        icon.clicked = lambda: self.opensite(\"http://%s:%s/%s\" % (ui_ip, config.ui_port, config.homepage))\n        self.quit_servers_event = gevent.threadpool.ThreadResult(\n            lambda res: gevent.spawn_later(0.1, self.quitServers), gevent.threadpool.get_hub(), lambda: True\n        )  # Fix gevent thread switch error\n        gevent.threadpool.start_new_thread(icon._run, ())  # Start in real thread (not gevent compatible)\n        super(ActionsPlugin, self).main()\n        icon._die = True\n\n    def quit(self):\n        self.icon.die()\n        self.quit_servers_event.set(True)\n\n    def quitServers(self):\n        self.main.ui_server.stop()\n        self.main.file_server.stop()\n\n    def opensite(self, url):\n        import webbrowser\n        webbrowser.open(url, new=0)\n\n    def titleIp(self):\n        title = \"!IP: %s \" % \", \".join(self.main.file_server.ip_external_list)\n        if any(self.main.file_server.port_opened):\n            title += _[\"(active)\"]\n        else:\n            title += _[\"(passive)\"]\n        return title\n\n    def titleConnections(self):\n        title = _[\"Connections: %s\"] % len(self.main.file_server.connections)\n        return title\n\n    def titleTransfer(self):\n        title = _[\"Received: %.2f MB | Sent: %.2f MB\"] % (\n            float(self.main.file_server.bytes_recv) / 1024 / 1024,\n            float(self.main.file_server.bytes_sent) / 1024 / 1024\n        )\n        return title\n\n    def titleConsole(self):\n        translate = _[\"Show console window\"]\n        if self.console:\n            return \"+\" + translate\n        else:\n            return translate\n\n    def toggleConsole(self):\n        if self.console:\n            notificationicon.hideConsole()\n            self.console = False\n        else:\n            notificationicon.showConsole()\n            self.console = True\n\n    def getAutorunPath(self):\n        return \"%s\\\\zeronet.cmd\" % winfolders.get(winfolders.STARTUP)\n\n    def formatAutorun(self):\n        args = sys.argv[:]\n\n        if not getattr(sys, 'frozen', False):  # Not frozen\n            args.insert(0, sys.executable)\n            cwd = os.getcwd()\n        else:\n            cwd = os.path.dirname(sys.executable)\n\n        ignored_args = [\n            \"--open_browser\", \"default_browser\",\n            \"--dist_type\", \"bundle_win64\"\n        ]\n\n        if sys.platform == 'win32':\n            args = ['\"%s\"' % arg for arg in args if arg and arg not in ignored_args]\n        cmd = \" \".join(args)\n\n        # Dont open browser on autorun\n        cmd = cmd.replace(\"start.py\", \"zeronet.py\").strip()\n        cmd += ' --open_browser \"\"'\n\n        return \"\\r\\n\".join([\n            '@echo off',\n            'chcp 65001 > nul',\n            'set PYTHONIOENCODING=utf-8',\n            'cd /D \\\"%s\\\"' % cwd,\n            'start \"\" %s' % cmd\n        ])\n\n    def isAutorunEnabled(self):\n        path = self.getAutorunPath()\n        return os.path.isfile(path) and open(path, \"rb\").read().decode(\"utf8\") == self.formatAutorun()\n\n    def titleAutorun(self):\n        translate = _[\"Start ZeroNet when Windows starts\"]\n        if self.isAutorunEnabled():\n            return \"+\" + translate\n        else:\n            return translate\n\n    def toggleAutorun(self):\n        if self.isAutorunEnabled():\n            os.unlink(self.getAutorunPath())\n        else:\n            open(self.getAutorunPath(), \"wb\").write(self.formatAutorun().encode(\"utf8\"))\n"
  },
  {
    "path": "plugins/Trayicon/__init__.py",
    "content": "import sys\n\nif sys.platform == 'win32':\n\tfrom . import TrayiconPlugin"
  },
  {
    "path": "plugins/Trayicon/languages/es.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Reportar fallo/sugerir característica\",\n\t\"!Open ZeroNet\": \"!Abrir ZeroNet\",\n\t\"Quit\": \"Sair\",\n\t\"(active)\": \"(activo)\",\n\t\"(passive)\": \"(pasivo)\",\n\t\"Connections: %s\": \"Conecciones: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Recibido: %.2f MB | Enviado: %.2f MB\",\n\t\"Show console window\": \"Mostrar consola\",\n\t\"Start ZeroNet when Windows starts\": \"Iniciar Zeronet cuando inicie Windows\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/fr.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Rapport d'erreur/Demander une fonctionnalité\",\n\t\"!Open ZeroNet\": \"!Ouvrir ZeroNet\",\n\t\"Quit\": \"Quitter\",\n\t\"(active)\": \"(actif)\",\n\t\"(passive)\": \"(passif)\",\n\t\"Connections: %s\": \"Connexions: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Reçu: %.2f MB | Envoyé: %.2f MB\",\n\t\"Show console window\": \"Afficher la console\",\n\t\"Start ZeroNet when Windows starts\": \"Lancer ZeroNet au démarrage de Windows\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/hu.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Hiba bejelentés/ötletek\",\n\t\"!Open ZeroNet\": \"!ZeroNet megnyitása\",\n\t\"Quit\": \"Kilépés\",\n\t\"(active)\": \"(aktív)\",\n\t\"(passive)\": \"(passive)\",\n\t\"Connections: %s\": \"Kapcsolatok: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Fogadott: %.2f MB | Küldött: %.2f MB\",\n\t\"Show console window\": \"Parancssor mutatása\",\n\t\"Start ZeroNet when Windows starts\": \"ZeroNet indítása a Windows-al együtt\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/it.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Segnala bug/richiesta di una funzione\",\n\t\"!Open ZeroNet\": \"!Apri ZeroNet\",\n\t\"Quit\": \"Chiudi\",\n\t\"(active)\": \"(attivo)\",\n\t\"(passive)\": \"(passivo)\",\n\t\"Connections: %s\": \"Connessioni: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Ricevuto: %.2f MB | Inviato: %.2f MB\",\n\t\"Show console window\": \"Mostra finestra console\",\n\t\"Start ZeroNet when Windows starts\": \"Avvia ZeroNet all'avvio di Windows\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/jp.json",
    "content": " {\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"バグ報告/要望\",\n\t\"!Open ZeroNet\": \"!ZeroNetをブラウザで開く\",\n\t\"Quit\": \"閉じる\",\n\t\"(active)\": \"(アクティブ)\",\n\t\"(passive)\": \"(パッシブ)\",\n\t\"Connections: %s\": \"接続数: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"受信: %.2f MB | 送信: %.2f MB\",\n\t\"Show console window\": \"コンソールを表示\",\n\t\"Start ZeroNet when Windows starts\": \"Windows起動時にZeroNetも起動する\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/pl.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Zgłoś błąd / propozycję\",\n\t\"!Open ZeroNet\": \"!Otwórz ZeroNet\",\n\t\"Quit\": \"Zamknij\",\n\t\"(active)\": \"(aktywny)\",\n\t\"(passive)\": \"(pasywny)\",\n\t\"Connections: %s\": \"Połączenia: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Odebrano: %.2f MB | Wysłano: %.2f MB\",\n\t\"Show console window\": \"Pokaż okno konsoli\",\n\t\"Start ZeroNet when Windows starts\": \"Uruchom ZeroNet podczas startu Windows\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/pt-br.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Reportar bug/sugerir recurso\",\n\t\"!Open ZeroNet\": \"!Abrir ZeroNet\",\n\t\"Quit\": \"Sair\",\n\t\"(active)\": \"(ativo)\",\n\t\"(passive)\": \"(passivo)\",\n\t\"Connections: %s\": \"Conexões: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Recebido: %.2f MB | Enviado: %.2f MB\",\n\t\"Show console window\": \"Mostrar console\",\n\t\"Start ZeroNet when Windows starts\": \"Iniciar o ZeroNet quando o Windows for iniciado\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/tr.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"Hata bildir/geliştirme taleb et\",\n\t\"!Open ZeroNet\": \"!ZeroNet'i Aç\",\n\t\"Quit\": \"Kapat\",\n\t\"(active)\": \"(aktif)\",\n\t\"(passive)\": \"(pasif)\",\n\t\"Connections: %s\": \"Bağlantı sayısı: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"Gelen: %.2f MB | Gönderilen: %.2f MB\",\n\t\"Show console window\": \"Konsolu aç\",\n\t\"Start ZeroNet when Windows starts\": \"ZeroNet'i açılışta otomatik başlat\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/zh-tw.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"回饋问题/請求功能\",\n\t\"!Open ZeroNet\": \"!開啟 ZeroNet\",\n\t\"Quit\": \"退出\",\n\t\"(active)\": \"(主動模式)\",\n\t\"(passive)\": \"(被動模式)\",\n\t\"Connections: %s\": \"連線數: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"已收到: %.2f MB | 已傳送: %.2f MB\",\n\t\"Show console window\": \"顯示控制臺窗體\",\n\t\"Start ZeroNet when Windows starts\": \"在 Windows 啟動時執行 ZeroNet\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/languages/zh.json",
    "content": "{\n\t\"ZeroNet Twitter\": \"ZeroNet Twitter\",\n\t\"ZeroNet Reddit\": \"ZeroNet Reddit\",\n\t\"ZeroNet Github\": \"ZeroNet Github\",\n\t\"Report bug/request feature\": \"反馈问题/请求功能\",\n\t\"!Open ZeroNet\": \"!打开 ZeroNet\",\n\t\"Quit\": \"退出\",\n\t\"(active)\": \"(主动模式)\",\n\t\"(passive)\": \"(被动模式)\",\n\t\"Connections: %s\": \"连接数: %s\",\n\t\"Received: %.2f MB | Sent: %.2f MB\": \"已接收: %.2f MB | 已发送: %.2f MB\",\n\t\"Show console window\": \"显示控制台窗口\",\n\t\"Start ZeroNet when Windows starts\": \"在 Windows 启动时运行 ZeroNet\"\n}\n"
  },
  {
    "path": "plugins/Trayicon/lib/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Trayicon/lib/notificationicon.py",
    "content": "# Pure ctypes windows taskbar notification icon\n# via https://gist.github.com/jasonbot/5759510\n# Modified for ZeroNet\n\nimport ctypes\nimport ctypes.wintypes\nimport os\nimport uuid\nimport time\nimport gevent\nimport threading\ntry:\n    from queue import Empty as queue_Empty  # Python 3\nexcept ImportError:\n    from Queue import Empty as queue_Empty  # Python 2\n\n__all__ = ['NotificationIcon']\n\n# Create popup menu\n\nCreatePopupMenu = ctypes.windll.user32.CreatePopupMenu\nCreatePopupMenu.restype = ctypes.wintypes.HMENU\nCreatePopupMenu.argtypes = []\n\nMF_BYCOMMAND    = 0x0\nMF_BYPOSITION   = 0x400\n\nMF_BITMAP       = 0x4\nMF_CHECKED      = 0x8\nMF_DISABLED     = 0x2\nMF_ENABLED      = 0x0\nMF_GRAYED       = 0x1\nMF_MENUBARBREAK = 0x20\nMF_MENUBREAK    = 0x40\nMF_OWNERDRAW    = 0x100\nMF_POPUP        = 0x10\nMF_SEPARATOR    = 0x800\nMF_STRING       = 0x0\nMF_UNCHECKED    = 0x0\n\nInsertMenu = ctypes.windll.user32.InsertMenuW\nInsertMenu.restype = ctypes.wintypes.BOOL\nInsertMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]\n\nAppendMenu = ctypes.windll.user32.AppendMenuW\nAppendMenu.restype = ctypes.wintypes.BOOL\nAppendMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]\n\nSetMenuDefaultItem = ctypes.windll.user32.SetMenuDefaultItem\nSetMenuDefaultItem.restype = ctypes.wintypes.BOOL\nSetMenuDefaultItem.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT]\n\nclass POINT(ctypes.Structure):\n    _fields_ = [ ('x', ctypes.wintypes.LONG),\n                 ('y', ctypes.wintypes.LONG)]\n\nGetCursorPos = ctypes.windll.user32.GetCursorPos\nGetCursorPos.argtypes = [ctypes.POINTER(POINT)]\n\nSetForegroundWindow = ctypes.windll.user32.SetForegroundWindow\nSetForegroundWindow.argtypes = [ctypes.wintypes.HWND]\n\nTPM_LEFTALIGN       = 0x0\nTPM_CENTERALIGN     = 0x4\nTPM_RIGHTALIGN      = 0x8\n\nTPM_TOPALIGN        = 0x0\nTPM_VCENTERALIGN    = 0x10\nTPM_BOTTOMALIGN     = 0x20\n\nTPM_NONOTIFY        = 0x80\nTPM_RETURNCMD       = 0x100\n\nTPM_LEFTBUTTON      = 0x0\nTPM_RIGHTBUTTON     = 0x2\n\nTPM_HORNEGANIMATION = 0x800\nTPM_HORPOSANIMATION = 0x400\nTPM_NOANIMATION     = 0x4000\nTPM_VERNEGANIMATION = 0x2000\nTPM_VERPOSANIMATION = 0x1000\n\nTrackPopupMenu = ctypes.windll.user32.TrackPopupMenu\nTrackPopupMenu.restype = ctypes.wintypes.BOOL\nTrackPopupMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_void_p]\n\nPostMessage = ctypes.windll.user32.PostMessageW\nPostMessage.restype = ctypes.wintypes.BOOL\nPostMessage.argtypes = [ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]\n\nDestroyMenu = ctypes.windll.user32.DestroyMenu\nDestroyMenu.restype = ctypes.wintypes.BOOL\nDestroyMenu.argtypes = [ctypes.wintypes.HMENU]\n\n# Create notification icon\n\nGUID = ctypes.c_ubyte * 16\n\nclass TimeoutVersionUnion(ctypes.Union):\n    _fields_ = [('uTimeout', ctypes.wintypes.UINT),\n                ('uVersion', ctypes.wintypes.UINT),]\n\nNIS_HIDDEN     = 0x1\nNIS_SHAREDICON = 0x2\n\nclass NOTIFYICONDATA(ctypes.Structure):\n    def __init__(self, *args, **kwargs):\n        super(NOTIFYICONDATA, self).__init__(*args, **kwargs)\n        self.cbSize = ctypes.sizeof(self)\n    _fields_ = [\n        ('cbSize', ctypes.wintypes.DWORD),\n        ('hWnd', ctypes.wintypes.HWND),\n        ('uID', ctypes.wintypes.UINT),\n        ('uFlags', ctypes.wintypes.UINT),\n        ('uCallbackMessage', ctypes.wintypes.UINT),\n        ('hIcon', ctypes.wintypes.HICON),\n        ('szTip', ctypes.wintypes.WCHAR * 64),\n        ('dwState', ctypes.wintypes.DWORD),\n        ('dwStateMask', ctypes.wintypes.DWORD),\n        ('szInfo', ctypes.wintypes.WCHAR * 256),\n        ('union', TimeoutVersionUnion),\n        ('szInfoTitle', ctypes.wintypes.WCHAR * 64),\n        ('dwInfoFlags', ctypes.wintypes.DWORD),\n        ('guidItem', GUID),\n        ('hBalloonIcon', ctypes.wintypes.HICON),\n    ]\n\nNIM_ADD = 0\nNIM_MODIFY = 1\nNIM_DELETE = 2\nNIM_SETFOCUS = 3\nNIM_SETVERSION = 4\n\nNIF_MESSAGE = 1\nNIF_ICON = 2\nNIF_TIP = 4\nNIF_STATE = 8\nNIF_INFO = 16\nNIF_GUID = 32\nNIF_REALTIME = 64\nNIF_SHOWTIP = 128\n\nNIIF_NONE = 0\nNIIF_INFO = 1\nNIIF_WARNING = 2\nNIIF_ERROR = 3\nNIIF_USER = 4\n\nNOTIFYICON_VERSION = 3\nNOTIFYICON_VERSION_4 = 4\n\nShell_NotifyIcon = ctypes.windll.shell32.Shell_NotifyIconW\nShell_NotifyIcon.restype = ctypes.wintypes.BOOL\nShell_NotifyIcon.argtypes = [ctypes.wintypes.DWORD, ctypes.POINTER(NOTIFYICONDATA)]\n\n# Load icon/image\n\nIMAGE_BITMAP = 0\nIMAGE_ICON = 1\nIMAGE_CURSOR = 2\n\nLR_CREATEDIBSECTION = 0x00002000\nLR_DEFAULTCOLOR     = 0x00000000\nLR_DEFAULTSIZE      = 0x00000040\nLR_LOADFROMFILE     = 0x00000010\nLR_LOADMAP3DCOLORS  = 0x00001000\nLR_LOADTRANSPARENT  = 0x00000020\nLR_MONOCHROME       = 0x00000001\nLR_SHARED           = 0x00008000\nLR_VGACOLOR         = 0x00000080\n\nOIC_SAMPLE      = 32512\nOIC_HAND        = 32513\nOIC_QUES        = 32514\nOIC_BANG        = 32515\nOIC_NOTE        = 32516\nOIC_WINLOGO     = 32517\nOIC_WARNING     = OIC_BANG\nOIC_ERROR       = OIC_HAND\nOIC_INFORMATION = OIC_NOTE\n\nLoadImage = ctypes.windll.user32.LoadImageW\nLoadImage.restype = ctypes.wintypes.HANDLE\nLoadImage.argtypes = [ctypes.wintypes.HINSTANCE, ctypes.wintypes.LPCWSTR, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.wintypes.UINT]\n\n# CreateWindow call\n\nWNDPROC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)\nDefWindowProc = ctypes.windll.user32.DefWindowProcW\nDefWindowProc.restype = ctypes.c_int\nDefWindowProc.argtypes = [ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]\n\nWS_OVERLAPPED       = 0x00000000\nWS_POPUP            = 0x80000000\nWS_CHILD            = 0x40000000\nWS_MINIMIZE         = 0x20000000\nWS_VISIBLE          = 0x10000000\nWS_DISABLED         = 0x08000000\nWS_CLIPSIBLINGS     = 0x04000000\nWS_CLIPCHILDREN     = 0x02000000\nWS_MAXIMIZE         = 0x01000000\nWS_CAPTION          = 0x00C00000\nWS_BORDER           = 0x00800000\nWS_DLGFRAME         = 0x00400000\nWS_VSCROLL          = 0x00200000\nWS_HSCROLL          = 0x00100000\nWS_SYSMENU          = 0x00080000\nWS_THICKFRAME       = 0x00040000\nWS_GROUP            = 0x00020000\nWS_TABSTOP          = 0x00010000\n\nWS_MINIMIZEBOX      = 0x00020000\nWS_MAXIMIZEBOX      = 0x00010000\n\nWS_OVERLAPPEDWINDOW = (WS_OVERLAPPED     |\n                       WS_CAPTION        |\n                       WS_SYSMENU        |\n                       WS_THICKFRAME     |\n                       WS_MINIMIZEBOX    |\n                       WS_MAXIMIZEBOX)\n\nSM_XVIRTUALSCREEN      = 76\nSM_YVIRTUALSCREEN      = 77\nSM_CXVIRTUALSCREEN     = 78\nSM_CYVIRTUALSCREEN     = 79\nSM_CMONITORS           = 80\nSM_SAMEDISPLAYFORMAT   = 81\n\nWM_NULL                   = 0x0000\nWM_CREATE                 = 0x0001\nWM_DESTROY                = 0x0002\nWM_MOVE                   = 0x0003\nWM_SIZE                   = 0x0005\nWM_ACTIVATE               = 0x0006\nWM_SETFOCUS               = 0x0007\nWM_KILLFOCUS              = 0x0008\nWM_ENABLE                 = 0x000A\nWM_SETREDRAW              = 0x000B\nWM_SETTEXT                = 0x000C\nWM_GETTEXT                = 0x000D\nWM_GETTEXTLENGTH          = 0x000E\nWM_PAINT                  = 0x000F\nWM_CLOSE                  = 0x0010\nWM_QUERYENDSESSION        = 0x0011\nWM_QUIT                   = 0x0012\nWM_QUERYOPEN              = 0x0013\nWM_ERASEBKGND             = 0x0014\nWM_SYSCOLORCHANGE         = 0x0015\nWM_ENDSESSION             = 0x0016\nWM_SHOWWINDOW             = 0x0018\nWM_CTLCOLOR               = 0x0019\nWM_WININICHANGE           = 0x001A\nWM_SETTINGCHANGE          = 0x001A\nWM_DEVMODECHANGE          = 0x001B\nWM_ACTIVATEAPP            = 0x001C\nWM_FONTCHANGE             = 0x001D\nWM_TIMECHANGE             = 0x001E\nWM_CANCELMODE             = 0x001F\nWM_SETCURSOR              = 0x0020\nWM_MOUSEACTIVATE          = 0x0021\nWM_CHILDACTIVATE          = 0x0022\nWM_QUEUESYNC              = 0x0023\nWM_GETMINMAXINFO          = 0x0024\nWM_PAINTICON              = 0x0026\nWM_ICONERASEBKGND         = 0x0027\nWM_NEXTDLGCTL             = 0x0028\nWM_SPOOLERSTATUS          = 0x002A\nWM_DRAWITEM               = 0x002B\nWM_MEASUREITEM            = 0x002C\nWM_DELETEITEM             = 0x002D\nWM_VKEYTOITEM             = 0x002E\nWM_CHARTOITEM             = 0x002F\nWM_SETFONT                = 0x0030\nWM_GETFONT                = 0x0031\nWM_SETHOTKEY              = 0x0032\nWM_GETHOTKEY              = 0x0033\nWM_QUERYDRAGICON          = 0x0037\nWM_COMPAREITEM            = 0x0039\nWM_GETOBJECT              = 0x003D\nWM_COMPACTING             = 0x0041\nWM_COMMNOTIFY             = 0x0044\nWM_WINDOWPOSCHANGING      = 0x0046\nWM_WINDOWPOSCHANGED       = 0x0047\nWM_POWER                  = 0x0048\nWM_COPYDATA               = 0x004A\nWM_CANCELJOURNAL          = 0x004B\nWM_NOTIFY                 = 0x004E\nWM_INPUTLANGCHANGEREQUEST = 0x0050\nWM_INPUTLANGCHANGE        = 0x0051\nWM_TCARD                  = 0x0052\nWM_HELP                   = 0x0053\nWM_USERCHANGED            = 0x0054\nWM_NOTIFYFORMAT           = 0x0055\nWM_CONTEXTMENU            = 0x007B\nWM_STYLECHANGING          = 0x007C\nWM_STYLECHANGED           = 0x007D\nWM_DISPLAYCHANGE          = 0x007E\nWM_GETICON                = 0x007F\nWM_SETICON                = 0x0080\nWM_NCCREATE               = 0x0081\nWM_NCDESTROY              = 0x0082\nWM_NCCALCSIZE             = 0x0083\nWM_NCHITTEST              = 0x0084\nWM_NCPAINT                = 0x0085\nWM_NCACTIVATE             = 0x0086\nWM_GETDLGCODE             = 0x0087\nWM_SYNCPAINT              = 0x0088\nWM_NCMOUSEMOVE            = 0x00A0\nWM_NCLBUTTONDOWN          = 0x00A1\nWM_NCLBUTTONUP            = 0x00A2\nWM_NCLBUTTONDBLCLK        = 0x00A3\nWM_NCRBUTTONDOWN          = 0x00A4\nWM_NCRBUTTONUP            = 0x00A5\nWM_NCRBUTTONDBLCLK        = 0x00A6\nWM_NCMBUTTONDOWN          = 0x00A7\nWM_NCMBUTTONUP            = 0x00A8\nWM_NCMBUTTONDBLCLK        = 0x00A9\nWM_KEYDOWN                = 0x0100\nWM_KEYUP                  = 0x0101\nWM_CHAR                   = 0x0102\nWM_DEADCHAR               = 0x0103\nWM_SYSKEYDOWN             = 0x0104\nWM_SYSKEYUP               = 0x0105\nWM_SYSCHAR                = 0x0106\nWM_SYSDEADCHAR            = 0x0107\nWM_KEYLAST                = 0x0108\nWM_IME_STARTCOMPOSITION   = 0x010D\nWM_IME_ENDCOMPOSITION     = 0x010E\nWM_IME_COMPOSITION        = 0x010F\nWM_IME_KEYLAST            = 0x010F\nWM_INITDIALOG             = 0x0110\nWM_COMMAND                = 0x0111\nWM_SYSCOMMAND             = 0x0112\nWM_TIMER                  = 0x0113\nWM_HSCROLL                = 0x0114\nWM_VSCROLL                = 0x0115\nWM_INITMENU               = 0x0116\nWM_INITMENUPOPUP          = 0x0117\nWM_MENUSELECT             = 0x011F\nWM_MENUCHAR               = 0x0120\nWM_ENTERIDLE              = 0x0121\nWM_MENURBUTTONUP          = 0x0122\nWM_MENUDRAG               = 0x0123\nWM_MENUGETOBJECT          = 0x0124\nWM_UNINITMENUPOPUP        = 0x0125\nWM_MENUCOMMAND            = 0x0126\nWM_CTLCOLORMSGBOX         = 0x0132\nWM_CTLCOLOREDIT           = 0x0133\nWM_CTLCOLORLISTBOX        = 0x0134\nWM_CTLCOLORBTN            = 0x0135\nWM_CTLCOLORDLG            = 0x0136\nWM_CTLCOLORSCROLLBAR      = 0x0137\nWM_CTLCOLORSTATIC         = 0x0138\nWM_MOUSEMOVE              = 0x0200\nWM_LBUTTONDOWN            = 0x0201\nWM_LBUTTONUP              = 0x0202\nWM_LBUTTONDBLCLK          = 0x0203\nWM_RBUTTONDOWN            = 0x0204\nWM_RBUTTONUP              = 0x0205\nWM_RBUTTONDBLCLK          = 0x0206\nWM_MBUTTONDOWN            = 0x0207\nWM_MBUTTONUP              = 0x0208\nWM_MBUTTONDBLCLK          = 0x0209\nWM_MOUSEWHEEL             = 0x020A\nWM_PARENTNOTIFY           = 0x0210\nWM_ENTERMENULOOP          = 0x0211\nWM_EXITMENULOOP           = 0x0212\nWM_NEXTMENU               = 0x0213\nWM_SIZING                 = 0x0214\nWM_CAPTURECHANGED         = 0x0215\nWM_MOVING                 = 0x0216\nWM_DEVICECHANGE           = 0x0219\nWM_MDICREATE              = 0x0220\nWM_MDIDESTROY             = 0x0221\nWM_MDIACTIVATE            = 0x0222\nWM_MDIRESTORE             = 0x0223\nWM_MDINEXT                = 0x0224\nWM_MDIMAXIMIZE            = 0x0225\nWM_MDITILE                = 0x0226\nWM_MDICASCADE             = 0x0227\nWM_MDIICONARRANGE         = 0x0228\nWM_MDIGETACTIVE           = 0x0229\nWM_MDISETMENU             = 0x0230\nWM_ENTERSIZEMOVE          = 0x0231\nWM_EXITSIZEMOVE           = 0x0232\nWM_DROPFILES              = 0x0233\nWM_MDIREFRESHMENU         = 0x0234\nWM_IME_SETCONTEXT         = 0x0281\nWM_IME_NOTIFY             = 0x0282\nWM_IME_CONTROL            = 0x0283\nWM_IME_COMPOSITIONFULL    = 0x0284\nWM_IME_SELECT             = 0x0285\nWM_IME_CHAR               = 0x0286\nWM_IME_REQUEST            = 0x0288\nWM_IME_KEYDOWN            = 0x0290\nWM_IME_KEYUP              = 0x0291\nWM_MOUSEHOVER             = 0x02A1\nWM_MOUSELEAVE             = 0x02A3\nWM_CUT                    = 0x0300\nWM_COPY                   = 0x0301\nWM_PASTE                  = 0x0302\nWM_CLEAR                  = 0x0303\nWM_UNDO                   = 0x0304\nWM_RENDERFORMAT           = 0x0305\nWM_RENDERALLFORMATS       = 0x0306\nWM_DESTROYCLIPBOARD       = 0x0307\nWM_DRAWCLIPBOARD          = 0x0308\nWM_PAINTCLIPBOARD         = 0x0309\nWM_VSCROLLCLIPBOARD       = 0x030A\nWM_SIZECLIPBOARD          = 0x030B\nWM_ASKCBFORMATNAME        = 0x030C\nWM_CHANGECBCHAIN          = 0x030D\nWM_HSCROLLCLIPBOARD       = 0x030E\nWM_QUERYNEWPALETTE        = 0x030F\nWM_PALETTEISCHANGING      = 0x0310\nWM_PALETTECHANGED         = 0x0311\nWM_HOTKEY                 = 0x0312\nWM_PRINT                  = 0x0317\nWM_PRINTCLIENT            = 0x0318\nWM_HANDHELDFIRST          = 0x0358\nWM_HANDHELDLAST           = 0x035F\nWM_AFXFIRST               = 0x0360\nWM_AFXLAST                = 0x037F\nWM_PENWINFIRST            = 0x0380\nWM_PENWINLAST             = 0x038F\nWM_APP                    = 0x8000\nWM_USER                   = 0x0400\nWM_REFLECT                = WM_USER + 0x1c00\n\nclass WNDCLASSEX(ctypes.Structure):\n    def __init__(self, *args, **kwargs):\n        super(WNDCLASSEX, self).__init__(*args, **kwargs)\n        self.cbSize = ctypes.sizeof(self)\n    _fields_ = [(\"cbSize\", ctypes.c_uint),\n                (\"style\", ctypes.c_uint),\n                (\"lpfnWndProc\", WNDPROC),\n                (\"cbClsExtra\", ctypes.c_int),\n                (\"cbWndExtra\", ctypes.c_int),\n                (\"hInstance\", ctypes.wintypes.HANDLE),\n                (\"hIcon\", ctypes.wintypes.HANDLE),\n                (\"hCursor\", ctypes.wintypes.HANDLE),\n                (\"hBrush\", ctypes.wintypes.HANDLE),\n                (\"lpszMenuName\", ctypes.wintypes.LPCWSTR),\n                (\"lpszClassName\", ctypes.wintypes.LPCWSTR),\n                (\"hIconSm\", ctypes.wintypes.HANDLE)]\n\nShowWindow = ctypes.windll.user32.ShowWindow\nShowWindow.argtypes = [ctypes.wintypes.HWND, ctypes.c_int]\n\ndef GenerateDummyWindow(callback, uid):\n    newclass = WNDCLASSEX()\n    newclass.lpfnWndProc = callback\n    newclass.lpszClassName = uid.replace(\"-\", \"\")\n    ATOM = ctypes.windll.user32.RegisterClassExW(ctypes.byref(newclass))\n    hwnd = ctypes.windll.user32.CreateWindowExW(0, newclass.lpszClassName, None, WS_POPUP, 0, 0, 0, 0, 0, 0, 0, 0)\n    return hwnd\n\n# Message loop calls\n\nTIMERCALLBACK = ctypes.WINFUNCTYPE(None,\n                                   ctypes.wintypes.HWND,\n                                   ctypes.wintypes.UINT,\n                                   ctypes.POINTER(ctypes.wintypes.UINT),\n                                   ctypes.wintypes.DWORD)\n\nSetTimer = ctypes.windll.user32.SetTimer\nSetTimer.restype = ctypes.POINTER(ctypes.wintypes.UINT)\nSetTimer.argtypes = [ctypes.wintypes.HWND,\n                     ctypes.POINTER(ctypes.wintypes.UINT),\n                     ctypes.wintypes.UINT,\n                     TIMERCALLBACK]\n\nKillTimer = ctypes.windll.user32.KillTimer\nKillTimer.restype = ctypes.wintypes.BOOL\nKillTimer.argtypes = [ctypes.wintypes.HWND,\n                      ctypes.POINTER(ctypes.wintypes.UINT)]\n\nclass MSG(ctypes.Structure):\n    _fields_ = [ ('HWND', ctypes.wintypes.HWND),\n                 ('message', ctypes.wintypes.UINT),\n                 ('wParam', ctypes.wintypes.WPARAM),\n                 ('lParam', ctypes.wintypes.LPARAM),\n                 ('time', ctypes.wintypes.DWORD),\n                 ('pt', POINT)]\n\nGetMessage = ctypes.windll.user32.GetMessageW\nGetMessage.restype = ctypes.wintypes.BOOL\nGetMessage.argtypes = [ctypes.POINTER(MSG), ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.UINT]\n\nTranslateMessage = ctypes.windll.user32.TranslateMessage\nTranslateMessage.restype = ctypes.wintypes.ULONG\nTranslateMessage.argtypes = [ctypes.POINTER(MSG)]\n\nDispatchMessage = ctypes.windll.user32.DispatchMessageW\nDispatchMessage.restype = ctypes.wintypes.ULONG\nDispatchMessage.argtypes = [ctypes.POINTER(MSG)]\n\ndef LoadIcon(iconfilename, small=False):\n        return LoadImage(0,\n                         str(iconfilename),\n                         IMAGE_ICON,\n                         16 if small else 0,\n                         16 if small else 0,\n                         LR_LOADFROMFILE)\n\n\nclass NotificationIcon(object):\n    def __init__(self, iconfilename, tooltip=None):\n        assert os.path.isfile(str(iconfilename)), \"{} doesn't exist\".format(iconfilename)\n        self._iconfile = str(iconfilename)\n        self._hicon = LoadIcon(self._iconfile, True)\n        assert self._hicon, \"Failed to load {}\".format(iconfilename)\n        #self._pumpqueue = Queue.Queue()\n        self._die = False\n        self._timerid = None\n        self._uid = uuid.uuid4()\n        self._tooltip = str(tooltip) if tooltip else ''\n        #self._thread = threading.Thread(target=self._run)\n        #self._thread.start()\n        self._info_bubble = None\n        self.items = []\n\n\n    def _bubble(self, iconinfo):\n        if self._info_bubble:\n            info_bubble = self._info_bubble\n            self._info_bubble = None\n            message = str(self._info_bubble)\n            iconinfo.uFlags |= NIF_INFO\n            iconinfo.szInfo = message\n            iconinfo.szInfoTitle = message\n            iconinfo.dwInfoFlags = NIIF_INFO\n            iconinfo.union.uTimeout = 10000\n            Shell_NotifyIcon(NIM_MODIFY, ctypes.pointer(iconinfo))\n\n\n    def _run(self):\n        self.WM_TASKBARCREATED = ctypes.windll.user32.RegisterWindowMessageW('TaskbarCreated')\n\n        self._windowproc = WNDPROC(self._callback)\n        self._hwnd = GenerateDummyWindow(self._windowproc, str(self._uid))\n\n        iconinfo = NOTIFYICONDATA()\n        iconinfo.hWnd = self._hwnd\n        iconinfo.uID = 100\n        iconinfo.uFlags = NIF_ICON | NIF_SHOWTIP | NIF_MESSAGE | (NIF_TIP if self._tooltip else 0)\n        iconinfo.uCallbackMessage = WM_MENUCOMMAND\n        iconinfo.hIcon = self._hicon\n        iconinfo.szTip = self._tooltip\n\n        Shell_NotifyIcon(NIM_ADD, ctypes.pointer(iconinfo))\n\n        self.iconinfo = iconinfo\n\n        PostMessage(self._hwnd, WM_NULL, 0, 0)\n\n        message = MSG()\n        last_time = -1\n        ret = None\n        while not self._die:\n            try:\n                ret = GetMessage(ctypes.pointer(message), 0, 0, 0)\n                TranslateMessage(ctypes.pointer(message))\n                DispatchMessage(ctypes.pointer(message))\n            except Exception as err:\n                # print \"NotificationIcon error\", err, message\n                message = MSG()\n            time.sleep(0.125)\n        print(\"Icon thread stopped, removing icon (hicon: %s, hwnd: %s)...\" % (self._hicon, self._hwnd))\n\n        Shell_NotifyIcon(NIM_DELETE, ctypes.cast(ctypes.pointer(iconinfo), ctypes.POINTER(NOTIFYICONDATA)))\n        ctypes.windll.user32.DestroyWindow(self._hwnd)\n        ctypes.windll.user32.DestroyIcon.argtypes = [ctypes.wintypes.HICON]\n        ctypes.windll.user32.DestroyIcon(self._hicon)\n\n\n    def _menu(self):\n        if not hasattr(self, 'items'):\n            return\n\n        menu = CreatePopupMenu()\n        func = None\n\n        try:\n            iidx = 1000\n            defaultitem = -1\n            item_map = {}\n            for fs in self.items:\n                iidx += 1\n                if isinstance(fs, str):\n                    if fs and not fs.strip('-_='):\n                        AppendMenu(menu, MF_SEPARATOR, iidx, fs)\n                    else:\n                        AppendMenu(menu, MF_STRING | MF_GRAYED, iidx, fs)\n                elif isinstance(fs, tuple):\n                    if callable(fs[0]):\n                        itemstring = fs[0]()\n                    else:\n                        itemstring = str(fs[0])\n                    flags = MF_STRING\n                    if itemstring.startswith(\"!\"):\n                        itemstring = itemstring[1:]\n                        defaultitem = iidx\n                    if itemstring.startswith(\"+\"):\n                        itemstring = itemstring[1:]\n                        flags = flags | MF_CHECKED\n                    itemcallable = fs[1]\n                    item_map[iidx] = itemcallable\n                    if itemcallable is False:\n                        flags = flags | MF_DISABLED\n                    elif not callable(itemcallable):\n                        flags = flags | MF_GRAYED\n                    AppendMenu(menu, flags, iidx, itemstring)\n\n            if defaultitem != -1:\n                SetMenuDefaultItem(menu, defaultitem, 0)\n\n            pos = POINT()\n            GetCursorPos(ctypes.pointer(pos))\n\n            PostMessage(self._hwnd, WM_NULL, 0, 0)\n\n            SetForegroundWindow(self._hwnd)\n\n            ti = TrackPopupMenu(menu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pos.x, pos.y, 0, self._hwnd, None)\n\n            if ti in item_map:\n                func = item_map[ti]\n\n            PostMessage(self._hwnd, WM_NULL, 0, 0)\n        finally:\n            DestroyMenu(menu)\n        if func: func()\n\n\n    def clicked(self):\n        self._menu()\n\n\n\n    def _callback(self, hWnd, msg, wParam, lParam):\n        # Check if the main thread is still alive\n        if msg == WM_TIMER:\n            if not any(thread.getName() == 'MainThread' and thread.isAlive()\n                       for thread in threading.enumerate()):\n                self._die = True\n        elif msg == WM_MENUCOMMAND and lParam == WM_LBUTTONUP:\n            self.clicked()\n        elif msg == WM_MENUCOMMAND and lParam == WM_RBUTTONUP:\n            self._menu()\n        elif msg == self.WM_TASKBARCREATED: # Explorer restarted, add the icon again.\n            Shell_NotifyIcon(NIM_ADD, ctypes.pointer(self.iconinfo))\n        else:\n            return DefWindowProc(hWnd, msg, wParam, lParam)\n        return 1\n\n\n    def die(self):\n        self._die = True\n        PostMessage(self._hwnd, WM_NULL, 0, 0)\n        time.sleep(0.2)\n        try:\n            Shell_NotifyIcon(NIM_DELETE, self.iconinfo)\n        except Exception as err:\n            print(\"Icon remove error\", err)\n        ctypes.windll.user32.DestroyWindow(self._hwnd)\n        ctypes.windll.user32.DestroyIcon(self._hicon)\n\n\n    def pump(self):\n        try:\n            while not self._pumpqueue.empty():\n                callable = self._pumpqueue.get(False)\n                callable()\n        except queue_Empty:\n            pass\n\n\n    def announce(self, text):\n        self._info_bubble = text\n\n\ndef hideConsole():\n    ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)\n\ndef showConsole():\n    ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)\n\ndef hasConsole():\n    return ctypes.windll.kernel32.GetConsoleWindow() != 0\n\nif __name__ == \"__main__\":\n    import time\n\n    def greet():\n        ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)\n        print(\"Hello\")\n\n    def quit():\n        ni._die = True\n\n    def announce():\n        ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)\n        ni.announce(\"Hello there\")\n\n    def clicked():\n        ni.announce(\"Hello\")\n\n    def dynamicTitle():\n        return \"!The time is: %s\" % time.time()\n\n    ni = NotificationIcon(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../trayicon.ico'), \"ZeroNet 0.2.9\")\n    ni.items = [\n        (dynamicTitle, False),\n        ('Hello', greet),\n        ('Title', False),\n        ('!Default', greet),\n        ('+Popup bubble', announce),\n        'Nothing',\n        '--',\n        ('Quit', quit)\n    ]\n    ni.clicked = clicked\n    import atexit\n\n    @atexit.register\n    def goodbye():\n        print(\"You are now leaving the Python sector.\")\n\n    ni._run()\n"
  },
  {
    "path": "plugins/Trayicon/lib/winfolders.py",
    "content": "''' Get windows special folders without pythonwin\n    Example:\n            import specialfolders\n            start_programs = specialfolders.get(specialfolders.PROGRAMS)\n\nCode is public domain, do with it what you will.\n\nLuke Pinner - Environment.gov.au, 2010 February 10\n'''\n\n#Imports use _syntax to mask them from autocomplete IDE's\nimport ctypes as _ctypes\nfrom ctypes import create_unicode_buffer as _cub\nfrom ctypes.wintypes import HWND as _HWND, HANDLE as _HANDLE,DWORD as _DWORD,LPCWSTR as _LPCWSTR,MAX_PATH as _MAX_PATH\n_SHGetFolderPath = _ctypes.windll.shell32.SHGetFolderPathW\n\n#public special folder constants\nDESKTOP=                             0\nPROGRAMS=                            2\nMYDOCUMENTS=                         5\nFAVORITES=                           6\nSTARTUP=                             7\nRECENT=                              8\nSENDTO=                              9\nSTARTMENU=                          11\nMYMUSIC=                            13\nMYVIDEOS=                           14\nNETHOOD=                            19\nFONTS=                              20\nTEMPLATES=                          21\nALLUSERSSTARTMENU=                  22\nALLUSERSPROGRAMS=                   23\nALLUSERSSTARTUP=                    24\nALLUSERSDESKTOP=                    25\nAPPLICATIONDATA=                    26\nPRINTHOOD=                          27\nLOCALSETTINGSAPPLICATIONDATA=       28\nALLUSERSFAVORITES=                  31\nLOCALSETTINGSTEMPORARYINTERNETFILES=32\nCOOKIES=                            33\nLOCALSETTINGSHISTORY=               34\nALLUSERSAPPLICATIONDATA=            35\n\ndef get(intFolder):\n    _SHGetFolderPath.argtypes = [_HWND, _ctypes.c_int, _HANDLE, _DWORD, _LPCWSTR]\n    auPathBuffer = _cub(_MAX_PATH)\n    exit_code=_SHGetFolderPath(0, intFolder, 0, 0, auPathBuffer)\n    return auPathBuffer.value\n\n\nif __name__ == \"__main__\":\n\timport os\n\tprint(get(STARTUP))\n\topen(get(STARTUP)+\"\\\\zeronet.cmd\", \"w\").write(\"cd /D %s\\r\\nzeronet.py\" % os.getcwd())"
  },
  {
    "path": "plugins/Trayicon/plugin_info.json",
    "content": "{\n\t\"name\": \"Trayicon\",\n\t\"description\": \"Icon for system tray. (Windows only)\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/UiConfig/UiConfigPlugin.py",
    "content": "import io\nimport os\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Translate import Translate\nfrom util.Flag import flag\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def actionWrapper(self, path, extra_headers=None):\n        if path.strip(\"/\") != \"Config\":\n            return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)\n\n        if not extra_headers:\n            extra_headers = {}\n\n        script_nonce = self.getScriptNonce()\n\n        self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)\n        site = self.server.site_manager.get(config.homepage)\n        return iter([super(UiRequestPlugin, self).renderWrapper(\n            site, path, \"uimedia/plugins/uiconfig/config.html\",\n            \"Config\", extra_headers, show_loadingscreen=False, script_nonce=script_nonce\n        )])\n\n    def actionUiMedia(self, path, *args, **kwargs):\n        if path.startswith(\"/uimedia/plugins/uiconfig/\"):\n            file_path = path.replace(\"/uimedia/plugins/uiconfig/\", plugin_dir + \"/media/\")\n            if config.debug and (file_path.endswith(\"all.js\") or file_path.endswith(\"all.css\")):\n                # If debugging merge *.css to all.css and *.js to all.js\n                from Debug import DebugMedia\n                DebugMedia.merge(file_path)\n\n            if file_path.endswith(\"js\"):\n                data = _.translateData(open(file_path).read(), mode=\"js\").encode(\"utf8\")\n            elif file_path.endswith(\"html\"):\n                data = _.translateData(open(file_path).read(), mode=\"html\").encode(\"utf8\")\n            else:\n                data = open(file_path, \"rb\").read()\n\n            return self.actionFile(file_path, file_obj=io.BytesIO(data), file_size=len(data))\n        else:\n            return super(UiRequestPlugin, self).actionUiMedia(path)\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    @flag.admin\n    def actionConfigList(self, to):\n        back = {}\n        config_values = vars(config.arguments)\n        config_values.update(config.pending_changes)\n        for key, val in config_values.items():\n            if key not in config.keys_api_change_allowed:\n                continue\n            is_pending = key in config.pending_changes\n            if val is None and is_pending:\n                val = config.parser.get_default(key)\n            back[key] = {\n                \"value\": val,\n                \"default\": config.parser.get_default(key),\n                \"pending\": is_pending\n            }\n        return back\n"
  },
  {
    "path": "plugins/UiConfig/__init__.py",
    "content": "from . import UiConfigPlugin\n"
  },
  {
    "path": "plugins/UiConfig/languages/hu.json",
    "content": "{\n\t\"ZeroNet config\": \"ZeroNet beállítások\",\n\t\"Web Interface\": \"Web felület\",\n\t\"Open web browser on ZeroNet startup\": \"Bögésző megnyitása a ZeroNet indulásakor\",\n\n\t\"Network\": \"Hálózat\",\n\t\"File server port\": \"FIle szerver port\",\n\t\"Other peers will use this port to reach your served sites. (default: 15441)\": \"Más kliensek ezen a porton tudják elérni a kiszolgált oldalaidat (alapbeállítás: 15441)\",\n\n\t\"Disable: Don't connect to peers on Tor network\": \"Kikapcsolás: Ne csatlakozzon a Tor hálózatra\",\n\t\"Enable: Only use Tor for Tor network peers\": \"Bekapcsolás: Csak a Tor kliensekhez használja a Tor hálózatot\",\n\t\"Always: Use Tor for every connections to hide your IP address (slower)\": \"Mindig: Minden kapcsolatot a Tor hálózaton keresztül hozza létre az IP cím elrejtéséhez (lassabb)\",\n\n\t\"Disable\": \"Kikapcsolás\",\n\t\"Enable\": \"Bekapcsolás\",\n\t\"Always\": \"Mindig\",\n\n\t\"Use Tor bridges\": \"Tor bridge-ek használata\",\n\t\"Use obfuscated bridge relays to avoid network level Tor block (even slower)\": \"Tor elrejtő bridge-ek használata a hálózat szintű Tor tiltás megkerüléséhez (még lassabb)\",\n\n\t\"Discover new peers using these adresses\": \"Új kapcsolat felfedezése ezen címek használatával\",\n\n\t\"Trackers files\": \"Tracker file-ok\",\n\t\"Load additional list of torrent trackers dynamically, from a file\": \"További trackerek felfedezése dinamikusan, ezen file használatával\",\n\t\"Eg.: data/trackers.json\": \"Pl.: data/trackers.json\",\n\n\t\"Proxy for tracker connections\": \"Proxy tracker kapcsolatohoz\",\n\n\t\" configuration item value changed\": \" beállítás megváltoztatva\",\n\t\"Save settings\": \"Beállítások mentése\",\n\t\"Some changed settings requires restart\": \"A beállítások érvényesítéséhez a kliens újraindítása szükséges\",\n\t\"Restart ZeroNet client\": \"ZeroNet kliens újraindítása\"\n}"
  },
  {
    "path": "plugins/UiConfig/languages/jp.json",
    "content": "{\n\t\"ZeroNet config\": \"ZeroNetの設定\",\n\t\"Web Interface\": \"WEBインターフェース\",\n\t\"Open web browser on ZeroNet startup\": \"ZeroNet起動時に自動でブラウザーを開く\",\n\n\t\"Network\": \"ネットワーク\",\n\t\"Offline mode\": \"オフラインモード\",\n\t\"Disable network communication.\": \"通信を無効化します\",\n\t\"File server network\": \"ファイルサーバネットワーク\",\n\t\"Accept incoming peers using IPv4 or IPv6 address. (default: dual)\": \"IPv4とIPv6からの受信を許可（既定: 両方）\",\n\t\"Dual (IPv4 & IPv6)\": \"両方 （IPv4 & IPv6）\",\n\t\"File server port\": \"ファイルサーバのポート\",\n\t\"Other peers will use this port to reach your served sites. (default: randomize)\": \"他のピアはこのポートを使用してあなたが所持しているサイトにアクセスします （既定: ランダム）\",\n\t\"File server external ip\": \"ファイルサーバの外部IP\",\n\t\"Detect automatically\": \"自動検出\",\n\t\"Your file server is accessible on these ips. (default: detect automatically)\": \"あなたのファイルサーバへはここで設定したIPでアクセスできます （既定: 自動検出）\",\n\n\t\"Disable: Don't connect to peers on Tor network\": \"無効: Torネットワーク上のピアに接続しない\",\n\t\"Enable: Only use Tor for Tor network peers\": \"有効: Torネットワーク上のピアに対してのみTorを使って接続する\",\n\t\"Always: Use Tor for every connections to hide your IP address (slower)\": \"常時: 全ての接続にTorを使いIPを秘匿する（低速）\",\n\n\t\"Disable\": \"無効\",\n\t\"Enable\": \"有効\",\n\t\"Always\": \"常時\",\n\n\t\"Use Tor bridges\": \"Torブリッジを使用\",\n\t\"Use obfuscated bridge relays to avoid network level Tor block (even slower)\": \"難読化されたブリッジリレーを使用してネットワークレベルのTorブロックを避ける（超低速）\",\n\n\t\"Discover new peers using these adresses\": \"ここで設定したアドレスを用いてピアを発見します\",\n\n\t\"Trackers files\": \"トラッカーファイル\",\n\t\"Load additional list of torrent trackers dynamically, from a file\": \"ファイルからトレントラッカーの追加リストを動的に読み込みます\",\n\t\"Eg.: data/trackers.json\": \"例: data/trackers.json\",\n\n\t\"Proxy for tracker connections\": \"トラッカーへの接続に使うプロキシ\",\n\t\"Custom\": \"カスタム\",\n\t\"Custom socks proxy address for trackers\": \"トラッカーに接続するためのカスタムsocksプロキシのアドレス\",\n\n\t\"Performance\": \"性能\",\n\t\"Level of logging to file\": \"ログレベル\",\n\t\"Everything\": \"全て\",\n\t\"Only important messages\": \"重要なメッセージのみ\",\n\t\"Only errors\": \"エラーのみ\",\n\t\"Threads for async file system reads\": \"非同期ファイルシステムの読み込みに使うスレッド\",\n\t\"Threads for async file system writes\": \"非同期ファイルシステムの書き込みに使うスレッド\",\n\t\"Threads for cryptographic functions\": \"暗号機能に使うスレッド\",\n\t\"Threads for database operations\": \"データベースの操作に使うスレッド\",\n\t\"Sync read\": \"同期読み取り\",\n\t\"Sync write\": \"同期書き込み\",\n\t\"Sync execution\": \"同期実行\",\n\t\"1 thread\": \"1スレッド\",\n\t\"2 threads\": \"2スレッド\",\n\t\"3 threads\": \"3スレッド\",\n\t\"4 threads\": \"4スレッド\",\n\t\"5 threads\": \"5スレッド\",\n\t\"10 threads\": \"10スレッド\",\n\n\t\" configuration item value changed\": \" の項目の値が変更されました\",\n\t\"Save settings\": \"設定を保存\",\n\t\"Some changed settings requires restart\": \"一部の変更の適用には再起動が必要です。\",\n\t\"Restart ZeroNet client\": \"ZeroNetクライアントを再起動\"\n}\n"
  },
  {
    "path": "plugins/UiConfig/languages/pl.json",
    "content": "{\n\t\"ZeroNet config\": \"Konfiguracja ZeroNet\",\n\t\"Web Interface\": \"Interfejs webowy\",\n\t\"Open web browser on ZeroNet startup\": \"Otwórz przeglądarkę podczas uruchomienia ZeroNet\",\n\n\t\"Network\": \"Sieć\",\n\t\"Offline mode\": \"Tryb offline\",\n\t\"Disable network communication.\": \"Wyłącz komunikacje sieciową\",\n\t\"File server network\": \"Sieć serwera plików\",\n\t\"Accept incoming peers using IPv4 or IPv6 address. (default: dual)\": \"Akceptuj połączenia przychodzące używając IPv4 i IPv6. (domyślnie: oba)\",\n\t\"Dual (IPv4 & IPv6)\": \"Oba (IPv4 i IPv6)\",\n\t\"File server port\": \"Port serwera plików\",\n\t\"Other peers will use this port to reach your served sites. (default: 15441)\": \"Inni użytkownicy będą używać tego portu do połączenia się z Tobą. (domyślnie 15441)\",\n\t\"File server external ip\": \"Zewnętrzny adres IP serwera plików\",\n\t\"Detect automatically\": \"Wykryj automatycznie\",\n\t\"Your file server is accessible on these ips. (default: detect automatically)\": \"Twój serwer plików będzie dostępny na tych adresach IP. (domyślnie: wykryj automatycznie)\",\n\n\t\"Disable: Don't connect to peers on Tor network\": \"Wyłącz: Nie łącz się do użytkowników sieci Tor\",\n\t\"Enable: Only use Tor for Tor network peers\": \"Włącz: Łącz się do użytkowników sieci Tor\",\n\t\"Always: Use Tor for every connections to hide your IP address (slower)\": \"Zawsze Tor: Użyj Tor dla wszystkich połączeń w celu ukrycia Twojego adresu IP (wolne działanie)\",\n\n\t\"Disable\": \"Wyłącz\",\n\t\"Enable\": \"Włącz\",\n\t\"Always\": \"Zawsze Tor\",\n\n\t\"Use Tor bridges\": \"Użyj Tor bridges\",\n\t\"Use obfuscated bridge relays to avoid network level Tor block (even slower)\": \"Użyj obfuskacji, aby uniknąć blokowania Tor na poziomie sieci (jeszcze wolniejsze działanie)\",\n\t\"Trackers\": \"Trackery\",\n\t\"Discover new peers using these adresses\": \"Wykryj użytkowników korzystając z tych adresów trackerów\",\n\n\t\"Trackers files\": \"Pliki trackerów\",\n\t\"Load additional list of torrent trackers dynamically, from a file\": \"Dynamicznie wczytaj dodatkową listę trackerów z pliku .json\",\n\t\"Eg.: data/trackers.json\": \"Np.: data/trackers.json\",\n\n\t\"Proxy for tracker connections\": \"Serwer proxy dla trackerów\",\n\t\"Custom\": \"Własny\",\n\t\"Custom socks proxy address for trackers\": \"Adres serwera proxy do łączenia z trackerami\",\n\t\"Eg.: 127.0.0.1:1080\": \"Np.: 127.0.0.1:1080\",\n\t\"Performance\": \"Wydajność\",\n\t\"Level of logging to file\": \"Poziom logowania do pliku\",\n\t\"Everything\": \"Wszystko\",\n\t\"Only important messages\": \"Tylko ważne wiadomości\",\n\t\"Only errors\": \"Tylko błędy\",\n\t\"Threads for async file system reads\": \"Wątki asynchroniczne dla odczytu\",\n\t\"Threads for async file system writes\": \"Wątki asynchroniczne dla zapisu\",\n\t\"Threads for cryptographic functions\": \"Wątki dla funkcji kryptograficznych\",\n\t\"Threads for database operations\": \"Wątki dla operacji na bazie danych\",\n\t\"Sync read\": \"Synchroniczny odczyt\",\n\t\"Sync write\": \"Synchroniczny zapis\",\n\t\"Sync execution\": \"Synchroniczne wykonanie\",\n\t\"1 thread\": \"1 wątek\",\n\t\"2 threads\": \"2 wątki\",\n\t\"3 threads\": \"3 wątki\",\n\t\"4 threads\": \"4 wątki\",\n\t\"5 threads\": \"5 wątków\",\n\t\"10 threads\": \"10 wątków\",\n\n\t\" configuration item value changed\": \" obiekt konfiguracji zmieniony\",\n\t\"Save settings\": \"Zapisz ustawienia\",\n\t\"Some changed settings requires restart\": \"Niektóre zmiany ustawień wymagają ponownego uruchomienia ZeroNet\",\n\t\"Restart ZeroNet client\": \"Uruchom ponownie ZeroNet\"\n}\n"
  },
  {
    "path": "plugins/UiConfig/languages/pt-br.json",
    "content": "{\n\t\"ZeroNet config\": \"Configurações da ZeroNet\",\n\t\"Web Interface\": \"Interface Web\",\n\t\"Open web browser on ZeroNet startup\": \"Abrir navegador da web quando a ZeroNet iniciar\",\n\n\t\"Network\": \"Rede\",\n\t\"File server port\": \"Porta de servidor de arquivos\",\n\t\"Other peers will use this port to reach your served sites. (default: 15441)\": \"Outros peers usarão esta porta para alcançar seus sites servidos. (padrão: 15441)\",\n\n\t\"Disable: Don't connect to peers on Tor network\": \"Desativar: Não conectar à peers na rede Tor\",\n\t\"Enable: Only use Tor for Tor network peers\": \"Ativar: Somente usar Tor para os peers da rede Tor\",\n\t\"Always: Use Tor for every connections to hide your IP address (slower)\": \"Sempre: Usar o Tor para todas as conexões, para esconder seu endereço IP (mais lento)\",\n\n\t\"Disable\": \"Desativar\",\n\t\"Enable\": \"Ativar\",\n\t\"Always\": \"Sempre\",\n\n\t\"Use Tor bridges\": \"Usar pontes do Tor\",\n\t\"Use obfuscated bridge relays to avoid network level Tor block (even slower)\": \"Usar relays de ponte ofuscados para evitar o bloqueio de Tor de nível de rede (ainda mais lento)\",\n\n\t\"Discover new peers using these adresses\": \"Descobrir novos peers usando estes endereços\",\n\n\t\"Trackers files\": \"Arquivos de trackers\",\n\t\"Load additional list of torrent trackers dynamically, from a file\": \"Carregar lista adicional de trackers de torrent dinamicamente, à partir de um arquivo\",\n\t\"Eg.: data/trackers.json\": \"Exemplo: data/trackers.json\",\n\n\t\"Proxy for tracker connections\": \"Proxy para conexões de tracker\",\n\n\t\"configuration item value changed\": \" valor do item de configuração foi alterado\",\n\t\"Save settings\": \"Salvar configurações\",\n\t\"Some changed settings requires restart\": \"Algumas configurações alteradas requrem reinicialização\",\n\t\"Restart ZeroNet client\": \"Reiniciar cliente da ZeroNet\",\n\n\t\"Offline mode\": \"Modo offline\",\n\t\"Disable network communication.\": \"Desativar a comunicação em rede.\",\n\t\"File server network\": \"Rede do servidor de arquivos\",\n\t\"Accept incoming peers using IPv4 or IPv6 address. (default: dual)\": \"Aceite pontos de entrada usando o endereço IPv4 ou IPv6. (padrão: dual)\",\n\t\"File server external ip\": \"IP externo do servidor de arquivos\",\n\t\"Performance\": \"Desempenho\",\n\t\"Level of logging to file\": \"Nível de registro no arquivo\",\n\t\"Everything\": \"Tudo\",\n\t\"Only important messages\": \"Apenas mensagens importantes\",\n\t\"Only errors\": \"Apenas erros\",\n\n\t\"Threads for async file system reads\": \"Threads para leituras de sistema de arquivos assíncronas\",\n\t\"Threads for async file system writes\": \"Threads para gravações do sistema de arquivos assíncrono\",\n\t\"Threads for cryptographic functions\": \"Threads para funções criptográficas\",\n\t\"Threads for database operations\": \"Threads para operações de banco de dados\",\n\t\"Sync execution\": \"Execução de sincronização\",\n\t\"Custom\": \"Personalizado\",\n\t\"Custom socks proxy address for trackers\": \"Endereço de proxy de meias personalizadas para trackers\",\n\t\"Your file server is accessible on these ips. (default: detect automatically)\": \"Seu servidor de arquivos está acessível nesses ips. (padrão: detectar automaticamente)\",\n\t\"Detect automatically\": \"Detectar automaticamente\",\n\t\" configuration item value changed\": \" valor do item de configuração alterado\"\n\n}\n"
  },
  {
    "path": "plugins/UiConfig/languages/zh.json",
    "content": "{\n\t\"ZeroNet config\": \"ZeroNet配置\",\n\t\"Web Interface\": \"网页界面\",\n\t\"Open web browser on ZeroNet startup\": \"ZeroNet启动时,打开浏览器\",\n\n\t\"Network\": \"网络\",\n\t\"Offline mode\": \"离线模式\",\n\t\"Disable network communication.\": \"关闭网络通信.\",\n\t\"File server network\": \"文件服务器网络\",\n\t\"Accept incoming peers using IPv4 or IPv6 address. (default: dual)\": \"使用IPv4或IPv6地址接受传入的节点请求. (默认:双重)\",\n\t\"Dual (IPv4 & IPv6)\": \"双重 (IPv4与IPv6)\",\n\t\"File server port\": \"文件服务器端口\",\n\t\"Other peers will use this port to reach your served sites. (default: 15441)\": \"其他节点可通过该端口访问您服务的站点. (默认:15441)\",\n\t\"File server external ip\": \"文件服务器外部IP\",\n\t\"Detect automatically\": \"自动检测\",\n\t\"Your file server is accessible on these ips. (default: detect automatically)\": \"其他节点可通过这些IP访问您的文件服务器. (默认:自动检测)\",\n\n\t\"Disable: Don't connect to peers on Tor network\": \"关闭: 不连接洋葱网络中的节点\",\n\t\"Enable: Only use Tor for Tor network peers\": \"开启: Tor仅用于连接洋葱网络中的节点\",\n\t\"Always: Use Tor for every connections to hide your IP address (slower)\": \"总是: 将Tor用于每个网络连接,从而隐藏您的IP地址 (很慢)\",\n\n\t\"Disable\": \"关闭\",\n\t\"Enable\": \"开启\",\n\t\"Always\": \"总是\",\n\n\t\"Use Tor bridges\": \"使用Tor网桥\",\n\t\"Use obfuscated bridge relays to avoid network level Tor block (even slower)\": \"使用混淆网桥中继,从而避免网络层Tor阻碍 (超级慢)\",\n\n\t\"Discover new peers using these adresses\": \"使用这些地址发现新节点\",\n\n\t\"Trackers files\": \"Trackers文件\",\n\t\"Load additional list of torrent trackers dynamically, from a file\": \"从一个文件中,动态加载额外的种子Trackers列表\",\n\t\"Eg.: data/trackers.json\": \"例如: data/trackers.json\",\n\n\t\"Proxy for tracker connections\": \"Tracker连接代理\",\n\t\"Custom\": \"自定义\",\n\t\"Custom socks proxy address for trackers\": \"Tracker的自定义socks代理地址\",\n\n\t\"Performance\": \"性能\",\n\t\"Level of logging to file\": \"日志记录级别\",\n\t\"Everything\": \"记录全部\",\n\t\"Only important messages\": \"仅记录重要信息\",\n\t\"Only errors\": \"仅记录错误\",\n\t\"Threads for async file system reads\": \"文件系统异步读取线程\",\n\t\"Threads for async file system writes\": \"文件系统异步写入线程\",\n\t\"Threads for cryptographic functions\": \"密码函数线程\",\n\t\"Threads for database operations\": \"数据库操作线程\",\n\t\"Sync read\": \"同步读取\",\n\t\"Sync write\": \"同步写入\",\n\t\"Sync execution\": \"同步执行\",\n\t\"1 thread\": \"1个线程\",\n\t\"2 threads\": \"2个线程\",\n\t\"3 threads\": \"3个线程\",\n\t\"4 threads\": \"4个线程\",\n\t\"5 threads\": \"5个线程\",\n\t\"10 threads\": \"10个线程\",\n\n\t\" configuration item value changed\": \" 个配置值已经改变\",\n\t\"Save settings\": \"保存配置\",\n\t\"Some changed settings requires restart\": \"一些配置已改变,需要重启才能生效\",\n\t\"Restart ZeroNet client\": \"重启ZeroNet客户端\"\n}\n"
  },
  {
    "path": "plugins/UiConfig/media/config.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>Settings - ZeroNet</title>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <link rel=\"stylesheet\" href=\"css/all.css?rev={rev}\" />\n</head>\n\n\n<h1>ZeroNet config</h1>\n\n<div class=\"content\" id=\"content\"></div>\n<div class=\"bottom\" id=\"bottom-save\"></div>\n<div class=\"bottom\" id=\"bottom-restart\"></div>\n\n<script type=\"text/javascript\" src=\"js/all.js?rev={rev}&lang={lang}\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "plugins/UiConfig/media/css/Config.css",
    "content": "body { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; backface-visibility: hidden; }\nh1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }\nh1 { background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }\nh2 { margin-top: 10px; }\nh3 { font-weight: normal }\na { color: #9760F9 }\na:hover { text-decoration: none }\n\n.link { background-color: transparent; outline: 5px solid transparent; transition: all 0.3s }\n.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; transition: none }\n\n.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; box-sizing: border-box; padding-bottom: 150px; }\n.section { margin: 0px 10%; }\n.config-items { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }\n.config-item { transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: relative; padding-bottom: 20px; padding-top: 10px; }\n.config-item.hidden { opacity: 0; height: 0px; padding: 0px; }\n.config-item .title { display: inline-block; line-height: 36px; }\n.config-item .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }\n.config-item .description { font-size: 14px; color: #666; line-height: 24px; }\n.config-item .value { display: inline-block;  white-space: nowrap; }\n.config-item .value-right { right: 0px; position: absolute; }\n.config-item .value-fullwidth { width: 100% }\n.config-item .marker {\n\tfont-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;\n\topacity: 0; pointer-events: none; transition: all 0.6s; transform: scale(2); color: #9760F9;\n}\n.config-item .marker.visible { opacity: 1; pointer-events: all; transform: scale(1); }\n.config-item .marker.changed { color: #2ecc71; }\n.config-item .marker.pending { color: #ffa200; }\n\n\n.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; border-radius: 3px; font-size: 17px; box-sizing: border-box; }\n.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }\n.input-textarea {  overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }\n\n.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }\n\n.value-right .input-text { text-align: right; width: 100px; }\n.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }\n.value-fullwidth { margin-top: 10px; }\n\n/* Checkbox */\n.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; border-radius: 15px; transition: all 0.3s ease-in-out; display: inline-block; }\n.checkbox-skin:before {\n\tcontent: \"\"; position: relative; width: 20px; background-color: white; height: 20px; display: block; border-radius: 100%; margin-top: 2px; margin-left: 2px;\n\ttransition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }\n.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }\n.checkbox.checked .checkbox-skin:before { margin-left: 27px; }\n.checkbox.checked .checkbox-skin { background-color: #2ECC71 }\n\n/* Bottom */\n\n.bottom {\n\twidth: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);\n\ttransition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: fixed; backface-visibility: hidden; box-sizing: border-box;\n}\n.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }\n.bottom .button { float: right; }\n.bottom.visible { bottom: 0px; box-shadow: 0px 0px 35px #dcdcdc; }\n.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }\n.bottom .title:before { content: \"•\"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }\n.bottom-restart .title:before { color: #ffa200; }\n\n.animate { transition: all 0.3s ease-out !important; }\n.animate-back { transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; }\n.animate-inout { transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; }\n"
  },
  {
    "path": "plugins/UiConfig/media/css/all.css",
    "content": "\n/* ---- Config.css ---- */\n\n\nbody { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; }\nh1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }\nh1 { background: -webkit-linear-gradient(33deg,#af3bff,#0d99c9);background: -moz-linear-gradient(33deg,#af3bff,#0d99c9);background: -o-linear-gradient(33deg,#af3bff,#0d99c9);background: -ms-linear-gradient(33deg,#af3bff,#0d99c9);background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }\nh2 { margin-top: 10px; }\nh3 { font-weight: normal }\na { color: #9760F9 }\na:hover { text-decoration: none }\n\n.link { background-color: transparent; outline: 5px solid transparent; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n\n.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; padding-bottom: 150px; }\n.section { margin: 0px 10%; }\n.config-items { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }\n.config-item { -webkit-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1) ; position: relative; padding-bottom: 20px; padding-top: 10px; }\n.config-item.hidden { opacity: 0; height: 0px; padding: 0px; }\n.config-item .title { display: inline-block; line-height: 36px; }\n.config-item .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }\n.config-item .description { font-size: 14px; color: #666; line-height: 24px; }\n.config-item .value { display: inline-block;  white-space: nowrap; }\n.config-item .value-right { right: 0px; position: absolute; }\n.config-item .value-fullwidth { width: 100% }\n.config-item .marker {\n\tfont-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;\n\topacity: 0; pointer-events: none; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ; -webkit-transform: scale(2); -moz-transform: scale(2); -o-transform: scale(2); -ms-transform: scale(2); transform: scale(2) ; color: #9760F9;\n}\n.config-item .marker.visible { opacity: 1; pointer-events: all; -webkit-transform: scale(1); -moz-transform: scale(1); -o-transform: scale(1); -ms-transform: scale(1); transform: scale(1) ; }\n.config-item .marker.changed { color: #2ecc71; }\n.config-item .marker.pending { color: #ffa200; }\n\n\n.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ; font-size: 17px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; }\n.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }\n.input-textarea {  overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }\n\n.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }\n\n.value-right .input-text { text-align: right; width: 100px; }\n.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }\n.value-fullwidth { margin-top: 10px; }\n\n/* Checkbox */\n.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; -ms-border-radius: 15px; border-radius: 15px ; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; -ms-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out ; display: inline-block; }\n.checkbox-skin:before {\n\tcontent: \"\"; position: relative; width: 20px; background-color: white; height: 20px; display: block; -webkit-border-radius: 100%; -moz-border-radius: 100%; -o-border-radius: 100%; -ms-border-radius: 100%; border-radius: 100% ; margin-top: 2px; margin-left: 2px;\n\t-webkit-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -moz-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -o-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -ms-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86) ;\n}\n.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }\n.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }\n.checkbox.checked .checkbox-skin:before { margin-left: 27px; }\n.checkbox.checked .checkbox-skin { background-color: #2ECC71 }\n\n/* Bottom */\n\n.bottom {\n\twidth: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);\n\t-webkit-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1) ; position: fixed; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;\n}\n.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }\n.bottom .button { float: right; }\n.bottom.visible { bottom: 0px; -webkit-box-shadow: 0px 0px 35px #dcdcdc; -moz-box-shadow: 0px 0px 35px #dcdcdc; -o-box-shadow: 0px 0px 35px #dcdcdc; -ms-box-shadow: 0px 0px 35px #dcdcdc; box-shadow: 0px 0px 35px #dcdcdc ; }\n.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }\n.bottom .title:before { content: \"•\"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }\n.bottom-restart .title:before { color: #ffa200; }\n\n.animate { -webkit-transition: all 0.3s ease-out !important; -moz-transition: all 0.3s ease-out !important; -o-transition: all 0.3s ease-out !important; -ms-transition: all 0.3s ease-out !important; transition: all 0.3s ease-out !important ; }\n.animate-back { -webkit-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -moz-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -o-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -ms-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important ; }\n.animate-inout { -webkit-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -moz-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -o-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -ms-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important ; }\n\n\n/* ---- button.css ---- */\n\n\n/* Button */\n.button {\n\tbackground-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;\n\t-webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; border-bottom: 2px solid #E8BE29; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; text-decoration: none;\n}\n.button:hover { border-color: white; border-bottom: 2px solid #BD960C; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  ; background-color: #FDEB07 }\n.button:active { position: relative; top: 1px }\n.button.loading {\n\tcolor: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;\n\t-webkit-transition: all 0.5s ease-out  ; -moz-transition: all 0.5s ease-out  ; -o-transition: all 0.5s ease-out  ; -ms-transition: all 0.5s ease-out  ; transition: all 0.5s ease-out   ; pointer-events: none; border-bottom: 2px solid #666\n}\n.button.disabled { color: #DDD; background-color: #999;  pointer-events: none; border-bottom: 2px solid #666 }\n\n\n/* ---- fonts.css ---- */\n\n\n/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */\n/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */\n\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 400;\n    src:\n        local('Roboto'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: bold;\n  src:\n  \tlocal('Roboto Medium'),\n  \turl(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'),\n}\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 200;\n    src:\n        local('Roboto Light'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}"
  },
  {
    "path": "plugins/UiConfig/media/css/button.css",
    "content": "/* Button */\n.button {\n\tbackground-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;\n\tborder-radius: 2px; border-bottom: 2px solid #E8BE29; transition: all 0.5s ease-out; text-decoration: none;\n}\n.button:hover { border-color: white; border-bottom: 2px solid #BD960C; transition: none ; background-color: #FDEB07 }\n.button:active { position: relative; top: 1px }\n.button.loading {\n\tcolor: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;\n\ttransition: all 0.5s ease-out  ; pointer-events: none; border-bottom: 2px solid #666\n}\n.button.disabled { color: #DDD; background-color: #999;  pointer-events: none; border-bottom: 2px solid #666 }\n"
  },
  {
    "path": "plugins/UiConfig/media/css/fonts.css",
    "content": "/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */\n/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */\n\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 400;\n    src:\n        local('Roboto'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: bold;\n  src:\n  \tlocal('Roboto Medium'),\n  \turl(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'),\n}\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 200;\n    src:\n        local('Roboto Light'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}"
  },
  {
    "path": "plugins/UiConfig/media/js/ConfigStorage.coffee",
    "content": "class ConfigStorage extends Class\n\tconstructor: (@config) ->\n\t\t@items = []\n\t\t@createSections()\n\t\t@setValues(@config)\n\n\tsetValues: (values) ->\n\t\tfor section in @items\n\t\t\tfor item in section.items\n\t\t\t\tif not values[item.key]\n\t\t\t\t\tcontinue\n\t\t\t\titem.value = @formatValue(values[item.key].value)\n\t\t\t\titem.default = @formatValue(values[item.key].default)\n\t\t\t\titem.pending = values[item.key].pending\n\t\t\t\tvalues[item.key].item = item\n\n\tformatValue: (value) ->\n\t\tif not value\n\t\t\treturn false\n\t\telse if typeof(value) == \"object\"\n\t\t\treturn value.join(\"\\n\")\n\t\telse if typeof(value) == \"number\"\n\t\t\treturn value.toString()\n\t\telse\n\t\t\treturn value\n\n\tdeformatValue: (value, type) ->\n\t\tif type == \"object\" and typeof(value) == \"string\"\n\t\t\tif not value.length\n\t\t\t\treturn value = null\n\t\t\telse\n\t\t\t\treturn value.split(\"\\n\")\n\t\tif type == \"boolean\" and not value\n\t\t\treturn false\n\t\telse if type == \"number\"\n\t\t\tif typeof(value) == \"number\"\n\t\t\t\treturn value.toString()\n\t\t\telse if not value\n\t\t\t\treturn \"0\"\n\t\t\telse\n\t\t\t\treturn value\n\t\telse\n\t\t\treturn value\n\n\tcreateSections: ->\n\t\t# Web Interface\n\t\tsection = @createSection(\"Web Interface\")\n\n\t\tsection.items.push\n\t\t\tkey: \"open_browser\"\n\t\t\ttitle: \"Open web browser on ZeroNet startup\"\n\t\t\ttype: \"checkbox\"\n\n\t\t# Network\n\t\tsection = @createSection(\"Network\")\n\t\tsection.items.push\n\t\t\tkey: \"offline\"\n\t\t\ttitle: \"Offline mode\"\n\t\t\ttype: \"checkbox\"\n\t\t\tdescription: \"Disable network communication.\"\n\n\t\tsection.items.push\n\t\t\tkey: \"fileserver_ip_type\"\n\t\t\ttitle: \"File server network\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"IPv4\", value: \"ipv4\"}\n\t\t\t\t{title: \"IPv6\", value: \"ipv6\"}\n\t\t\t\t{title: \"Dual (IPv4 & IPv6)\", value: \"dual\"}\n\t\t\t]\n\t\t\tdescription: \"Accept incoming peers using IPv4 or IPv6 address. (default: dual)\"\n\n\t\tsection.items.push\n\t\t\tkey: \"fileserver_port\"\n\t\t\ttitle: \"File server port\"\n\t\t\ttype: \"text\"\n\t\t\tvalid_pattern: /[0-9]*/\n\t\t\tdescription: \"Other peers will use this port to reach your served sites. (default: randomize)\"\n\n\t\tsection.items.push\n\t\t\tkey: \"ip_external\"\n\t\t\ttitle: \"File server external ip\"\n\t\t\ttype: \"textarea\"\n\t\t\tplaceholder: \"Detect automatically\"\n\t\t\tdescription: \"Your file server is accessible on these ips. (default: detect automatically)\"\n\n\t\tsection.items.push\n\t\t\ttitle: \"Tor\"\n\t\t\tkey: \"tor\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Disable\", value: \"disable\"}\n\t\t\t\t{title: \"Enable\", value: \"enable\"}\n\t\t\t\t{title: \"Always\", value: \"always\"}\n\t\t\t]\n\t\t\tdescription: [\n\t\t\t\t\"Disable: Don't connect to peers on Tor network\", h(\"br\"),\n\t\t\t\t\"Enable: Only use Tor for Tor network peers\", h(\"br\"),\n\t\t\t\t\"Always: Use Tor for every connections to hide your IP address (slower)\"\n\t\t\t]\n\n\t\tsection.items.push\n\t\t\ttitle: \"Use Tor bridges\"\n\t\t\tkey: \"tor_use_bridges\"\n\t\t\ttype: \"checkbox\"\n\t\t\tdescription: \"Use obfuscated bridge relays to avoid network level Tor block (even slower)\"\n\t\t\tisHidden: ->\n\t\t\t\treturn not Page.server_info.tor_has_meek_bridges\n\n\t\tsection.items.push\n\t\t\ttitle: \"Trackers\"\n\t\t\tkey: \"trackers\"\n\t\t\ttype: \"textarea\"\n\t\t\tdescription: \"Discover new peers using these adresses\"\n\n\t\tsection.items.push\n\t\t\ttitle: \"Trackers files\"\n\t\t\tkey: \"trackers_file\"\n\t\t\ttype: \"textarea\"\n\t\t\tdescription: \"Load additional list of torrent trackers dynamically, from a file\"\n\t\t\tplaceholder: \"Eg.: {data_dir}/trackers.json\"\n\t\t\tvalue_pos: \"fullwidth\"\n\n\t\tsection.items.push\n\t\t\ttitle: \"Proxy for tracker connections\"\n\t\t\tkey: \"trackers_proxy\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Custom\", value: \"\"}\n\t\t\t\t{title: \"Tor\", value: \"tor\"}\n\t\t\t\t{title: \"Disable\", value: \"disable\"}\n\t\t\t]\n\t\t\tisHidden: ->\n\t\t\t\tPage.values[\"tor\"] == \"always\"\n\n\t\tsection.items.push\n\t\t\ttitle: \"Custom socks proxy address for trackers\"\n\t\t\tkey: \"trackers_proxy\"\n\t\t\ttype: \"text\"\n\t\t\tplaceholder: \"Eg.: 127.0.0.1:1080\"\n\t\t\tvalue_pos: \"fullwidth\"\n\t\t\tvalid_pattern: /.+:[0-9]+/\n\t\t\tisHidden: =>\n\t\t\t\tPage.values[\"trackers_proxy\"] in [\"tor\", \"disable\"]\n\n\t\t# Performance\n\t\tsection = @createSection(\"Performance\")\n\n\t\tsection.items.push\n\t\t\tkey: \"log_level\"\n\t\t\ttitle: \"Level of logging to file\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Everything\", value: \"DEBUG\"}\n\t\t\t\t{title: \"Only important messages\", value: \"INFO\"}\n\t\t\t\t{title: \"Only errors\", value: \"ERROR\"}\n\t\t\t]\n\n\t\tsection.items.push\n\t\t\tkey: \"threads_fs_read\"\n\t\t\ttitle: \"Threads for async file system reads\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Sync read\", value: 0}\n\t\t\t\t{title: \"1 thread\", value: 1}\n\t\t\t\t{title: \"2 threads\", value: 2}\n\t\t\t\t{title: \"3 threads\", value: 3}\n\t\t\t\t{title: \"4 threads\", value: 4}\n\t\t\t\t{title: \"5 threads\", value: 5}\n\t\t\t\t{title: \"10 threads\", value: 10}\n\t\t\t]\n\n\t\tsection.items.push\n\t\t\tkey: \"threads_fs_write\"\n\t\t\ttitle: \"Threads for async file system writes\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Sync write\", value: 0}\n\t\t\t\t{title: \"1 thread\", value: 1}\n\t\t\t\t{title: \"2 threads\", value: 2}\n\t\t\t\t{title: \"3 threads\", value: 3}\n\t\t\t\t{title: \"4 threads\", value: 4}\n\t\t\t\t{title: \"5 threads\", value: 5}\n\t\t\t\t{title: \"10 threads\", value: 10}\n\t\t\t]\n\n\t\tsection.items.push\n\t\t\tkey: \"threads_crypt\"\n\t\t\ttitle: \"Threads for cryptographic functions\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Sync execution\", value: 0}\n\t\t\t\t{title: \"1 thread\", value: 1}\n\t\t\t\t{title: \"2 threads\", value: 2}\n\t\t\t\t{title: \"3 threads\", value: 3}\n\t\t\t\t{title: \"4 threads\", value: 4}\n\t\t\t\t{title: \"5 threads\", value: 5}\n\t\t\t\t{title: \"10 threads\", value: 10}\n\t\t\t]\n\n\t\tsection.items.push\n\t\t\tkey: \"threads_db\"\n\t\t\ttitle: \"Threads for database operations\"\n\t\t\ttype: \"select\"\n\t\t\toptions: [\n\t\t\t\t{title: \"Sync execution\", value: 0}\n\t\t\t\t{title: \"1 thread\", value: 1}\n\t\t\t\t{title: \"2 threads\", value: 2}\n\t\t\t\t{title: \"3 threads\", value: 3}\n\t\t\t\t{title: \"4 threads\", value: 4}\n\t\t\t\t{title: \"5 threads\", value: 5}\n\t\t\t\t{title: \"10 threads\", value: 10}\n\t\t\t]\n\n\tcreateSection: (title) =>\n\t\tsection = {}\n\t\tsection.title = title\n\t\tsection.items = []\n\t\t@items.push(section)\n\t\treturn section\n\nwindow.ConfigStorage = ConfigStorage\n"
  },
  {
    "path": "plugins/UiConfig/media/js/ConfigView.coffee",
    "content": "class ConfigView extends Class\n\tconstructor: () ->\n\t\t@\n\n\trender: ->\n\t\t@config_storage.items.map @renderSection\n\n\trenderSection: (section) =>\n\t\th(\"div.section\", {key: section.title}, [\n\t\t\th(\"h2\", section.title),\n\t\t\th(\"div.config-items\", section.items.map @renderSectionItem)\n\t\t])\n\n\thandleResetClick: (e) =>\n\t\tnode = e.currentTarget\n\t\tconfig_key = node.attributes.config_key.value\n\t\tdefault_value = node.attributes.default_value?.value\n\t\tPage.cmd \"wrapperConfirm\", [\"Reset #{config_key} value?\", \"Reset to default\"], (res) =>\n\t\t\tif (res)\n\t\t\t\t@values[config_key] = default_value\n\t\t\tPage.projector.scheduleRender()\n\n\trenderSectionItem: (item) =>\n\t\tvalue_pos = item.value_pos\n\n\t\tif item.type == \"textarea\"\n\t\t\tvalue_pos ?= \"fullwidth\"\n\t\telse\n\t\t\tvalue_pos ?= \"right\"\n\n\t\tvalue_changed = @config_storage.formatValue(@values[item.key]) != item.value\n\t\tvalue_default = @config_storage.formatValue(@values[item.key]) == item.default\n\n\t\tif item.key in [\"open_browser\", \"fileserver_port\"]  # Value default for some settings makes no sense\n\t\t\tvalue_default = true\n\n\t\tmarker_title = \"Changed from default value: #{item.default} -> #{@values[item.key]}\"\n\t\tif item.pending\n\t\t\tmarker_title += \" (change pending until client restart)\"\n\n\t\tif item.isHidden?()\n\t\t\treturn null\n\n\t\th(\"div.config-item\", {key: item.title, enterAnimation: Animation.slideDown, exitAnimation: Animation.slideUpInout}, [\n\t\t\th(\"div.title\", [\n\t\t\t\th(\"h3\", item.title),\n\t\t\t\th(\"div.description\", item.description)\n\t\t\t])\n\t\t\th(\"div.value.value-#{value_pos}\",\n\t\t\t\tif item.type == \"select\"\n\t\t\t\t\t@renderValueSelect(item)\n\t\t\t\telse if item.type == \"checkbox\"\n\t\t\t\t\t@renderValueCheckbox(item)\n\t\t\t\telse if item.type == \"textarea\"\n\t\t\t\t\t@renderValueTextarea(item)\n\t\t\t\telse\n\t\t\t\t\t@renderValueText(item)\n\t\t\t\th(\"a.marker\", {\n\t\t\t\t\thref: \"#Reset\", title: marker_title,\n\t\t\t\t\tonclick: @handleResetClick, config_key: item.key, default_value: item.default,\n\t\t\t\t\tclasses: {default: value_default, changed: value_changed, visible: not value_default or value_changed or item.pending, pending: item.pending}\n\t\t\t\t}, \"\\u2022\")\n\t\t\t)\n\t\t])\n\n\t# Values\n\thandleInputChange: (e) =>\n\t\tnode = e.target\n\t\tconfig_key = node.attributes.config_key.value\n\t\t@values[config_key] = node.value\n\t\tPage.projector.scheduleRender()\n\n\thandleCheckboxChange: (e) =>\n\t\tnode = e.currentTarget\n\t\tconfig_key = node.attributes.config_key.value\n\t\tvalue = not node.classList.contains(\"checked\")\n\t\t@values[config_key] = value\n\t\tPage.projector.scheduleRender()\n\n\trenderValueText: (item) =>\n\t\tvalue = @values[item.key]\n\t\tif not value\n\t\t\tvalue = \"\"\n\t\th(\"input.input-#{item.type}\", {type: item.type, config_key: item.key, value: value, placeholder: item.placeholder, oninput: @handleInputChange})\n\n\tautosizeTextarea: (e) =>\n\t\tif e.currentTarget\n\t\t\t# @handleInputChange(e)\n\t\t\tnode = e.currentTarget\n\t\telse\n\t\t\tnode = e\n\t\theight_before = node.style.height\n\t\tif height_before\n\t\t\tnode.style.height = \"0px\"\n\t\th = node.offsetHeight\n\t\tscrollh = node.scrollHeight + 20\n\t\tif scrollh > h\n\t\t\tnode.style.height = scrollh + \"px\"\n\t\telse\n\t\t\tnode.style.height = height_before\n\n\trenderValueTextarea: (item) =>\n\t\tvalue = @values[item.key]\n\t\tif not value\n\t\t\tvalue = \"\"\n\t\th(\"textarea.input-#{item.type}.input-text\",{\n\t\t\ttype: item.type, config_key: item.key, oninput: @handleInputChange, afterCreate: @autosizeTextarea,\n\t\t\tupdateAnimation: @autosizeTextarea, value: value, placeholder: item.placeholder\n\t\t})\n\n\trenderValueCheckbox: (item) =>\n\t\tif @values[item.key] and @values[item.key] != \"False\"\n\t\t\tchecked = true\n\t\telse\n\t\t\tchecked = false\n\t\th(\"div.checkbox\", {onclick: @handleCheckboxChange, config_key: item.key, classes: {checked: checked}}, h(\"div.checkbox-skin\"))\n\n\trenderValueSelect: (item) =>\n\t\th(\"select.input-select\", {config_key: item.key, oninput: @handleInputChange},\n\t\t\titem.options.map (option) =>\n\t\t\t\th(\"option\", {selected: option.value.toString() == @values[item.key], value: option.value}, option.title)\n\t\t)\n\nwindow.ConfigView = ConfigView"
  },
  {
    "path": "plugins/UiConfig/media/js/UiConfig.coffee",
    "content": "window.h = maquette.h\n\nclass UiConfig extends ZeroFrame\n\tinit: ->\n\t\t@save_visible = true\n\t\t@config = null  # Setting currently set on the server\n\t\t@values = null  # Entered values on the page\n\t\t@config_view = new ConfigView()\n\t\twindow.onbeforeunload = =>\n\t\t\tif @getValuesChanged().length > 0\n\t\t\t\treturn true\n\t\t\telse\n\t\t\t\treturn null\n\n\tonOpenWebsocket: =>\n\t\t@cmd(\"wrapperSetTitle\", \"Config - ZeroNet\")\n\t\t@cmd \"serverInfo\", {}, (server_info) =>\n\t\t\t@server_info = server_info\n\t\t@restart_loading = false\n\t\t@updateConfig()\n\n\tupdateConfig: (cb) =>\n\t\t@cmd \"configList\", [], (res) =>\n\t\t\t@config = res\n\t\t\t@values = {}\n\t\t\t@config_storage = new ConfigStorage(@config)\n\t\t\t@config_view.values = @values\n\t\t\t@config_view.config_storage = @config_storage\n\t\t\tfor key, item of res\n\t\t\t\tvalue = item.value\n\t\t\t\t@values[key] = @config_storage.formatValue(value)\n\t\t\t@projector.scheduleRender()\n\t\t\tcb?()\n\n\tcreateProjector: =>\n\t\t@projector = maquette.createProjector()\n\t\t@projector.replace($(\"#content\"), @render)\n\t\t@projector.replace($(\"#bottom-save\"), @renderBottomSave)\n\t\t@projector.replace($(\"#bottom-restart\"), @renderBottomRestart)\n\n\tgetValuesChanged: =>\n\t\tvalues_changed = []\n\t\tfor key, value of @values\n\t\t\tif @config_storage.formatValue(value) != @config_storage.formatValue(@config[key]?.value)\n\t\t\t\tvalues_changed.push({key: key, value: value})\n\t\treturn values_changed\n\n\tgetValuesPending: =>\n\t\tvalues_pending = []\n\t\tfor key, item of @config\n\t\t\tif item.pending\n\t\t\t\tvalues_pending.push(key)\n\t\treturn values_pending\n\n\tsaveValues: (cb) =>\n\t\tchanged_values = @getValuesChanged()\n\t\tfor item, i in changed_values\n\t\t\tlast = i == changed_values.length - 1\n\t\t\tvalue = @config_storage.deformatValue(item.value, typeof(@config[item.key].default))\n\t\t\tdefault_value = @config_storage.deformatValue(@config[item.key].default, typeof(@config[item.key].default))\n\t\t\tvalue_same_as_default = JSON.stringify(default_value) == JSON.stringify(value)\n\n\t\t\tif @config[item.key].item.valid_pattern and not @config[item.key].item.isHidden?()\n\t\t\t\tmatch = value.match(@config[item.key].item.valid_pattern)\n\t\t\t\tif not match or match[0] != value\n\t\t\t\t\tmessage = \"Invalid value of #{@config[item.key].item.title}: #{value} (does not matches #{@config[item.key].item.valid_pattern})\"\n\t\t\t\t\tPage.cmd(\"wrapperNotification\", [\"error\", message])\n\t\t\t\t\tcb(false)\n\t\t\t\t\tbreak\n\n\t\t\tif value_same_as_default\n\t\t\t\tvalue = null\n\n\t\t\t@saveValue(item.key, value, if last then cb else null)\n\n\tsaveValue: (key, value, cb) =>\n\t\tif key == \"open_browser\"\n\t\t\tif value\n\t\t\t\tvalue = \"default_browser\"\n\t\t\telse\n\t\t\t\tvalue = \"False\"\n\n\t\tPage.cmd \"configSet\", [key, value], (res) =>\n\t\t\tif res != \"ok\"\n\t\t\t\tPage.cmd \"wrapperNotification\", [\"error\", res.error]\n\t\t\tcb?(true)\n\n\trender: =>\n\t\tif not @config\n\t\t\treturn h(\"div.content\")\n\n\t\th(\"div.content\", [\n\t\t\t@config_view.render()\n\t\t])\n\n\thandleSaveClick: =>\n\t\t@save_loading = true\n\t\t@logStart \"Save\"\n\t\t@saveValues (success) =>\n\t\t\t@save_loading = false\n\t\t\t@logEnd \"Save\"\n\t\t\tif success\n\t\t\t\t@updateConfig()\n\t\t\tPage.projector.scheduleRender()\n\t\treturn false\n\n\trenderBottomSave: =>\n\t\tvalues_changed = @getValuesChanged()\n\t\th(\"div.bottom.bottom-save\", {classes: {visible: values_changed.length}}, h(\"div.bottom-content\", [\n\t\t\th(\"div.title\", \"#{values_changed.length} configuration item value changed\"),\n\t\t\th(\"a.button.button-submit.button-save\", {href: \"#Save\", classes: {loading: @save_loading}, onclick: @handleSaveClick}, \"Save settings\")\n\t\t]))\n\n\thandleRestartClick: =>\n\t\t@restart_loading = true\n\t\tPage.cmd(\"serverShutdown\", {restart: true})\n\t\tPage.projector.scheduleRender()\n\t\treturn false\n\n\trenderBottomRestart: =>\n\t\tvalues_pending = @getValuesPending()\n\t\tvalues_changed = @getValuesChanged()\n\t\th(\"div.bottom.bottom-restart\", {classes: {visible: values_pending.length and not values_changed.length}}, h(\"div.bottom-content\", [\n\t\t\th(\"div.title\", \"Some changed settings requires restart\"),\n\t\t\th(\"a.button.button-submit.button-restart\", {href: \"#Restart\", classes: {loading: @restart_loading}, onclick: @handleRestartClick}, \"Restart ZeroNet client\")\n\t\t]))\n\nwindow.Page = new UiConfig()\nwindow.Page.createProjector()\n"
  },
  {
    "path": "plugins/UiConfig/media/js/all.js",
    "content": "\n/* ---- lib/Class.coffee ---- */\n\n\n(function() {\n  var Class,\n    slice = [].slice;\n\n  Class = (function() {\n    function Class() {}\n\n    Class.prototype.trace = true;\n\n    Class.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      if (!this.trace) {\n        return;\n      }\n      if (typeof console === 'undefined') {\n        return;\n      }\n      args.unshift(\"[\" + this.constructor.name + \"]\");\n      console.log.apply(console, args);\n      return this;\n    };\n\n    Class.prototype.logStart = function() {\n      var args, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      if (!this.trace) {\n        return;\n      }\n      this.logtimers || (this.logtimers = {});\n      this.logtimers[name] = +(new Date);\n      if (args.length > 0) {\n        this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(started)\"]));\n      }\n      return this;\n    };\n\n    Class.prototype.logEnd = function() {\n      var args, ms, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      ms = +(new Date) - this.logtimers[name];\n      this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(Done in \" + ms + \"ms)\"]));\n      return this;\n    };\n\n    return Class;\n\n  })();\n\n  window.Class = Class;\n\n}).call(this);\n\n/* ---- lib/Promise.coffee ---- */\n\n\n(function() {\n  var Promise,\n    slice = [].slice;\n\n  Promise = (function() {\n    Promise.when = function() {\n      var args, fn, i, len, num_uncompleted, promise, task, task_id, tasks;\n      tasks = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      num_uncompleted = tasks.length;\n      args = new Array(num_uncompleted);\n      promise = new Promise();\n      fn = function(task_id) {\n        return task.then(function() {\n          args[task_id] = Array.prototype.slice.call(arguments);\n          num_uncompleted--;\n          if (num_uncompleted === 0) {\n            return promise.complete.apply(promise, args);\n          }\n        });\n      };\n      for (task_id = i = 0, len = tasks.length; i < len; task_id = ++i) {\n        task = tasks[task_id];\n        fn(task_id);\n      }\n      return promise;\n    };\n\n    function Promise() {\n      this.resolved = false;\n      this.end_promise = null;\n      this.result = null;\n      this.callbacks = [];\n    }\n\n    Promise.prototype.resolve = function() {\n      var back, callback, i, len, ref;\n      if (this.resolved) {\n        return false;\n      }\n      this.resolved = true;\n      this.data = arguments;\n      if (!arguments.length) {\n        this.data = [true];\n      }\n      this.result = this.data[0];\n      ref = this.callbacks;\n      for (i = 0, len = ref.length; i < len; i++) {\n        callback = ref[i];\n        back = callback.apply(callback, this.data);\n      }\n      if (this.end_promise) {\n        return this.end_promise.resolve(back);\n      }\n    };\n\n    Promise.prototype.fail = function() {\n      return this.resolve(false);\n    };\n\n    Promise.prototype.then = function(callback) {\n      if (this.resolved === true) {\n        callback.apply(callback, this.data);\n        return;\n      }\n      this.callbacks.push(callback);\n      return this.end_promise = new Promise();\n    };\n\n    return Promise;\n\n  })();\n\n  window.Promise = Promise;\n\n\n  /*\n  s = Date.now()\n  log = (text) ->\n  \tconsole.log Date.now()-s, Array.prototype.slice.call(arguments).join(\", \")\n  \n  log \"Started\"\n  \n  cmd = (query) ->\n  \tp = new Promise()\n  \tsetTimeout ( ->\n  \t\tp.resolve query+\" Result\"\n  \t), 100\n  \treturn p\n  \n  back = cmd(\"SELECT * FROM message\").then (res) ->\n  \tlog res\n  \treturn \"Return from query\"\n  .then (res) ->\n  \tlog \"Back then\", res\n  \n  log \"Query started\", back\n   */\n\n}).call(this);\n\n/* ---- lib/Prototypes.coffee ---- */\n\n\n(function() {\n  String.prototype.startsWith = function(s) {\n    return this.slice(0, s.length) === s;\n  };\n\n  String.prototype.endsWith = function(s) {\n    return s === '' || this.slice(-s.length) === s;\n  };\n\n  String.prototype.repeat = function(count) {\n    return new Array(count + 1).join(this);\n  };\n\n  window.isEmpty = function(obj) {\n    var key;\n    for (key in obj) {\n      return false;\n    }\n    return true;\n  };\n\n}).call(this);\n\n/* ---- lib/maquette.js ---- */\n\n\n(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['exports'], factory);\n    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {\n        // CommonJS\n        factory(exports);\n    } else {\n        // Browser globals\n        factory(root.maquette = {});\n    }\n}(this, function (exports) {\n    'use strict';\n    ;\n    ;\n    ;\n    ;\n    var NAMESPACE_W3 = 'http://www.w3.org/';\n    var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';\n    var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';\n    // Utilities\n    var emptyArray = [];\n    var extend = function (base, overrides) {\n        var result = {};\n        Object.keys(base).forEach(function (key) {\n            result[key] = base[key];\n        });\n        if (overrides) {\n            Object.keys(overrides).forEach(function (key) {\n                result[key] = overrides[key];\n            });\n        }\n        return result;\n    };\n    // Hyperscript helper functions\n    var same = function (vnode1, vnode2) {\n        if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {\n            return false;\n        }\n        if (vnode1.properties && vnode2.properties) {\n            if (vnode1.properties.key !== vnode2.properties.key) {\n                return false;\n            }\n            return vnode1.properties.bind === vnode2.properties.bind;\n        }\n        return !vnode1.properties && !vnode2.properties;\n    };\n    var toTextVNode = function (data) {\n        return {\n            vnodeSelector: '',\n            properties: undefined,\n            children: undefined,\n            text: data.toString(),\n            domNode: null\n        };\n    };\n    var appendChildren = function (parentSelector, insertions, main) {\n        for (var i = 0; i < insertions.length; i++) {\n            var item = insertions[i];\n            if (Array.isArray(item)) {\n                appendChildren(parentSelector, item, main);\n            } else {\n                if (item !== null && item !== undefined) {\n                    if (!item.hasOwnProperty('vnodeSelector')) {\n                        item = toTextVNode(item);\n                    }\n                    main.push(item);\n                }\n            }\n        }\n    };\n    // Render helper functions\n    var missingTransition = function () {\n        throw new Error('Provide a transitions object to the projectionOptions to do animations');\n    };\n    var DEFAULT_PROJECTION_OPTIONS = {\n        namespace: undefined,\n        eventHandlerInterceptor: undefined,\n        styleApplyer: function (domNode, styleName, value) {\n            // Provides a hook to add vendor prefixes for browsers that still need it.\n            domNode.style[styleName] = value;\n        },\n        transitions: {\n            enter: missingTransition,\n            exit: missingTransition\n        }\n    };\n    var applyDefaultProjectionOptions = function (projectorOptions) {\n        return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);\n    };\n    var checkStyleValue = function (styleValue) {\n        if (typeof styleValue !== 'string') {\n            throw new Error('Style values must be strings');\n        }\n    };\n    var setProperties = function (domNode, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            /* tslint:disable:no-var-keyword: edge case */\n            var propValue = properties[propName];\n            /* tslint:enable:no-var-keyword */\n            if (propName === 'className') {\n                throw new Error('Property \"className\" is not supported, use \"class\".');\n            } else if (propName === 'class') {\n                if (domNode.className) {\n                    // May happen if classes is specified before class\n                    domNode.className += ' ' + propValue;\n                } else {\n                    domNode.className = propValue;\n                }\n            } else if (propName === 'classes') {\n                // object with string keys and boolean values\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    if (propValue[className]) {\n                        domNode.classList.add(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                // object with string keys and string (!) values\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var styleValue = propValue[styleName];\n                    if (styleValue) {\n                        checkStyleValue(styleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, styleValue);\n                    }\n                }\n            } else if (propName === 'key') {\n                continue;\n            } else if (propValue === null || propValue === undefined) {\n                continue;\n            } else {\n                var type = typeof propValue;\n                if (type === 'function') {\n                    if (propName.lastIndexOf('on', 0) === 0) {\n                        if (eventHandlerInterceptor) {\n                            propValue = eventHandlerInterceptor(propName, propValue, domNode, properties);    // intercept eventhandlers\n                        }\n                        if (propName === 'oninput') {\n                            (function () {\n                                // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput\n                                var oldPropValue = propValue;\n                                propValue = function (evt) {\n                                    evt.target['oninput-value'] = evt.target.value;\n                                    // may be HTMLTextAreaElement as well\n                                    oldPropValue.apply(this, [evt]);\n                                };\n                            }());\n                        }\n                        domNode[propName] = propValue;\n                    }\n                } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {\n                    if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                        domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                    } else {\n                        domNode.setAttribute(propName, propValue);\n                    }\n                } else {\n                    domNode[propName] = propValue;\n                }\n            }\n        }\n    };\n    var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var propertiesUpdated = false;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            // assuming that properties will be nullified instead of missing is by design\n            var propValue = properties[propName];\n            var previousValue = previousProperties[propName];\n            if (propName === 'class') {\n                if (previousValue !== propValue) {\n                    throw new Error('\"class\" property may not be updated. Use the \"classes\" property for conditional css classes.');\n                }\n            } else if (propName === 'classes') {\n                var classList = domNode.classList;\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    var on = !!propValue[className];\n                    var previousOn = !!previousValue[className];\n                    if (on === previousOn) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (on) {\n                        classList.add(className);\n                    } else {\n                        classList.remove(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var newStyleValue = propValue[styleName];\n                    var oldStyleValue = previousValue[styleName];\n                    if (newStyleValue === oldStyleValue) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (newStyleValue) {\n                        checkStyleValue(newStyleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, newStyleValue);\n                    } else {\n                        projectionOptions.styleApplyer(domNode, styleName, '');\n                    }\n                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };\n    var findIndexOfChild = function (children, sameAs, start) {\n        if (sameAs.vnodeSelector !== '') {\n            // Never scan for text-nodes\n            for (var i = start; i < children.length; i++) {\n                if (same(children[i], sameAs)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    var nodeAdded = function (vNode, transitions) {\n        if (vNode.properties) {\n            var enterAnimation = vNode.properties.enterAnimation;\n            if (enterAnimation) {\n                if (typeof enterAnimation === 'function') {\n                    enterAnimation(vNode.domNode, vNode.properties);\n                } else {\n                    transitions.enter(vNode.domNode, vNode.properties, enterAnimation);\n                }\n            }\n        }\n    };\n    var nodeToRemove = function (vNode, transitions) {\n        var domNode = vNode.domNode;\n        if (vNode.properties) {\n            var exitAnimation = vNode.properties.exitAnimation;\n            if (exitAnimation) {\n                domNode.style.pointerEvents = 'none';\n                var removeDomNode = function () {\n                    if (domNode.parentNode) {\n                        domNode.parentNode.removeChild(domNode);\n                    }\n                };\n                if (typeof exitAnimation === 'function') {\n                    exitAnimation(domNode, removeDomNode, vNode.properties);\n                    return;\n                } else {\n                    transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);\n                    return;\n                }\n            }\n        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;\n        var transitions = projectionOptions.transitions;\n        var oldIndex = 0;\n        var newIndex = 0;\n        var i;\n        var textUpdated = false;\n        while (newIndex < newChildrenLength) {\n            var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;\n            var newChild = newChildren[newIndex];\n            if (oldChild !== undefined && same(oldChild, newChild)) {\n                textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;\n                oldIndex++;\n            } else {\n                var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);\n                if (findOldIndex >= 0) {\n                    // Remove preceding missing children\n                    for (i = oldIndex; i < findOldIndex; i++) {\n                        nodeToRemove(oldChildren[i], transitions);\n                        checkDistinguishable(oldChildren, i, vnode, 'removed');\n                    }\n                    textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;\n                    oldIndex = findOldIndex + 1;\n                } else {\n                    // New child\n                    createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);\n                    nodeAdded(newChild, transitions);\n                    checkDistinguishable(newChildren, newIndex, vnode, 'added');\n                }\n            }\n            newIndex++;\n        }\n        if (oldChildrenLength > oldIndex) {\n            // Remove child fragments\n            for (i = oldIndex; i < oldChildrenLength; i++) {\n                nodeToRemove(oldChildren[i], transitions);\n                checkDistinguishable(oldChildren, i, vnode, 'removed');\n            }\n        }\n        return textUpdated;\n    };\n    var addChildren = function (domNode, children, projectionOptions) {\n        if (!children) {\n            return;\n        }\n        for (var i = 0; i < children.length; i++) {\n            createDom(children[i], domNode, undefined, projectionOptions);\n        }\n    };\n    var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {\n        addChildren(domNode, vnode.children, projectionOptions);\n        // children before properties, needed for value property of <select>.\n        if (vnode.text) {\n            domNode.textContent = vnode.text;\n        }\n        setProperties(domNode, vnode.properties, projectionOptions);\n        if (vnode.properties && vnode.properties.afterCreate) {\n            vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n        }\n    };\n    createDom = function (vnode, parentNode, insertBefore, projectionOptions) {\n        var domNode, i, c, start = 0, type, found;\n        var vnodeSelector = vnode.vnodeSelector;\n        if (vnodeSelector === '') {\n            domNode = vnode.domNode = document.createTextNode(vnode.text);\n            if (insertBefore !== undefined) {\n                parentNode.insertBefore(domNode, insertBefore);\n            } else {\n                parentNode.appendChild(domNode);\n            }\n        } else {\n            for (i = 0; i <= vnodeSelector.length; ++i) {\n                c = vnodeSelector.charAt(i);\n                if (i === vnodeSelector.length || c === '.' || c === '#') {\n                    type = vnodeSelector.charAt(start - 1);\n                    found = vnodeSelector.slice(start, i);\n                    if (type === '.') {\n                        domNode.classList.add(found);\n                    } else if (type === '#') {\n                        domNode.id = found;\n                    } else {\n                        if (found === 'svg') {\n                            projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n                        }\n                        if (projectionOptions.namespace !== undefined) {\n                            domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);\n                        } else {\n                            domNode = vnode.domNode = document.createElement(found);\n                        }\n                        if (insertBefore !== undefined) {\n                            parentNode.insertBefore(domNode, insertBefore);\n                        } else {\n                            parentNode.appendChild(domNode);\n                        }\n                    }\n                    start = i + 1;\n                }\n            }\n            initPropertiesAndChildren(domNode, vnode, projectionOptions);\n        }\n    };\n    updateDom = function (previous, vnode, projectionOptions) {\n        var domNode = previous.domNode;\n        var textUpdated = false;\n        if (previous === vnode) {\n            return false;    // By contract, VNode objects may not be modified anymore after passing them to maquette\n        }\n        var updated = false;\n        if (vnode.vnodeSelector === '') {\n            if (vnode.text !== previous.text) {\n                var newVNode = document.createTextNode(vnode.text);\n                domNode.parentNode.replaceChild(newVNode, domNode);\n                vnode.domNode = newVNode;\n                textUpdated = true;\n                return textUpdated;\n            }\n        } else {\n            if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {\n                projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n            }\n            if (previous.text !== vnode.text) {\n                updated = true;\n                if (vnode.text === undefined) {\n                    domNode.removeChild(domNode.firstChild);    // the only textnode presumably\n                } else {\n                    domNode.textContent = vnode.text;\n                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {\n            // Optional properties argument was omitted\n            properties = undefined;\n        }\n        var text = undefined;\n        var children = undefined;\n        var argsLength = arguments.length;\n        // Recognize a common special case where there is only a single text node\n        if (argsLength === childIndex + 1) {\n            var onlyChild = arguments[childIndex];\n            if (typeof onlyChild === 'string') {\n                text = onlyChild;\n            } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {\n                text = onlyChild[0];\n            }\n        }\n        if (text === undefined) {\n            children = [];\n            for (; childIndex < arguments.length; childIndex++) {\n                var child = arguments[childIndex];\n                if (child === null || child === undefined) {\n                    continue;\n                } else if (Array.isArray(child)) {\n                    appendChildren(selector, child, children);\n                } else if (child.hasOwnProperty('vnodeSelector')) {\n                    children.push(child);\n                } else {\n                    children.push(toTextVNode(child));\n                }\n            }\n        }\n        return {\n            vnodeSelector: selector,\n            properties: properties,\n            children: children,\n            text: text === '' ? undefined : text,\n            domNode: null\n        };\n    };\n    /**\n * Contains simple low-level utility functions to manipulate the real DOM.\n */\n    exports.dom = {\n        /**\n     * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in\n     * its [[Projection.domNode|domNode]] property.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection.\n     * @returns The [[Projection]] which also contains the DOM Node that was created.\n     */\n        create: function (vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, document.createElement('div'), undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Appends a new childnode to the DOM which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param parentNode - The parent node for the new childNode.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the [[Projection]].\n     * @returns The [[Projection]] that was created.\n     */\n        append: function (parentNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, parentNode, undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Inserts a new DOM node which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param beforeNode - The node that the DOM Node is inserted before.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.\n     * NOTE: [[VNode]] objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        insertBefore: function (beforeNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.\n     * This means that the virtual DOM and the real DOM will have one overlapping element.\n     * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects\n     * may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        merge: function (element, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            vnode.domNode = element;\n            initPropertiesAndChildren(element, vnode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        }\n    };\n    /**\n * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.\n * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.\n * For more information, see [[CalculationCache]].\n *\n * @param <Result> The type of the value that is cached.\n */\n    exports.createCache = function () {\n        var cachedInputs = undefined;\n        var cachedOutcome = undefined;\n        var result = {\n            invalidate: function () {\n                cachedOutcome = undefined;\n                cachedInputs = undefined;\n            },\n            result: function (inputs, calculation) {\n                if (cachedInputs) {\n                    for (var i = 0; i < inputs.length; i++) {\n                        if (cachedInputs[i] !== inputs[i]) {\n                            cachedOutcome = undefined;\n                        }\n                    }\n                }\n                if (!cachedOutcome) {\n                    cachedOutcome = calculation();\n                    cachedInputs = inputs;\n                }\n                return cachedOutcome;\n            }\n        };\n        return result;\n    };\n    /**\n * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.\n * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.\n *\n * @param <Source>       The type of source items. A database-record for instance.\n * @param <Target>       The type of target items. A [[Component]] for instance.\n * @param getSourceKey   `function(source)` that must return a key to identify each source object. The result must either be a string or a number.\n * @param createResult   `function(source, index)` that must create a new result object from a given source. This function is identical\n *                       to the `callback` argument in `Array.map(callback)`.\n * @param updateResult   `function(source, target, index)` that updates a result to an updated source.\n */\n    exports.createMapping = function (getSourceKey, createResult, updateResult) {\n        var keys = [];\n        var results = [];\n        return {\n            results: results,\n            map: function (newSources) {\n                var newKeys = newSources.map(getSourceKey);\n                var oldTargets = results.slice();\n                var oldIndex = 0;\n                for (var i = 0; i < newSources.length; i++) {\n                    var source = newSources[i];\n                    var sourceKey = newKeys[i];\n                    if (sourceKey === keys[oldIndex]) {\n                        results[i] = oldTargets[oldIndex];\n                        updateResult(source, oldTargets[oldIndex], i);\n                        oldIndex++;\n                    } else {\n                        var found = false;\n                        for (var j = 1; j < keys.length; j++) {\n                            var searchIndex = (oldIndex + j) % keys.length;\n                            if (keys[searchIndex] === sourceKey) {\n                                results[i] = oldTargets[searchIndex];\n                                updateResult(newSources[i], oldTargets[searchIndex], i);\n                                oldIndex = searchIndex + 1;\n                                found = true;\n                                break;\n                            }\n                        }\n                        if (!found) {\n                            results[i] = createResult(source, i);\n                        }\n                    }\n                }\n                results.length = newSources.length;\n                keys = newKeys;\n            }\n        };\n    };\n    /**\n * Creates a [[Projector]] instance using the provided projectionOptions.\n *\n * For more information, see [[Projector]].\n *\n * @param projectionOptions   Options that influence how the DOM is rendered and updated.\n */\n    exports.createProjector = function (projectorOptions) {\n        var projector;\n        var projectionOptions = applyDefaultProjectionOptions(projectorOptions);\n        projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {\n            return function () {\n                // intercept function calls (event handlers) to do a render afterwards.\n                projector.scheduleRender();\n                return eventHandler.apply(properties.bind || this, arguments);\n            };\n        };\n        var renderCompleted = true;\n        var scheduled;\n        var stopped = false;\n        var projections = [];\n        var renderFunctions = [];\n        // matches the projections array\n        var doRender = function () {\n            scheduled = undefined;\n            if (!renderCompleted) {\n                return;    // The last render threw an error, it should be logged in the browser console.\n            }\n            renderCompleted = false;\n            for (var i = 0; i < projections.length; i++) {\n                var updatedVnode = renderFunctions[i]();\n                projections[i].update(updatedVnode);\n            }\n            renderCompleted = true;\n        };\n        projector = {\n            scheduleRender: function () {\n                if (!scheduled && !stopped) {\n                    scheduled = requestAnimationFrame(doRender);\n                }\n            },\n            stop: function () {\n                if (scheduled) {\n                    cancelAnimationFrame(scheduled);\n                    scheduled = undefined;\n                }\n                stopped = true;\n            },\n            resume: function () {\n                stopped = false;\n                renderCompleted = true;\n                projector.scheduleRender();\n            },\n            append: function (parentNode, renderMaquetteFunction) {\n                projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            insertBefore: function (beforeNode, renderMaquetteFunction) {\n                projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            merge: function (domNode, renderMaquetteFunction) {\n                projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            replace: function (domNode, renderMaquetteFunction) {\n                var vnode = renderMaquetteFunction();\n                createDom(vnode, domNode.parentNode, domNode, projectionOptions);\n                domNode.parentNode.removeChild(domNode);\n                projections.push(createProjection(vnode, projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            detach: function (renderMaquetteFunction) {\n                for (var i = 0; i < renderFunctions.length; i++) {\n                    if (renderFunctions[i] === renderMaquetteFunction) {\n                        renderFunctions.splice(i, 1);\n                        return projections.splice(i, 1)[0];\n                    }\n                }\n                throw new Error('renderMaquetteFunction was not found');\n            }\n        };\n        return projector;\n    };\n}));\n\n\n/* ---- utils/Animation.coffee ---- */\n\n\n(function() {\n  var Animation;\n\n  Animation = (function() {\n    function Animation() {}\n\n    Animation.prototype.slideDown = function(elem, props) {\n      var cstyle, h, margin_bottom, margin_top, padding_bottom, padding_top, transition;\n      if (elem.offsetTop > 2000) {\n        return;\n      }\n      h = elem.offsetHeight;\n      cstyle = window.getComputedStyle(elem);\n      margin_top = cstyle.marginTop;\n      margin_bottom = cstyle.marginBottom;\n      padding_top = cstyle.paddingTop;\n      padding_bottom = cstyle.paddingBottom;\n      transition = cstyle.transition;\n      elem.style.boxSizing = \"border-box\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(0.6)\";\n      elem.style.opacity = \"0\";\n      elem.style.height = \"0px\";\n      elem.style.marginTop = \"0px\";\n      elem.style.marginBottom = \"0px\";\n      elem.style.paddingTop = \"0px\";\n      elem.style.paddingBottom = \"0px\";\n      elem.style.transition = \"none\";\n      setTimeout((function() {\n        elem.className += \" animate-inout\";\n        elem.style.height = h + \"px\";\n        elem.style.transform = \"scale(1)\";\n        elem.style.opacity = \"1\";\n        elem.style.marginTop = margin_top;\n        elem.style.marginBottom = margin_bottom;\n        elem.style.paddingTop = padding_top;\n        return elem.style.paddingBottom = padding_bottom;\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate-inout\");\n        elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null;\n        elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null;\n        elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null;\n        return elem.removeEventListener(\"transitionend\", arguments.callee, false);\n      });\n    };\n\n    Animation.prototype.slideUp = function(elem, remove_func, props) {\n      if (elem.offsetTop > 1000) {\n        return remove_func();\n      }\n      elem.className += \" animate-back\";\n      elem.style.boxSizing = \"border-box\";\n      elem.style.height = elem.offsetHeight + \"px\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(1)\";\n      elem.style.opacity = \"1\";\n      elem.style.pointerEvents = \"none\";\n      setTimeout((function() {\n        elem.style.height = \"0px\";\n        elem.style.marginTop = \"0px\";\n        elem.style.marginBottom = \"0px\";\n        elem.style.paddingTop = \"0px\";\n        elem.style.paddingBottom = \"0px\";\n        elem.style.transform = \"scale(0.8)\";\n        elem.style.borderTopWidth = \"0px\";\n        elem.style.borderBottomWidth = \"0px\";\n        return elem.style.opacity = \"0\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\" || e.elapsedTime >= 0.6) {\n          elem.removeEventListener(\"transitionend\", arguments.callee, false);\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.slideUpInout = function(elem, remove_func, props) {\n      elem.className += \" animate-inout\";\n      elem.style.boxSizing = \"border-box\";\n      elem.style.height = elem.offsetHeight + \"px\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(1)\";\n      elem.style.opacity = \"1\";\n      elem.style.pointerEvents = \"none\";\n      setTimeout((function() {\n        elem.style.height = \"0px\";\n        elem.style.marginTop = \"0px\";\n        elem.style.marginBottom = \"0px\";\n        elem.style.paddingTop = \"0px\";\n        elem.style.paddingBottom = \"0px\";\n        elem.style.transform = \"scale(0.8)\";\n        elem.style.borderTopWidth = \"0px\";\n        elem.style.borderBottomWidth = \"0px\";\n        return elem.style.opacity = \"0\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\" || e.elapsedTime >= 0.6) {\n          elem.removeEventListener(\"transitionend\", arguments.callee, false);\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.showRight = function(elem, props) {\n      elem.className += \" animate\";\n      elem.style.opacity = 0;\n      elem.style.transform = \"TranslateX(-20px) Scale(1.01)\";\n      setTimeout((function() {\n        elem.style.opacity = 1;\n        return elem.style.transform = \"TranslateX(0px) Scale(1)\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate\");\n        return elem.style.transform = elem.style.opacity = null;\n      });\n    };\n\n    Animation.prototype.show = function(elem, props) {\n      var delay, ref;\n      delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;\n      elem.style.opacity = 0;\n      setTimeout((function() {\n        return elem.className += \" animate\";\n      }), 1);\n      setTimeout((function() {\n        return elem.style.opacity = 1;\n      }), delay);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate\");\n        elem.style.opacity = null;\n        return elem.removeEventListener(\"transitionend\", arguments.callee, false);\n      });\n    };\n\n    Animation.prototype.hide = function(elem, remove_func, props) {\n      var delay, ref;\n      delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;\n      elem.className += \" animate\";\n      setTimeout((function() {\n        return elem.style.opacity = 0;\n      }), delay);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\") {\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.addVisibleClass = function(elem, props) {\n      return setTimeout(function() {\n        return elem.classList.add(\"visible\");\n      });\n    };\n\n    return Animation;\n\n  })();\n\n  window.Animation = new Animation();\n\n}).call(this);\n\n/* ---- utils/Dollar.coffee ---- */\n\n\n(function() {\n  window.$ = function(selector) {\n    if (selector.startsWith(\"#\")) {\n      return document.getElementById(selector.replace(\"#\", \"\"));\n    }\n  };\n\n}).call(this);\n\n/* ---- utils/ZeroFrame.coffee ---- */\n\n\n(function() {\n  var ZeroFrame,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  ZeroFrame = (function(superClass) {\n    extend(ZeroFrame, superClass);\n\n    function ZeroFrame(url) {\n      this.onCloseWebsocket = bind(this.onCloseWebsocket, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      this.onRequest = bind(this.onRequest, this);\n      this.onMessage = bind(this.onMessage, this);\n      this.url = url;\n      this.waiting_cb = {};\n      this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\");\n      this.connect();\n      this.next_message_id = 1;\n      this.history_state = {};\n      this.init();\n    }\n\n    ZeroFrame.prototype.init = function() {\n      return this;\n    };\n\n    ZeroFrame.prototype.connect = function() {\n      this.target = window.parent;\n      window.addEventListener(\"message\", this.onMessage, false);\n      this.cmd(\"innerReady\");\n      window.addEventListener(\"beforeunload\", (function(_this) {\n        return function(e) {\n          _this.log(\"save scrollTop\", window.pageYOffset);\n          _this.history_state[\"scrollTop\"] = window.pageYOffset;\n          return _this.cmd(\"wrapperReplaceState\", [_this.history_state, null]);\n        };\n      })(this));\n      return this.cmd(\"wrapperGetState\", [], (function(_this) {\n        return function(state) {\n          if (state != null) {\n            _this.history_state = state;\n          }\n          _this.log(\"restore scrollTop\", state, window.pageYOffset);\n          if (window.pageYOffset === 0 && state) {\n            return window.scroll(window.pageXOffset, state.scrollTop);\n          }\n        };\n      })(this));\n    };\n\n    ZeroFrame.prototype.onMessage = function(e) {\n      var cmd, message;\n      message = e.data;\n      cmd = message.cmd;\n      if (cmd === \"response\") {\n        if (this.waiting_cb[message.to] != null) {\n          return this.waiting_cb[message.to](message.result);\n        } else {\n          return this.log(\"Websocket callback not found:\", message);\n        }\n      } else if (cmd === \"wrapperReady\") {\n        return this.cmd(\"innerReady\");\n      } else if (cmd === \"ping\") {\n        return this.response(message.id, \"pong\");\n      } else if (cmd === \"wrapperOpenedWebsocket\") {\n        return this.onOpenWebsocket();\n      } else if (cmd === \"wrapperClosedWebsocket\") {\n        return this.onCloseWebsocket();\n      } else {\n        return this.onRequest(cmd, message.params);\n      }\n    };\n\n    ZeroFrame.prototype.onRequest = function(cmd, message) {\n      return this.log(\"Unknown request\", message);\n    };\n\n    ZeroFrame.prototype.response = function(to, result) {\n      return this.send({\n        \"cmd\": \"response\",\n        \"to\": to,\n        \"result\": result\n      });\n    };\n\n    ZeroFrame.prototype.cmd = function(cmd, params, cb) {\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      return this.send({\n        \"cmd\": cmd,\n        \"params\": params\n      }, cb);\n    };\n\n    ZeroFrame.prototype.send = function(message, cb) {\n      if (cb == null) {\n        cb = null;\n      }\n      message.wrapper_nonce = this.wrapper_nonce;\n      message.id = this.next_message_id;\n      this.next_message_id += 1;\n      this.target.postMessage(message, \"*\");\n      if (cb) {\n        return this.waiting_cb[message.id] = cb;\n      }\n    };\n\n    ZeroFrame.prototype.onOpenWebsocket = function() {\n      return this.log(\"Websocket open\");\n    };\n\n    ZeroFrame.prototype.onCloseWebsocket = function() {\n      return this.log(\"Websocket close\");\n    };\n\n    return ZeroFrame;\n\n  })(Class);\n\n  window.ZeroFrame = ZeroFrame;\n\n}).call(this);\n\n/* ---- ConfigStorage.coffee ---- */\n\n\n(function() {\n  var ConfigStorage,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  ConfigStorage = (function(superClass) {\n    extend(ConfigStorage, superClass);\n\n    function ConfigStorage(config) {\n      this.config = config;\n      this.createSection = bind(this.createSection, this);\n      this.items = [];\n      this.createSections();\n      this.setValues(this.config);\n    }\n\n    ConfigStorage.prototype.setValues = function(values) {\n      var i, item, len, ref, results, section;\n      ref = this.items;\n      results = [];\n      for (i = 0, len = ref.length; i < len; i++) {\n        section = ref[i];\n        results.push((function() {\n          var j, len1, ref1, results1;\n          ref1 = section.items;\n          results1 = [];\n          for (j = 0, len1 = ref1.length; j < len1; j++) {\n            item = ref1[j];\n            if (!values[item.key]) {\n              continue;\n            }\n            item.value = this.formatValue(values[item.key].value);\n            item[\"default\"] = this.formatValue(values[item.key][\"default\"]);\n            item.pending = values[item.key].pending;\n            results1.push(values[item.key].item = item);\n          }\n          return results1;\n        }).call(this));\n      }\n      return results;\n    };\n\n    ConfigStorage.prototype.formatValue = function(value) {\n      if (!value) {\n        return false;\n      } else if (typeof value === \"object\") {\n        return value.join(\"\\n\");\n      } else if (typeof value === \"number\") {\n        return value.toString();\n      } else {\n        return value;\n      }\n    };\n\n    ConfigStorage.prototype.deformatValue = function(value, type) {\n      if (type === \"object\" && typeof value === \"string\") {\n        if (!value.length) {\n          return value = null;\n        } else {\n          return value.split(\"\\n\");\n        }\n      }\n      if (type === \"boolean\" && !value) {\n        return false;\n      } else if (type === \"number\") {\n        if (typeof value === \"number\") {\n          return value.toString();\n        } else if (!value) {\n          return \"0\";\n        } else {\n          return value;\n        }\n      } else {\n        return value;\n      }\n    };\n\n    ConfigStorage.prototype.createSections = function() {\n      var section;\n      section = this.createSection(\"Web Interface\");\n      section.items.push({\n        key: \"open_browser\",\n        title: \"Open web browser on ZeroNet startup\",\n        type: \"checkbox\"\n      });\n      section = this.createSection(\"Network\");\n      section.items.push({\n        key: \"offline\",\n        title: \"Offline mode\",\n        type: \"checkbox\",\n        description: \"Disable network communication.\"\n      });\n      section.items.push({\n        key: \"fileserver_ip_type\",\n        title: \"File server network\",\n        type: \"select\",\n        options: [\n          {\n            title: \"IPv4\",\n            value: \"ipv4\"\n          }, {\n            title: \"IPv6\",\n            value: \"ipv6\"\n          }, {\n            title: \"Dual (IPv4 & IPv6)\",\n            value: \"dual\"\n          }\n        ],\n        description: \"Accept incoming peers using IPv4 or IPv6 address. (default: dual)\"\n      });\n      section.items.push({\n        key: \"fileserver_port\",\n        title: \"File server port\",\n        type: \"text\",\n        valid_pattern: /[0-9]*/,\n        description: \"Other peers will use this port to reach your served sites. (default: randomize)\"\n      });\n      section.items.push({\n        key: \"ip_external\",\n        title: \"File server external ip\",\n        type: \"textarea\",\n        placeholder: \"Detect automatically\",\n        description: \"Your file server is accessible on these ips. (default: detect automatically)\"\n      });\n      section.items.push({\n        title: \"Tor\",\n        key: \"tor\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Disable\",\n            value: \"disable\"\n          }, {\n            title: \"Enable\",\n            value: \"enable\"\n          }, {\n            title: \"Always\",\n            value: \"always\"\n          }\n        ],\n        description: [\"Disable: Don't connect to peers on Tor network\", h(\"br\"), \"Enable: Only use Tor for Tor network peers\", h(\"br\"), \"Always: Use Tor for every connections to hide your IP address (slower)\"]\n      });\n      section.items.push({\n        title: \"Use Tor bridges\",\n        key: \"tor_use_bridges\",\n        type: \"checkbox\",\n        description: \"Use obfuscated bridge relays to avoid network level Tor block (even slower)\",\n        isHidden: function() {\n          return !Page.server_info.tor_has_meek_bridges;\n        }\n      });\n      section.items.push({\n        title: \"Trackers\",\n        key: \"trackers\",\n        type: \"textarea\",\n        description: \"Discover new peers using these adresses\"\n      });\n      section.items.push({\n        title: \"Trackers files\",\n        key: \"trackers_file\",\n        type: \"textarea\",\n        description: \"Load additional list of torrent trackers dynamically, from a file\",\n        placeholder: \"Eg.: {data_dir}/trackers.json\",\n        value_pos: \"fullwidth\"\n      });\n      section.items.push({\n        title: \"Proxy for tracker connections\",\n        key: \"trackers_proxy\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Custom\",\n            value: \"\"\n          }, {\n            title: \"Tor\",\n            value: \"tor\"\n          }, {\n            title: \"Disable\",\n            value: \"disable\"\n          }\n        ],\n        isHidden: function() {\n          return Page.values[\"tor\"] === \"always\";\n        }\n      });\n      section.items.push({\n        title: \"Custom socks proxy address for trackers\",\n        key: \"trackers_proxy\",\n        type: \"text\",\n        placeholder: \"Eg.: 127.0.0.1:1080\",\n        value_pos: \"fullwidth\",\n        valid_pattern: /.+:[0-9]+/,\n        isHidden: (function(_this) {\n          return function() {\n            var ref;\n            return (ref = Page.values[\"trackers_proxy\"]) === \"tor\" || ref === \"disable\";\n          };\n        })(this)\n      });\n      section = this.createSection(\"Performance\");\n      section.items.push({\n        key: \"log_level\",\n        title: \"Level of logging to file\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Everything\",\n            value: \"DEBUG\"\n          }, {\n            title: \"Only important messages\",\n            value: \"INFO\"\n          }, {\n            title: \"Only errors\",\n            value: \"ERROR\"\n          }\n        ]\n      });\n      section.items.push({\n        key: \"threads_fs_read\",\n        title: \"Threads for async file system reads\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Sync read\",\n            value: 0\n          }, {\n            title: \"1 thread\",\n            value: 1\n          }, {\n            title: \"2 threads\",\n            value: 2\n          }, {\n            title: \"3 threads\",\n            value: 3\n          }, {\n            title: \"4 threads\",\n            value: 4\n          }, {\n            title: \"5 threads\",\n            value: 5\n          }, {\n            title: \"10 threads\",\n            value: 10\n          }\n        ]\n      });\n      section.items.push({\n        key: \"threads_fs_write\",\n        title: \"Threads for async file system writes\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Sync write\",\n            value: 0\n          }, {\n            title: \"1 thread\",\n            value: 1\n          }, {\n            title: \"2 threads\",\n            value: 2\n          }, {\n            title: \"3 threads\",\n            value: 3\n          }, {\n            title: \"4 threads\",\n            value: 4\n          }, {\n            title: \"5 threads\",\n            value: 5\n          }, {\n            title: \"10 threads\",\n            value: 10\n          }\n        ]\n      });\n      section.items.push({\n        key: \"threads_crypt\",\n        title: \"Threads for cryptographic functions\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Sync execution\",\n            value: 0\n          }, {\n            title: \"1 thread\",\n            value: 1\n          }, {\n            title: \"2 threads\",\n            value: 2\n          }, {\n            title: \"3 threads\",\n            value: 3\n          }, {\n            title: \"4 threads\",\n            value: 4\n          }, {\n            title: \"5 threads\",\n            value: 5\n          }, {\n            title: \"10 threads\",\n            value: 10\n          }\n        ]\n      });\n      return section.items.push({\n        key: \"threads_db\",\n        title: \"Threads for database operations\",\n        type: \"select\",\n        options: [\n          {\n            title: \"Sync execution\",\n            value: 0\n          }, {\n            title: \"1 thread\",\n            value: 1\n          }, {\n            title: \"2 threads\",\n            value: 2\n          }, {\n            title: \"3 threads\",\n            value: 3\n          }, {\n            title: \"4 threads\",\n            value: 4\n          }, {\n            title: \"5 threads\",\n            value: 5\n          }, {\n            title: \"10 threads\",\n            value: 10\n          }\n        ]\n      });\n    };\n\n    ConfigStorage.prototype.createSection = function(title) {\n      var section;\n      section = {};\n      section.title = title;\n      section.items = [];\n      this.items.push(section);\n      return section;\n    };\n\n    return ConfigStorage;\n\n  })(Class);\n\n  window.ConfigStorage = ConfigStorage;\n\n}).call(this);\n\n\n/* ---- ConfigView.coffee ---- */\n\n\n(function() {\n  var ConfigView,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  ConfigView = (function(superClass) {\n    extend(ConfigView, superClass);\n\n    function ConfigView() {\n      this.renderValueSelect = bind(this.renderValueSelect, this);\n      this.renderValueCheckbox = bind(this.renderValueCheckbox, this);\n      this.renderValueTextarea = bind(this.renderValueTextarea, this);\n      this.autosizeTextarea = bind(this.autosizeTextarea, this);\n      this.renderValueText = bind(this.renderValueText, this);\n      this.handleCheckboxChange = bind(this.handleCheckboxChange, this);\n      this.handleInputChange = bind(this.handleInputChange, this);\n      this.renderSectionItem = bind(this.renderSectionItem, this);\n      this.handleResetClick = bind(this.handleResetClick, this);\n      this.renderSection = bind(this.renderSection, this);\n      this;\n    }\n\n    ConfigView.prototype.render = function() {\n      return this.config_storage.items.map(this.renderSection);\n    };\n\n    ConfigView.prototype.renderSection = function(section) {\n      return h(\"div.section\", {\n        key: section.title\n      }, [h(\"h2\", section.title), h(\"div.config-items\", section.items.map(this.renderSectionItem))]);\n    };\n\n    ConfigView.prototype.handleResetClick = function(e) {\n      var config_key, default_value, node, ref;\n      node = e.currentTarget;\n      config_key = node.attributes.config_key.value;\n      default_value = (ref = node.attributes.default_value) != null ? ref.value : void 0;\n      return Page.cmd(\"wrapperConfirm\", [\"Reset \" + config_key + \" value?\", \"Reset to default\"], (function(_this) {\n        return function(res) {\n          if (res) {\n            _this.values[config_key] = default_value;\n          }\n          return Page.projector.scheduleRender();\n        };\n      })(this));\n    };\n\n    ConfigView.prototype.renderSectionItem = function(item) {\n      var marker_title, ref, value_changed, value_default, value_pos;\n      value_pos = item.value_pos;\n      if (item.type === \"textarea\") {\n        if (value_pos == null) {\n          value_pos = \"fullwidth\";\n        }\n      } else {\n        if (value_pos == null) {\n          value_pos = \"right\";\n        }\n      }\n      value_changed = this.config_storage.formatValue(this.values[item.key]) !== item.value;\n      value_default = this.config_storage.formatValue(this.values[item.key]) === item[\"default\"];\n      if ((ref = item.key) === \"open_browser\" || ref === \"fileserver_port\") {\n        value_default = true;\n      }\n      marker_title = \"Changed from default value: \" + item[\"default\"] + \" -> \" + this.values[item.key];\n      if (item.pending) {\n        marker_title += \" (change pending until client restart)\";\n      }\n      if (typeof item.isHidden === \"function\" ? item.isHidden() : void 0) {\n        return null;\n      }\n      return h(\"div.config-item\", {\n        key: item.title,\n        enterAnimation: Animation.slideDown,\n        exitAnimation: Animation.slideUpInout\n      }, [\n        h(\"div.title\", [h(\"h3\", item.title), h(\"div.description\", item.description)]), h(\"div.value.value-\" + value_pos, item.type === \"select\" ? this.renderValueSelect(item) : item.type === \"checkbox\" ? this.renderValueCheckbox(item) : item.type === \"textarea\" ? this.renderValueTextarea(item) : this.renderValueText(item), h(\"a.marker\", {\n          href: \"#Reset\",\n          title: marker_title,\n          onclick: this.handleResetClick,\n          config_key: item.key,\n          default_value: item[\"default\"],\n          classes: {\n            \"default\": value_default,\n            changed: value_changed,\n            visible: !value_default || value_changed || item.pending,\n            pending: item.pending\n          }\n        }, \"\\u2022\"))\n      ]);\n    };\n\n    ConfigView.prototype.handleInputChange = function(e) {\n      var config_key, node;\n      node = e.target;\n      config_key = node.attributes.config_key.value;\n      this.values[config_key] = node.value;\n      return Page.projector.scheduleRender();\n    };\n\n    ConfigView.prototype.handleCheckboxChange = function(e) {\n      var config_key, node, value;\n      node = e.currentTarget;\n      config_key = node.attributes.config_key.value;\n      value = !node.classList.contains(\"checked\");\n      this.values[config_key] = value;\n      return Page.projector.scheduleRender();\n    };\n\n    ConfigView.prototype.renderValueText = function(item) {\n      var value;\n      value = this.values[item.key];\n      if (!value) {\n        value = \"\";\n      }\n      return h(\"input.input-\" + item.type, {\n        type: item.type,\n        config_key: item.key,\n        value: value,\n        placeholder: item.placeholder,\n        oninput: this.handleInputChange\n      });\n    };\n\n    ConfigView.prototype.autosizeTextarea = function(e) {\n      var h, height_before, node, scrollh;\n      if (e.currentTarget) {\n        node = e.currentTarget;\n      } else {\n        node = e;\n      }\n      height_before = node.style.height;\n      if (height_before) {\n        node.style.height = \"0px\";\n      }\n      h = node.offsetHeight;\n      scrollh = node.scrollHeight + 20;\n      if (scrollh > h) {\n        return node.style.height = scrollh + \"px\";\n      } else {\n        return node.style.height = height_before;\n      }\n    };\n\n    ConfigView.prototype.renderValueTextarea = function(item) {\n      var value;\n      value = this.values[item.key];\n      if (!value) {\n        value = \"\";\n      }\n      return h(\"textarea.input-\" + item.type + \".input-text\", {\n        type: item.type,\n        config_key: item.key,\n        oninput: this.handleInputChange,\n        afterCreate: this.autosizeTextarea,\n        updateAnimation: this.autosizeTextarea,\n        value: value,\n        placeholder: item.placeholder\n      });\n    };\n\n    ConfigView.prototype.renderValueCheckbox = function(item) {\n      var checked;\n      if (this.values[item.key] && this.values[item.key] !== \"False\") {\n        checked = true;\n      } else {\n        checked = false;\n      }\n      return h(\"div.checkbox\", {\n        onclick: this.handleCheckboxChange,\n        config_key: item.key,\n        classes: {\n          checked: checked\n        }\n      }, h(\"div.checkbox-skin\"));\n    };\n\n    ConfigView.prototype.renderValueSelect = function(item) {\n      return h(\"select.input-select\", {\n        config_key: item.key,\n        oninput: this.handleInputChange\n      }, item.options.map((function(_this) {\n        return function(option) {\n          return h(\"option\", {\n            selected: option.value.toString() === _this.values[item.key],\n            value: option.value\n          }, option.title);\n        };\n      })(this)));\n    };\n\n    return ConfigView;\n\n  })(Class);\n\n  window.ConfigView = ConfigView;\n\n}).call(this);\n\n/* ---- UiConfig.coffee ---- */\n\n\n(function() {\n  var UiConfig,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  window.h = maquette.h;\n\n  UiConfig = (function(superClass) {\n    extend(UiConfig, superClass);\n\n    function UiConfig() {\n      this.renderBottomRestart = bind(this.renderBottomRestart, this);\n      this.handleRestartClick = bind(this.handleRestartClick, this);\n      this.renderBottomSave = bind(this.renderBottomSave, this);\n      this.handleSaveClick = bind(this.handleSaveClick, this);\n      this.render = bind(this.render, this);\n      this.saveValue = bind(this.saveValue, this);\n      this.saveValues = bind(this.saveValues, this);\n      this.getValuesPending = bind(this.getValuesPending, this);\n      this.getValuesChanged = bind(this.getValuesChanged, this);\n      this.createProjector = bind(this.createProjector, this);\n      this.updateConfig = bind(this.updateConfig, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      return UiConfig.__super__.constructor.apply(this, arguments);\n    }\n\n    UiConfig.prototype.init = function() {\n      this.save_visible = true;\n      this.config = null;\n      this.values = null;\n      this.config_view = new ConfigView();\n      return window.onbeforeunload = (function(_this) {\n        return function() {\n          if (_this.getValuesChanged().length > 0) {\n            return true;\n          } else {\n            return null;\n          }\n        };\n      })(this);\n    };\n\n    UiConfig.prototype.onOpenWebsocket = function() {\n      this.cmd(\"wrapperSetTitle\", \"Config - ZeroNet\");\n      this.cmd(\"serverInfo\", {}, (function(_this) {\n        return function(server_info) {\n          return _this.server_info = server_info;\n        };\n      })(this));\n      this.restart_loading = false;\n      return this.updateConfig();\n    };\n\n    UiConfig.prototype.updateConfig = function(cb) {\n      return this.cmd(\"configList\", [], (function(_this) {\n        return function(res) {\n          var item, key, value;\n          _this.config = res;\n          _this.values = {};\n          _this.config_storage = new ConfigStorage(_this.config);\n          _this.config_view.values = _this.values;\n          _this.config_view.config_storage = _this.config_storage;\n          for (key in res) {\n            item = res[key];\n            value = item.value;\n            _this.values[key] = _this.config_storage.formatValue(value);\n          }\n          _this.projector.scheduleRender();\n          return typeof cb === \"function\" ? cb() : void 0;\n        };\n      })(this));\n    };\n\n    UiConfig.prototype.createProjector = function() {\n      this.projector = maquette.createProjector();\n      this.projector.replace($(\"#content\"), this.render);\n      this.projector.replace($(\"#bottom-save\"), this.renderBottomSave);\n      return this.projector.replace($(\"#bottom-restart\"), this.renderBottomRestart);\n    };\n\n    UiConfig.prototype.getValuesChanged = function() {\n      var key, ref, ref1, value, values_changed;\n      values_changed = [];\n      ref = this.values;\n      for (key in ref) {\n        value = ref[key];\n        if (this.config_storage.formatValue(value) !== this.config_storage.formatValue((ref1 = this.config[key]) != null ? ref1.value : void 0)) {\n          values_changed.push({\n            key: key,\n            value: value\n          });\n        }\n      }\n      return values_changed;\n    };\n\n    UiConfig.prototype.getValuesPending = function() {\n      var item, key, ref, values_pending;\n      values_pending = [];\n      ref = this.config;\n      for (key in ref) {\n        item = ref[key];\n        if (item.pending) {\n          values_pending.push(key);\n        }\n      }\n      return values_pending;\n    };\n\n    UiConfig.prototype.saveValues = function(cb) {\n      var base, changed_values, default_value, i, item, j, last, len, match, message, results, value, value_same_as_default;\n      changed_values = this.getValuesChanged();\n      results = [];\n      for (i = j = 0, len = changed_values.length; j < len; i = ++j) {\n        item = changed_values[i];\n        last = i === changed_values.length - 1;\n        value = this.config_storage.deformatValue(item.value, typeof this.config[item.key][\"default\"]);\n        default_value = this.config_storage.deformatValue(this.config[item.key][\"default\"], typeof this.config[item.key][\"default\"]);\n        value_same_as_default = JSON.stringify(default_value) === JSON.stringify(value);\n        if (this.config[item.key].item.valid_pattern && !(typeof (base = this.config[item.key].item).isHidden === \"function\" ? base.isHidden() : void 0)) {\n          match = value.match(this.config[item.key].item.valid_pattern);\n          if (!match || match[0] !== value) {\n            message = \"Invalid value of \" + this.config[item.key].item.title + \": \" + value + \" (does not matches \" + this.config[item.key].item.valid_pattern + \")\";\n            Page.cmd(\"wrapperNotification\", [\"error\", message]);\n            cb(false);\n            break;\n          }\n        }\n        if (value_same_as_default) {\n          value = null;\n        }\n        results.push(this.saveValue(item.key, value, last ? cb : null));\n      }\n      return results;\n    };\n\n    UiConfig.prototype.saveValue = function(key, value, cb) {\n      if (key === \"open_browser\") {\n        if (value) {\n          value = \"default_browser\";\n        } else {\n          value = \"False\";\n        }\n      }\n      return Page.cmd(\"configSet\", [key, value], (function(_this) {\n        return function(res) {\n          if (res !== \"ok\") {\n            Page.cmd(\"wrapperNotification\", [\"error\", res.error]);\n          }\n          return typeof cb === \"function\" ? cb(true) : void 0;\n        };\n      })(this));\n    };\n\n    UiConfig.prototype.render = function() {\n      if (!this.config) {\n        return h(\"div.content\");\n      }\n      return h(\"div.content\", [this.config_view.render()]);\n    };\n\n    UiConfig.prototype.handleSaveClick = function() {\n      this.save_loading = true;\n      this.logStart(\"Save\");\n      this.saveValues((function(_this) {\n        return function(success) {\n          _this.save_loading = false;\n          _this.logEnd(\"Save\");\n          if (success) {\n            _this.updateConfig();\n          }\n          return Page.projector.scheduleRender();\n        };\n      })(this));\n      return false;\n    };\n\n    UiConfig.prototype.renderBottomSave = function() {\n      var values_changed;\n      values_changed = this.getValuesChanged();\n      return h(\"div.bottom.bottom-save\", {\n        classes: {\n          visible: values_changed.length\n        }\n      }, h(\"div.bottom-content\", [\n        h(\"div.title\", values_changed.length + \" configuration item value changed\"), h(\"a.button.button-submit.button-save\", {\n          href: \"#Save\",\n          classes: {\n            loading: this.save_loading\n          },\n          onclick: this.handleSaveClick\n        }, \"Save settings\")\n      ]));\n    };\n\n    UiConfig.prototype.handleRestartClick = function() {\n      this.restart_loading = true;\n      Page.cmd(\"serverShutdown\", {\n        restart: true\n      });\n      Page.projector.scheduleRender();\n      return false;\n    };\n\n    UiConfig.prototype.renderBottomRestart = function() {\n      var values_changed, values_pending;\n      values_pending = this.getValuesPending();\n      values_changed = this.getValuesChanged();\n      return h(\"div.bottom.bottom-restart\", {\n        classes: {\n          visible: values_pending.length && !values_changed.length\n        }\n      }, h(\"div.bottom-content\", [\n        h(\"div.title\", \"Some changed settings requires restart\"), h(\"a.button.button-submit.button-restart\", {\n          href: \"#Restart\",\n          classes: {\n            loading: this.restart_loading\n          },\n          onclick: this.handleRestartClick\n        }, \"Restart ZeroNet client\")\n      ]));\n    };\n\n    return UiConfig;\n\n  })(ZeroFrame);\n\n  window.Page = new UiConfig();\n\n  window.Page.createProjector();\n\n}).call(this);\n"
  },
  {
    "path": "plugins/UiConfig/media/js/lib/Class.coffee",
    "content": "class Class\n\ttrace: true\n\n\tlog: (args...) ->\n\t\treturn unless @trace\n\t\treturn if typeof console is 'undefined'\n\t\targs.unshift(\"[#{@.constructor.name}]\")\n\t\tconsole.log(args...)\n\t\t@\n\t\t\n\tlogStart: (name, args...) ->\n\t\treturn unless @trace\n\t\t@logtimers or= {}\n\t\t@logtimers[name] = +(new Date)\n\t\t@log \"#{name}\", args..., \"(started)\" if args.length > 0\n\t\t@\n\t\t\n\tlogEnd: (name, args...) ->\n\t\tms = +(new Date)-@logtimers[name]\n\t\t@log \"#{name}\", args..., \"(Done in #{ms}ms)\"\n\t\t@ \n\nwindow.Class = Class"
  },
  {
    "path": "plugins/UiConfig/media/js/lib/Promise.coffee",
    "content": "# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html\n\nclass Promise\n\t@when: (tasks...) ->\n\t\tnum_uncompleted = tasks.length\n\t\targs = new Array(num_uncompleted)\n\t\tpromise = new Promise()\n\n\t\tfor task, task_id in tasks\n\t\t\t((task_id) ->\n\t\t\t\ttask.then(() ->\n\t\t\t\t\targs[task_id] = Array.prototype.slice.call(arguments)\n\t\t\t\t\tnum_uncompleted--\n\t\t\t\t\tpromise.complete.apply(promise, args) if num_uncompleted == 0\n\t\t\t\t)\n\t\t\t)(task_id)\n\n\t\treturn promise\n\n\tconstructor: ->\n\t\t@resolved = false\n\t\t@end_promise = null\n\t\t@result = null\n\t\t@callbacks = []\n\n\tresolve: ->\n\t\tif @resolved\n\t\t\treturn false\n\t\t@resolved = true\n\t\t@data = arguments\n\t\tif not arguments.length\n\t\t\t@data = [true]\n\t\t@result = @data[0]\n\t\tfor callback in @callbacks\n\t\t\tback = callback.apply callback, @data\n\t\tif @end_promise\n\t\t\t@end_promise.resolve(back)\n\n\tfail: ->\n\t\t@resolve(false)\n\n\tthen: (callback) ->\n\t\tif @resolved == true\n\t\t\tcallback.apply callback, @data\n\t\t\treturn\n\n\t\t@callbacks.push callback\n\n\t\t@end_promise = new Promise()\n\nwindow.Promise = Promise\n\n###\ns = Date.now()\nlog = (text) ->\n\tconsole.log Date.now()-s, Array.prototype.slice.call(arguments).join(\", \")\n\nlog \"Started\"\n\ncmd = (query) ->\n\tp = new Promise()\n\tsetTimeout ( ->\n\t\tp.resolve query+\" Result\"\n\t), 100\n\treturn p\n\nback = cmd(\"SELECT * FROM message\").then (res) ->\n\tlog res\n\treturn \"Return from query\"\n.then (res) ->\n\tlog \"Back then\", res\n\nlog \"Query started\", back\n###"
  },
  {
    "path": "plugins/UiConfig/media/js/lib/Prototypes.coffee",
    "content": "String::startsWith = (s) -> @[...s.length] is s\nString::endsWith = (s) -> s is '' or @[-s.length..] is s\nString::repeat = (count) -> new Array( count + 1 ).join(@)\n\nwindow.isEmpty = (obj) ->\n\tfor key of obj\n\t\treturn false\n\treturn true\n"
  },
  {
    "path": "plugins/UiConfig/media/js/lib/maquette.js",
    "content": "(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['exports'], factory);\n    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {\n        // CommonJS\n        factory(exports);\n    } else {\n        // Browser globals\n        factory(root.maquette = {});\n    }\n}(this, function (exports) {\n    'use strict';\n    ;\n    ;\n    ;\n    ;\n    var NAMESPACE_W3 = 'http://www.w3.org/';\n    var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';\n    var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';\n    // Utilities\n    var emptyArray = [];\n    var extend = function (base, overrides) {\n        var result = {};\n        Object.keys(base).forEach(function (key) {\n            result[key] = base[key];\n        });\n        if (overrides) {\n            Object.keys(overrides).forEach(function (key) {\n                result[key] = overrides[key];\n            });\n        }\n        return result;\n    };\n    // Hyperscript helper functions\n    var same = function (vnode1, vnode2) {\n        if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {\n            return false;\n        }\n        if (vnode1.properties && vnode2.properties) {\n            if (vnode1.properties.key !== vnode2.properties.key) {\n                return false;\n            }\n            return vnode1.properties.bind === vnode2.properties.bind;\n        }\n        return !vnode1.properties && !vnode2.properties;\n    };\n    var toTextVNode = function (data) {\n        return {\n            vnodeSelector: '',\n            properties: undefined,\n            children: undefined,\n            text: data.toString(),\n            domNode: null\n        };\n    };\n    var appendChildren = function (parentSelector, insertions, main) {\n        for (var i = 0; i < insertions.length; i++) {\n            var item = insertions[i];\n            if (Array.isArray(item)) {\n                appendChildren(parentSelector, item, main);\n            } else {\n                if (item !== null && item !== undefined) {\n                    if (!item.hasOwnProperty('vnodeSelector')) {\n                        item = toTextVNode(item);\n                    }\n                    main.push(item);\n                }\n            }\n        }\n    };\n    // Render helper functions\n    var missingTransition = function () {\n        throw new Error('Provide a transitions object to the projectionOptions to do animations');\n    };\n    var DEFAULT_PROJECTION_OPTIONS = {\n        namespace: undefined,\n        eventHandlerInterceptor: undefined,\n        styleApplyer: function (domNode, styleName, value) {\n            // Provides a hook to add vendor prefixes for browsers that still need it.\n            domNode.style[styleName] = value;\n        },\n        transitions: {\n            enter: missingTransition,\n            exit: missingTransition\n        }\n    };\n    var applyDefaultProjectionOptions = function (projectorOptions) {\n        return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);\n    };\n    var checkStyleValue = function (styleValue) {\n        if (typeof styleValue !== 'string') {\n            throw new Error('Style values must be strings');\n        }\n    };\n    var setProperties = function (domNode, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            /* tslint:disable:no-var-keyword: edge case */\n            var propValue = properties[propName];\n            /* tslint:enable:no-var-keyword */\n            if (propName === 'className') {\n                throw new Error('Property \"className\" is not supported, use \"class\".');\n            } else if (propName === 'class') {\n                if (domNode.className) {\n                    // May happen if classes is specified before class\n                    domNode.className += ' ' + propValue;\n                } else {\n                    domNode.className = propValue;\n                }\n            } else if (propName === 'classes') {\n                // object with string keys and boolean values\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    if (propValue[className]) {\n                        domNode.classList.add(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                // object with string keys and string (!) values\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var styleValue = propValue[styleName];\n                    if (styleValue) {\n                        checkStyleValue(styleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, styleValue);\n                    }\n                }\n            } else if (propName === 'key') {\n                continue;\n            } else if (propValue === null || propValue === undefined) {\n                continue;\n            } else {\n                var type = typeof propValue;\n                if (type === 'function') {\n                    if (propName.lastIndexOf('on', 0) === 0) {\n                        if (eventHandlerInterceptor) {\n                            propValue = eventHandlerInterceptor(propName, propValue, domNode, properties);    // intercept eventhandlers\n                        }\n                        if (propName === 'oninput') {\n                            (function () {\n                                // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput\n                                var oldPropValue = propValue;\n                                propValue = function (evt) {\n                                    evt.target['oninput-value'] = evt.target.value;\n                                    // may be HTMLTextAreaElement as well\n                                    oldPropValue.apply(this, [evt]);\n                                };\n                            }());\n                        }\n                        domNode[propName] = propValue;\n                    }\n                } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {\n                    if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                        domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                    } else {\n                        domNode.setAttribute(propName, propValue);\n                    }\n                } else {\n                    domNode[propName] = propValue;\n                }\n            }\n        }\n    };\n    var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var propertiesUpdated = false;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            // assuming that properties will be nullified instead of missing is by design\n            var propValue = properties[propName];\n            var previousValue = previousProperties[propName];\n            if (propName === 'class') {\n                if (previousValue !== propValue) {\n                    throw new Error('\"class\" property may not be updated. Use the \"classes\" property for conditional css classes.');\n                }\n            } else if (propName === 'classes') {\n                var classList = domNode.classList;\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    var on = !!propValue[className];\n                    var previousOn = !!previousValue[className];\n                    if (on === previousOn) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (on) {\n                        classList.add(className);\n                    } else {\n                        classList.remove(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var newStyleValue = propValue[styleName];\n                    var oldStyleValue = previousValue[styleName];\n                    if (newStyleValue === oldStyleValue) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (newStyleValue) {\n                        checkStyleValue(newStyleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, newStyleValue);\n                    } else {\n                        projectionOptions.styleApplyer(domNode, styleName, '');\n                    }\n                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };\n    var findIndexOfChild = function (children, sameAs, start) {\n        if (sameAs.vnodeSelector !== '') {\n            // Never scan for text-nodes\n            for (var i = start; i < children.length; i++) {\n                if (same(children[i], sameAs)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    var nodeAdded = function (vNode, transitions) {\n        if (vNode.properties) {\n            var enterAnimation = vNode.properties.enterAnimation;\n            if (enterAnimation) {\n                if (typeof enterAnimation === 'function') {\n                    enterAnimation(vNode.domNode, vNode.properties);\n                } else {\n                    transitions.enter(vNode.domNode, vNode.properties, enterAnimation);\n                }\n            }\n        }\n    };\n    var nodeToRemove = function (vNode, transitions) {\n        var domNode = vNode.domNode;\n        if (vNode.properties) {\n            var exitAnimation = vNode.properties.exitAnimation;\n            if (exitAnimation) {\n                domNode.style.pointerEvents = 'none';\n                var removeDomNode = function () {\n                    if (domNode.parentNode) {\n                        domNode.parentNode.removeChild(domNode);\n                    }\n                };\n                if (typeof exitAnimation === 'function') {\n                    exitAnimation(domNode, removeDomNode, vNode.properties);\n                    return;\n                } else {\n                    transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);\n                    return;\n                }\n            }\n        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;\n        var transitions = projectionOptions.transitions;\n        var oldIndex = 0;\n        var newIndex = 0;\n        var i;\n        var textUpdated = false;\n        while (newIndex < newChildrenLength) {\n            var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;\n            var newChild = newChildren[newIndex];\n            if (oldChild !== undefined && same(oldChild, newChild)) {\n                textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;\n                oldIndex++;\n            } else {\n                var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);\n                if (findOldIndex >= 0) {\n                    // Remove preceding missing children\n                    for (i = oldIndex; i < findOldIndex; i++) {\n                        nodeToRemove(oldChildren[i], transitions);\n                        checkDistinguishable(oldChildren, i, vnode, 'removed');\n                    }\n                    textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;\n                    oldIndex = findOldIndex + 1;\n                } else {\n                    // New child\n                    createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);\n                    nodeAdded(newChild, transitions);\n                    checkDistinguishable(newChildren, newIndex, vnode, 'added');\n                }\n            }\n            newIndex++;\n        }\n        if (oldChildrenLength > oldIndex) {\n            // Remove child fragments\n            for (i = oldIndex; i < oldChildrenLength; i++) {\n                nodeToRemove(oldChildren[i], transitions);\n                checkDistinguishable(oldChildren, i, vnode, 'removed');\n            }\n        }\n        return textUpdated;\n    };\n    var addChildren = function (domNode, children, projectionOptions) {\n        if (!children) {\n            return;\n        }\n        for (var i = 0; i < children.length; i++) {\n            createDom(children[i], domNode, undefined, projectionOptions);\n        }\n    };\n    var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {\n        addChildren(domNode, vnode.children, projectionOptions);\n        // children before properties, needed for value property of <select>.\n        if (vnode.text) {\n            domNode.textContent = vnode.text;\n        }\n        setProperties(domNode, vnode.properties, projectionOptions);\n        if (vnode.properties && vnode.properties.afterCreate) {\n            vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n        }\n    };\n    createDom = function (vnode, parentNode, insertBefore, projectionOptions) {\n        var domNode, i, c, start = 0, type, found;\n        var vnodeSelector = vnode.vnodeSelector;\n        if (vnodeSelector === '') {\n            domNode = vnode.domNode = document.createTextNode(vnode.text);\n            if (insertBefore !== undefined) {\n                parentNode.insertBefore(domNode, insertBefore);\n            } else {\n                parentNode.appendChild(domNode);\n            }\n        } else {\n            for (i = 0; i <= vnodeSelector.length; ++i) {\n                c = vnodeSelector.charAt(i);\n                if (i === vnodeSelector.length || c === '.' || c === '#') {\n                    type = vnodeSelector.charAt(start - 1);\n                    found = vnodeSelector.slice(start, i);\n                    if (type === '.') {\n                        domNode.classList.add(found);\n                    } else if (type === '#') {\n                        domNode.id = found;\n                    } else {\n                        if (found === 'svg') {\n                            projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n                        }\n                        if (projectionOptions.namespace !== undefined) {\n                            domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);\n                        } else {\n                            domNode = vnode.domNode = document.createElement(found);\n                        }\n                        if (insertBefore !== undefined) {\n                            parentNode.insertBefore(domNode, insertBefore);\n                        } else {\n                            parentNode.appendChild(domNode);\n                        }\n                    }\n                    start = i + 1;\n                }\n            }\n            initPropertiesAndChildren(domNode, vnode, projectionOptions);\n        }\n    };\n    updateDom = function (previous, vnode, projectionOptions) {\n        var domNode = previous.domNode;\n        var textUpdated = false;\n        if (previous === vnode) {\n            return false;    // By contract, VNode objects may not be modified anymore after passing them to maquette\n        }\n        var updated = false;\n        if (vnode.vnodeSelector === '') {\n            if (vnode.text !== previous.text) {\n                var newVNode = document.createTextNode(vnode.text);\n                domNode.parentNode.replaceChild(newVNode, domNode);\n                vnode.domNode = newVNode;\n                textUpdated = true;\n                return textUpdated;\n            }\n        } else {\n            if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {\n                projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n            }\n            if (previous.text !== vnode.text) {\n                updated = true;\n                if (vnode.text === undefined) {\n                    domNode.removeChild(domNode.firstChild);    // the only textnode presumably\n                } else {\n                    domNode.textContent = vnode.text;\n                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {\n            // Optional properties argument was omitted\n            properties = undefined;\n        }\n        var text = undefined;\n        var children = undefined;\n        var argsLength = arguments.length;\n        // Recognize a common special case where there is only a single text node\n        if (argsLength === childIndex + 1) {\n            var onlyChild = arguments[childIndex];\n            if (typeof onlyChild === 'string') {\n                text = onlyChild;\n            } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {\n                text = onlyChild[0];\n            }\n        }\n        if (text === undefined) {\n            children = [];\n            for (; childIndex < arguments.length; childIndex++) {\n                var child = arguments[childIndex];\n                if (child === null || child === undefined) {\n                    continue;\n                } else if (Array.isArray(child)) {\n                    appendChildren(selector, child, children);\n                } else if (child.hasOwnProperty('vnodeSelector')) {\n                    children.push(child);\n                } else {\n                    children.push(toTextVNode(child));\n                }\n            }\n        }\n        return {\n            vnodeSelector: selector,\n            properties: properties,\n            children: children,\n            text: text === '' ? undefined : text,\n            domNode: null\n        };\n    };\n    /**\n * Contains simple low-level utility functions to manipulate the real DOM.\n */\n    exports.dom = {\n        /**\n     * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in\n     * its [[Projection.domNode|domNode]] property.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection.\n     * @returns The [[Projection]] which also contains the DOM Node that was created.\n     */\n        create: function (vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, document.createElement('div'), undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Appends a new childnode to the DOM which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param parentNode - The parent node for the new childNode.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the [[Projection]].\n     * @returns The [[Projection]] that was created.\n     */\n        append: function (parentNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, parentNode, undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Inserts a new DOM node which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param beforeNode - The node that the DOM Node is inserted before.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.\n     * NOTE: [[VNode]] objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        insertBefore: function (beforeNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.\n     * This means that the virtual DOM and the real DOM will have one overlapping element.\n     * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects\n     * may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        merge: function (element, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            vnode.domNode = element;\n            initPropertiesAndChildren(element, vnode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        }\n    };\n    /**\n * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.\n * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.\n * For more information, see [[CalculationCache]].\n *\n * @param <Result> The type of the value that is cached.\n */\n    exports.createCache = function () {\n        var cachedInputs = undefined;\n        var cachedOutcome = undefined;\n        var result = {\n            invalidate: function () {\n                cachedOutcome = undefined;\n                cachedInputs = undefined;\n            },\n            result: function (inputs, calculation) {\n                if (cachedInputs) {\n                    for (var i = 0; i < inputs.length; i++) {\n                        if (cachedInputs[i] !== inputs[i]) {\n                            cachedOutcome = undefined;\n                        }\n                    }\n                }\n                if (!cachedOutcome) {\n                    cachedOutcome = calculation();\n                    cachedInputs = inputs;\n                }\n                return cachedOutcome;\n            }\n        };\n        return result;\n    };\n    /**\n * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.\n * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.\n *\n * @param <Source>       The type of source items. A database-record for instance.\n * @param <Target>       The type of target items. A [[Component]] for instance.\n * @param getSourceKey   `function(source)` that must return a key to identify each source object. The result must either be a string or a number.\n * @param createResult   `function(source, index)` that must create a new result object from a given source. This function is identical\n *                       to the `callback` argument in `Array.map(callback)`.\n * @param updateResult   `function(source, target, index)` that updates a result to an updated source.\n */\n    exports.createMapping = function (getSourceKey, createResult, updateResult) {\n        var keys = [];\n        var results = [];\n        return {\n            results: results,\n            map: function (newSources) {\n                var newKeys = newSources.map(getSourceKey);\n                var oldTargets = results.slice();\n                var oldIndex = 0;\n                for (var i = 0; i < newSources.length; i++) {\n                    var source = newSources[i];\n                    var sourceKey = newKeys[i];\n                    if (sourceKey === keys[oldIndex]) {\n                        results[i] = oldTargets[oldIndex];\n                        updateResult(source, oldTargets[oldIndex], i);\n                        oldIndex++;\n                    } else {\n                        var found = false;\n                        for (var j = 1; j < keys.length; j++) {\n                            var searchIndex = (oldIndex + j) % keys.length;\n                            if (keys[searchIndex] === sourceKey) {\n                                results[i] = oldTargets[searchIndex];\n                                updateResult(newSources[i], oldTargets[searchIndex], i);\n                                oldIndex = searchIndex + 1;\n                                found = true;\n                                break;\n                            }\n                        }\n                        if (!found) {\n                            results[i] = createResult(source, i);\n                        }\n                    }\n                }\n                results.length = newSources.length;\n                keys = newKeys;\n            }\n        };\n    };\n    /**\n * Creates a [[Projector]] instance using the provided projectionOptions.\n *\n * For more information, see [[Projector]].\n *\n * @param projectionOptions   Options that influence how the DOM is rendered and updated.\n */\n    exports.createProjector = function (projectorOptions) {\n        var projector;\n        var projectionOptions = applyDefaultProjectionOptions(projectorOptions);\n        projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {\n            return function () {\n                // intercept function calls (event handlers) to do a render afterwards.\n                projector.scheduleRender();\n                return eventHandler.apply(properties.bind || this, arguments);\n            };\n        };\n        var renderCompleted = true;\n        var scheduled;\n        var stopped = false;\n        var projections = [];\n        var renderFunctions = [];\n        // matches the projections array\n        var doRender = function () {\n            scheduled = undefined;\n            if (!renderCompleted) {\n                return;    // The last render threw an error, it should be logged in the browser console.\n            }\n            renderCompleted = false;\n            for (var i = 0; i < projections.length; i++) {\n                var updatedVnode = renderFunctions[i]();\n                projections[i].update(updatedVnode);\n            }\n            renderCompleted = true;\n        };\n        projector = {\n            scheduleRender: function () {\n                if (!scheduled && !stopped) {\n                    scheduled = requestAnimationFrame(doRender);\n                }\n            },\n            stop: function () {\n                if (scheduled) {\n                    cancelAnimationFrame(scheduled);\n                    scheduled = undefined;\n                }\n                stopped = true;\n            },\n            resume: function () {\n                stopped = false;\n                renderCompleted = true;\n                projector.scheduleRender();\n            },\n            append: function (parentNode, renderMaquetteFunction) {\n                projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            insertBefore: function (beforeNode, renderMaquetteFunction) {\n                projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            merge: function (domNode, renderMaquetteFunction) {\n                projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            replace: function (domNode, renderMaquetteFunction) {\n                var vnode = renderMaquetteFunction();\n                createDom(vnode, domNode.parentNode, domNode, projectionOptions);\n                domNode.parentNode.removeChild(domNode);\n                projections.push(createProjection(vnode, projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            detach: function (renderMaquetteFunction) {\n                for (var i = 0; i < renderFunctions.length; i++) {\n                    if (renderFunctions[i] === renderMaquetteFunction) {\n                        renderFunctions.splice(i, 1);\n                        return projections.splice(i, 1)[0];\n                    }\n                }\n                throw new Error('renderMaquetteFunction was not found');\n            }\n        };\n        return projector;\n    };\n}));\n"
  },
  {
    "path": "plugins/UiConfig/media/js/utils/Animation.coffee",
    "content": "class Animation\n\tslideDown: (elem, props) ->\n\t\tif elem.offsetTop > 2000\n\t\t\treturn\n\n\t\th = elem.offsetHeight\n\t\tcstyle = window.getComputedStyle(elem)\n\t\tmargin_top = cstyle.marginTop\n\t\tmargin_bottom = cstyle.marginBottom\n\t\tpadding_top = cstyle.paddingTop\n\t\tpadding_bottom = cstyle.paddingBottom\n\t\ttransition = cstyle.transition\n\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(0.6)\"\n\t\telem.style.opacity = \"0\"\n\t\telem.style.height = \"0px\"\n\t\telem.style.marginTop = \"0px\"\n\t\telem.style.marginBottom = \"0px\"\n\t\telem.style.paddingTop = \"0px\"\n\t\telem.style.paddingBottom = \"0px\"\n\t\telem.style.transition = \"none\"\n\n\t\tsetTimeout (->\n\t\t\telem.className += \" animate-inout\"\n\t\t\telem.style.height = h+\"px\"\n\t\t\telem.style.transform = \"scale(1)\"\n\t\t\telem.style.opacity = \"1\"\n\t\t\telem.style.marginTop = margin_top\n\t\t\telem.style.marginBottom = margin_bottom\n\t\t\telem.style.paddingTop = padding_top\n\t\t\telem.style.paddingBottom = padding_bottom\n\t\t), 1\n\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate-inout\")\n\t\t\telem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null\n\t\t\telem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null\n\t\t\telem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null\n\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\n\n\tslideUp: (elem, remove_func, props) ->\n\t\tif elem.offsetTop > 1000\n\t\t\treturn remove_func()\n\n\t\telem.className += \" animate-back\"\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.height = elem.offsetHeight+\"px\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(1)\"\n\t\telem.style.opacity = \"1\"\n\t\telem.style.pointerEvents = \"none\"\n\t\tsetTimeout (->\n\t\t\telem.style.height = \"0px\"\n\t\t\telem.style.marginTop = \"0px\"\n\t\t\telem.style.marginBottom = \"0px\"\n\t\t\telem.style.paddingTop = \"0px\"\n\t\t\telem.style.paddingBottom = \"0px\"\n\t\t\telem.style.transform = \"scale(0.8)\"\n\t\t\telem.style.borderTopWidth = \"0px\"\n\t\t\telem.style.borderBottomWidth = \"0px\"\n\t\t\telem.style.opacity = \"0\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\" or e.elapsedTime >= 0.6\n\t\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\t\t\t\tremove_func()\n\n\n\tslideUpInout: (elem, remove_func, props) ->\n\t\telem.className += \" animate-inout\"\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.height = elem.offsetHeight+\"px\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(1)\"\n\t\telem.style.opacity = \"1\"\n\t\telem.style.pointerEvents = \"none\"\n\t\tsetTimeout (->\n\t\t\telem.style.height = \"0px\"\n\t\t\telem.style.marginTop = \"0px\"\n\t\t\telem.style.marginBottom = \"0px\"\n\t\t\telem.style.paddingTop = \"0px\"\n\t\t\telem.style.paddingBottom = \"0px\"\n\t\t\telem.style.transform = \"scale(0.8)\"\n\t\t\telem.style.borderTopWidth = \"0px\"\n\t\t\telem.style.borderBottomWidth = \"0px\"\n\t\t\telem.style.opacity = \"0\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\" or e.elapsedTime >= 0.6\n\t\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\t\t\t\tremove_func()\n\n\n\tshowRight: (elem, props) ->\n\t\telem.className += \" animate\"\n\t\telem.style.opacity = 0\n\t\telem.style.transform = \"TranslateX(-20px) Scale(1.01)\"\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 1\n\t\t\telem.style.transform = \"TranslateX(0px) Scale(1)\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate\")\n\t\t\telem.style.transform = elem.style.opacity = null\n\n\n\tshow: (elem, props) ->\n\t\tdelay = arguments[arguments.length-2]?.delay*1000 or 1\n\t\telem.style.opacity = 0\n\t\tsetTimeout (->\n\t\t\telem.className += \" animate\"\n\t\t), 1\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 1\n\t\t), delay\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate\")\n\t\t\telem.style.opacity = null\n\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\n\thide: (elem, remove_func, props) ->\n\t\tdelay = arguments[arguments.length-2]?.delay*1000 or 1\n\t\telem.className += \" animate\"\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 0\n\t\t), delay\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\"\n\t\t\t\tremove_func()\n\n\taddVisibleClass: (elem, props) ->\n\t\tsetTimeout ->\n\t\t\telem.classList.add(\"visible\")\n\nwindow.Animation = new Animation()"
  },
  {
    "path": "plugins/UiConfig/media/js/utils/Dollar.coffee",
    "content": "window.$ = (selector) ->\n\tif selector.startsWith(\"#\")\n\t\treturn document.getElementById(selector.replace(\"#\", \"\"))\n"
  },
  {
    "path": "plugins/UiConfig/media/js/utils/ZeroFrame.coffee",
    "content": "class ZeroFrame extends Class\n\tconstructor: (url) ->\n\t\t@url = url\n\t\t@waiting_cb = {}\n\t\t@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\")\n\t\t@connect()\n\t\t@next_message_id = 1\n\t\t@history_state = {}\n\t\t@init()\n\n\n\tinit: ->\n\t\t@\n\n\n\tconnect: ->\n\t\t@target = window.parent\n\t\twindow.addEventListener(\"message\", @onMessage, false)\n\t\t@cmd(\"innerReady\")\n\n\t\t# Save scrollTop\n\t\twindow.addEventListener \"beforeunload\", (e) =>\n\t\t\t@log \"save scrollTop\", window.pageYOffset\n\t\t\t@history_state[\"scrollTop\"] = window.pageYOffset\n\t\t\t@cmd \"wrapperReplaceState\", [@history_state, null]\n\n\t\t# Restore scrollTop\n\t\t@cmd \"wrapperGetState\", [], (state) =>\n\t\t\t@history_state = state if state?\n\t\t\t@log \"restore scrollTop\", state, window.pageYOffset\n\t\t\tif window.pageYOffset == 0 and state\n\t\t\t\twindow.scroll(window.pageXOffset, state.scrollTop)\n\n\n\tonMessage: (e) =>\n\t\tmessage = e.data\n\t\tcmd = message.cmd\n\t\tif cmd == \"response\"\n\t\t\tif @waiting_cb[message.to]?\n\t\t\t\t@waiting_cb[message.to](message.result)\n\t\t\telse\n\t\t\t\t@log \"Websocket callback not found:\", message\n\t\telse if cmd == \"wrapperReady\" # Wrapper inited later\n\t\t\t@cmd(\"innerReady\")\n\t\telse if cmd == \"ping\"\n\t\t\t@response message.id, \"pong\"\n\t\telse if cmd == \"wrapperOpenedWebsocket\"\n\t\t\t@onOpenWebsocket()\n\t\telse if cmd == \"wrapperClosedWebsocket\"\n\t\t\t@onCloseWebsocket()\n\t\telse\n\t\t\t@onRequest cmd, message.params\n\n\n\tonRequest: (cmd, message) =>\n\t\t@log \"Unknown request\", message\n\n\n\tresponse: (to, result) ->\n\t\t@send {\"cmd\": \"response\", \"to\": to, \"result\": result}\n\n\n\tcmd: (cmd, params={}, cb=null) ->\n\t\t@send {\"cmd\": cmd, \"params\": params}, cb\n\n\n\tsend: (message, cb=null) ->\n\t\tmessage.wrapper_nonce = @wrapper_nonce\n\t\tmessage.id = @next_message_id\n\t\t@next_message_id += 1\n\t\t@target.postMessage(message, \"*\")\n\t\tif cb\n\t\t\t@waiting_cb[message.id] = cb\n\n\n\tonOpenWebsocket: =>\n\t\t@log \"Websocket open\"\n\n\n\tonCloseWebsocket: =>\n\t\t@log \"Websocket close\"\n\n\n\nwindow.ZeroFrame = ZeroFrame\n"
  },
  {
    "path": "plugins/UiConfig/plugin_info.json",
    "content": "{\n\t\"name\": \"UiConfig\",\n\t\"description\": \"Change client settings using the web interface.\",\n\t\"default\": \"enabled\"\n}"
  },
  {
    "path": "plugins/UiFileManager/UiFileManagerPlugin.py",
    "content": "import io\nimport os\nimport re\nimport urllib\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Translate import Translate\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiFileManagerPlugin(object):\n    def actionWrapper(self, path, extra_headers=None):\n        match = re.match(\"/list/(.*?)(/.*|)$\", path)\n        if not match:\n            return super().actionWrapper(path, extra_headers)\n\n        if not extra_headers:\n            extra_headers = {}\n\n        request_address, inner_path = match.groups()\n\n        script_nonce = self.getScriptNonce()\n\n        self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)\n\n        site = self.server.site_manager.need(request_address)\n\n        if not site:\n            return super().actionWrapper(path, extra_headers)\n\n        request_params = urllib.parse.urlencode(\n            {\"address\": site.address, \"site\": request_address, \"inner_path\": inner_path.strip(\"/\")}\n        )\n\n        is_content_loaded = \"content.json\" in site.content_manager.contents\n\n        return iter([super().renderWrapper(\n            site, path, \"uimedia/plugins/uifilemanager/list.html?%s\" % request_params,\n            \"List\", extra_headers, show_loadingscreen=not is_content_loaded, script_nonce=script_nonce\n        )])\n\n    def actionUiMedia(self, path, *args, **kwargs):\n        if path.startswith(\"/uimedia/plugins/uifilemanager/\"):\n            file_path = path.replace(\"/uimedia/plugins/uifilemanager/\", plugin_dir + \"/media/\")\n            if config.debug and (file_path.endswith(\"all.js\") or file_path.endswith(\"all.css\")):\n                # If debugging merge *.css to all.css and *.js to all.js\n                from Debug import DebugMedia\n                DebugMedia.merge(file_path)\n\n            if file_path.endswith(\"js\"):\n                data = _.translateData(open(file_path).read(), mode=\"js\").encode(\"utf8\")\n            elif file_path.endswith(\"html\"):\n                if self.get.get(\"address\"):\n                    site = self.server.site_manager.need(self.get.get(\"address\"))\n                    if \"content.json\" not in site.content_manager.contents:\n                        site.needFile(\"content.json\")\n                data = _.translateData(open(file_path).read(), mode=\"html\").encode(\"utf8\")\n            else:\n                data = open(file_path, \"rb\").read()\n\n            return self.actionFile(file_path, file_obj=io.BytesIO(data), file_size=len(data))\n        else:\n            return super().actionUiMedia(path)\n\n    def error404(self, path=\"\"):\n        if not path.endswith(\"index.html\") and not path.endswith(\"/\"):\n            return super().error404(path)\n\n        path_parts = self.parsePath(path)\n        if not path_parts:\n            return super().error404(path)\n\n        site = self.server.site_manager.get(path_parts[\"request_address\"])\n\n        if not site or not site.content_manager.contents.get(\"content.json\"):\n            return super().error404(path)\n\n        if path_parts[\"inner_path\"] in site.content_manager.contents.get(\"content.json\").get(\"files\", {}):\n            return super().error404(path)\n\n        self.sendHeader(200)\n        path_redirect = \"/list\" + re.sub(\"^/media/\", \"/\", path)\n        self.log.debug(\"Index.html not found: %s, redirecting to: %s\" % (path, path_redirect))\n        return self.formatRedirect(path_redirect)\n"
  },
  {
    "path": "plugins/UiFileManager/__init__.py",
    "content": "from . import UiFileManagerPlugin\n"
  },
  {
    "path": "plugins/UiFileManager/languages/hu.json",
    "content": "{\n    \"New file name:\": \"Új fájl neve:\",\n    \"Delete\": \"Törlés\",\n    \"Cancel\": \"Mégse\",\n    \"Selected:\": \"Köjelölt:\",\n    \"Delete and remove optional:\": \"Törlés és opcionális fájl eltávolítása\",\n    \" files\": \" fájl\",\n    \" (modified)\": \" (módostott)\",\n    \" (new)\": \" (új)\",\n    \" (optional)\": \" (opcionális)\",\n    \" (ignored from content.json)\": \" (content.json-ból kihagyott)\",\n    \"Total: \": \"Összesen: \",\n    \" dir, \": \" könyvtár, \",\n    \" file in \": \" fájl, \",\n    \"+ New\": \"+ Új\",\n    \"Edit\": \"Módosít\",\n    \"View\": \"Megnyit\",\n    \"Save\": \"Mentés\",\n    \"Save: done!\": \"Mentés: Kész!\"\n}"
  },
  {
    "path": "plugins/UiFileManager/languages/jp.json",
    "content": "{\n\t\"New file name:\": \"新しいファイルの名前:\",\n\t\"Delete\": \"削除\",\n\t\"Cancel\": \"キャンセル\",\n\t\"Selected:\": \"選択済み: \",\n\t\"Delete and remove optional:\": \"オプションを削除\",\n\t\" files\": \" ファイル\",\n\t\" (modified)\": \" （編集済み）\",\n\t\" (new)\": \" （新しい）\",\n\t\" (optional)\": \" （オプション）\",\n\t\" (ignored from content.json)\": \" （content.jsonから無視されます）\",\n\t\"Total: \": \"合計: \",\n\t\" dir, \": \" のディレクトリ, \",\n\t\" file in \": \" のファイル, \",\n\t\"+ New\": \"+ 新規作成\",\n\t\"Edit\": \"編集\",\n\t\"View\": \"閲覧\",\n\t\"Save\": \"保存\",\n\t\"Save: done!\": \"保存完了!\"\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/LICENSE",
    "content": "MIT License\n\nCopyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/all.css",
    "content": "\n/* ---- base/codemirror.css ---- */\n\n\n/* BASICS */\n\n.CodeMirror {\n  /* Set height, width, borders, and global font properties here */\n  font-family: monospace;\n  height: 300px;\n  color: black;\n  direction: ltr;\n}\n\n/* PADDING */\n\n.CodeMirror-lines {\n  padding: 4px 0; /* Vertical padding around content */\n}\n.CodeMirror pre.CodeMirror-line,\n.CodeMirror pre.CodeMirror-line-like {\n  padding: 0 4px; /* Horizontal padding of content */\n}\n\n.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  background-color: white; /* The little square between H and V scrollbars */\n}\n\n/* GUTTER */\n\n.CodeMirror-gutters {\n  border-right: 1px solid #ddd;\n  background-color: #f7f7f7;\n  white-space: nowrap;\n}\n.CodeMirror-linenumbers {}\n.CodeMirror-linenumber {\n  padding: 0 3px 0 5px;\n  min-width: 20px;\n  text-align: right;\n  color: #999;\n  white-space: nowrap;\n}\n\n.CodeMirror-guttermarker { color: black; }\n.CodeMirror-guttermarker-subtle { color: #999; }\n\n/* CURSOR */\n\n.CodeMirror-cursor {\n  border-left: 1px solid black;\n  border-right: none;\n  width: 0;\n}\n/* Shown when moving in bi-directional text */\n.CodeMirror div.CodeMirror-secondarycursor {\n  border-left: 1px solid silver;\n}\n.cm-fat-cursor .CodeMirror-cursor {\n  width: auto;\n  border: 0 !important;\n  background: #7e7;\n}\n.cm-fat-cursor div.CodeMirror-cursors {\n  z-index: 1;\n}\n.cm-fat-cursor-mark {\n  background-color: rgba(20, 255, 20, 0.5);\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  -webkit-animation: blink 1.06s steps(1) infinite; -moz-animation: blink 1.06s steps(1) infinite; -o-animation: blink 1.06s steps(1) infinite; -ms-animation: blink 1.06s steps(1) infinite; animation: blink 1.06s steps(1) infinite ;\n}\n.cm-animate-fat-cursor {\n  width: auto;\n  border: 0;\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  -webkit-animation: blink 1.06s steps(1) infinite; -moz-animation: blink 1.06s steps(1) infinite; -o-animation: blink 1.06s steps(1) infinite; -ms-animation: blink 1.06s steps(1) infinite; animation: blink 1.06s steps(1) infinite ;\n  background-color: #7e7;\n}\n@-moz-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@-webkit-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@-webkit-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@-moz-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n\n\n/* Can style cursor different in overwrite (non-insert) mode */\n.CodeMirror-overwrite .CodeMirror-cursor {}\n\n.cm-tab { display: inline-block; text-decoration: inherit; }\n\n.CodeMirror-rulers {\n  position: absolute;\n  left: 0; right: 0; top: -50px; bottom: 0;\n  overflow: hidden;\n}\n.CodeMirror-ruler {\n  border-left: 1px solid #ccc;\n  top: 0; bottom: 0;\n  position: absolute;\n}\n\n/* DEFAULT THEME */\n\n.cm-s-default .cm-header {color: blue;}\n.cm-s-default .cm-quote {color: #090;}\n.cm-negative {color: #d44;}\n.cm-positive {color: #292;}\n.cm-header, .cm-strong {font-weight: bold;}\n.cm-em {font-style: italic;}\n.cm-link {text-decoration: underline;}\n.cm-strikethrough {text-decoration: line-through;}\n\n.cm-s-default .cm-keyword {color: #708;}\n.cm-s-default .cm-atom {color: #219;}\n.cm-s-default .cm-number {color: #164;}\n.cm-s-default .cm-def {color: #00f;}\n.cm-s-default .cm-variable,\n.cm-s-default .cm-punctuation,\n.cm-s-default .cm-property,\n.cm-s-default .cm-operator {}\n.cm-s-default .cm-variable-2 {color: #05a;}\n.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}\n.cm-s-default .cm-comment {color: #a50;}\n.cm-s-default .cm-string {color: #a11;}\n.cm-s-default .cm-string-2 {color: #f50;}\n.cm-s-default .cm-meta {color: #555;}\n.cm-s-default .cm-qualifier {color: #555;}\n.cm-s-default .cm-builtin {color: #30a;}\n.cm-s-default .cm-bracket {color: #997;}\n.cm-s-default .cm-tag {color: #170;}\n.cm-s-default .cm-attribute {color: #00c;}\n.cm-s-default .cm-hr {color: #999;}\n.cm-s-default .cm-link {color: #00c;}\n\n.cm-s-default .cm-error {color: #f00;}\n.cm-invalidchar {color: #f00;}\n\n.CodeMirror-composing { border-bottom: 2px solid; }\n\n/* Default styles for common addons */\n\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}\n.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }\n.CodeMirror-activeline-background {background: #e8f2ff;}\n\n/* STOP */\n\n/* The rest of this file contains styles related to the mechanics of\n   the editor. You probably shouldn't touch them. */\n\n.CodeMirror {\n  position: relative;\n  overflow: hidden;\n  background: white;\n}\n\n.CodeMirror-scroll {\n  overflow: scroll !important; /* Things will break if this is overridden */\n  /* 50px is the magic margin used to hide the element's real scrollbars */\n  /* See overflow: hidden in .CodeMirror */\n  margin-bottom: -50px; margin-right: -50px;\n  padding-bottom: 50px;\n  height: 100%;\n  outline: none; /* Prevent dragging from highlighting the element */\n  position: relative;\n}\n.CodeMirror-sizer {\n  position: relative;\n  border-right: 50px solid transparent;\n}\n\n/* The fake, visible scrollbars. Used to force redraw during scrolling\n   before actual scrolling happens, thus preventing shaking and\n   flickering artifacts. */\n.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  position: absolute;\n  z-index: 6;\n  display: none;\n}\n.CodeMirror-vscrollbar {\n  right: 0; top: 0;\n  overflow-x: hidden;\n  overflow-y: scroll;\n}\n.CodeMirror-hscrollbar {\n  bottom: 0; left: 0;\n  overflow-y: hidden;\n  overflow-x: scroll;\n}\n.CodeMirror-scrollbar-filler {\n  right: 0; bottom: 0;\n}\n.CodeMirror-gutter-filler {\n  left: 0; bottom: 0;\n}\n\n.CodeMirror-gutters {\n  position: absolute; left: 0; top: 0;\n  min-height: 100%;\n  z-index: 3;\n}\n.CodeMirror-gutter {\n  white-space: normal;\n  height: 100%;\n  display: inline-block;\n  vertical-align: top;\n  margin-bottom: -50px;\n}\n.CodeMirror-gutter-wrapper {\n  position: absolute;\n  z-index: 4;\n  background: none !important;\n  border: none !important;\n}\n.CodeMirror-gutter-background {\n  position: absolute;\n  top: 0; bottom: 0;\n  z-index: 4;\n}\n.CodeMirror-gutter-elt {\n  position: absolute;\n  cursor: default;\n  z-index: 4;\n}\n.CodeMirror-gutter-wrapper ::selection { background-color: transparent }\n.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }\n\n.CodeMirror-lines {\n  cursor: text;\n  min-height: 1px; /* prevents collapsing before first draw */\n}\n.CodeMirror pre.CodeMirror-line,\n.CodeMirror pre.CodeMirror-line-like {\n  /* Reset some styles that the rest of the page might have set */\n  -moz-border-radius: 0; -webkit-border-radius: 0; -webkit-border-radius: 0; -moz-border-radius: 0; -o-border-radius: 0; -ms-border-radius: 0; border-radius: 0 ;\n  border-width: 0;\n  background: transparent;\n  font-family: inherit;\n  font-size: inherit;\n  margin: 0;\n  white-space: pre;\n  word-wrap: normal;\n  line-height: inherit;\n  color: inherit;\n  z-index: 2;\n  position: relative;\n  overflow: visible;\n  -webkit-tap-highlight-color: transparent;\n  -webkit-font-variant-ligatures: contextual;\n  font-variant-ligatures: contextual;\n}\n.CodeMirror-wrap pre.CodeMirror-line,\n.CodeMirror-wrap pre.CodeMirror-line-like {\n  word-wrap: break-word;\n  white-space: pre-wrap;\n  word-break: normal;\n}\n\n.CodeMirror-linebackground {\n  position: absolute;\n  left: 0; right: 0; top: 0; bottom: 0;\n  z-index: 0;\n}\n\n.CodeMirror-linewidget {\n  position: relative;\n  z-index: 2;\n  padding: 0.1px; /* Force widget margins to stay inside of the container */\n}\n\n.CodeMirror-widget {}\n\n.CodeMirror-rtl pre { direction: rtl; }\n\n.CodeMirror-code {\n  outline: none;\n}\n\n/* Force content-box sizing for the elements where we expect it */\n.CodeMirror-scroll,\n.CodeMirror-sizer,\n.CodeMirror-gutter,\n.CodeMirror-gutters,\n.CodeMirror-linenumber {\n  -moz-box-sizing: content-box;\n  -webkit-box-sizing: content-box; -moz-box-sizing: content-box; -o-box-sizing: content-box; -ms-box-sizing: content-box; box-sizing: content-box ;\n}\n\n.CodeMirror-measure {\n  position: absolute;\n  width: 100%;\n  height: 0;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.CodeMirror-cursor {\n  position: absolute;\n  pointer-events: none;\n}\n.CodeMirror-measure pre { position: static; }\n\ndiv.CodeMirror-cursors {\n  visibility: hidden;\n  position: relative;\n  z-index: 3;\n}\ndiv.CodeMirror-dragcursors {\n  visibility: visible;\n}\n\n.CodeMirror-focused div.CodeMirror-cursors {\n  visibility: visible;\n}\n\n.CodeMirror-selected { background: #d9d9d9; }\n.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }\n.CodeMirror-crosshair { cursor: crosshair; }\n.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }\n.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }\n\n.cm-searching {\n  background-color: #ffa;\n  background-color: rgba(255, 255, 0, .4);\n}\n\n/* Used to force a border model for a node */\n.cm-force-border { padding-right: .1px; }\n\n@media print {\n  /* Hide the cursor when printing */\n  .CodeMirror div.CodeMirror-cursors {\n    visibility: hidden;\n  }\n}\n\n/* See issue #2901 */\n.cm-tab-wrap-hack:after { content: ''; }\n\n/* Help users use markselection to safely style text background */\nspan.CodeMirror-selectedtext { background: none; }\n\n\n/* ---- extension/mdn-like-custom.css ---- */\n\n\n/*\n  MDN-LIKE Theme - Mozilla\n  Ported to CodeMirror by Peter Kroon <plakroon@gmail.com>\n  Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues\n  GitHub: @peterkroon\n\n  The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation\n\n*/\n.cm-s-mdn-like.CodeMirror { color: #666; background-color: #fff; }\n.cm-s-mdn-like div.CodeMirror-selected { background: #fefdb5; }\n.cm-s-mdn-like .CodeMirror-line::selection, .cm-s-mdn-like .CodeMirror-line > span::selection, .cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #fefdb5; }\n.cm-s-mdn-like .CodeMirror-line::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #fefdb5; }\n\n.cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; color: #333; }\n.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; }\n.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; }\n\n.cm-s-mdn-like .cm-keyword { color: #6262FF; }\n.cm-s-mdn-like .cm-atom { color: #F90; }\n.cm-s-mdn-like .cm-number { color:  #ca7841; }\n.cm-s-mdn-like .cm-def { color: #8DA6CE; }\n.cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; }\n.cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def, .cm-s-mdn-like span.cm-type { color: #07a; }\n\n.cm-s-mdn-like .cm-variable { color: #07a; }\n.cm-s-mdn-like .cm-property { color: #905; }\n.cm-s-mdn-like .cm-qualifier { color: #690; }\n\n.cm-s-mdn-like .cm-operator { color: #cda869; }\n.cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; }\n.cm-s-mdn-like .cm-string { color:#07a; }\n.cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/\n.cm-s-mdn-like .cm-meta { color: #000; } /*?*/\n.cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/\n.cm-s-mdn-like .cm-tag { color: #997643; }\n.cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/\n.cm-s-mdn-like .cm-header { color: #FF6400; }\n.cm-s-mdn-like .cm-hr { color: #AEAEAE; }\n.cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; }\n.cm-s-mdn-like .cm-error { border-bottom: 1px solid red; }\n\ndiv.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff; }\ndiv.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey; color: inherit; }\n\n\n/* ---- extension/dialog/dialog.css ---- */\n\n\n.CodeMirror-dialog {\n  position: absolute;\n  left: 0; right: 0;\n  background: inherit;\n  z-index: 15;\n  padding: .1em .8em;\n  overflow: hidden;\n  color: inherit;\n}\n\n.CodeMirror-dialog-top {\n  border-bottom: 1px solid #eee;\n  top: 0;\n}\n\n.CodeMirror-dialog-bottom {\n  border-top: 1px solid #eee;\n  bottom: 0;\n}\n\n.CodeMirror-dialog input {\n  border: none;\n  outline: none;\n  background: transparent;\n  width: 20em;\n  color: inherit;\n  font-family: monospace;\n}\n\n.CodeMirror-dialog button {\n  font-size: 70%;\n}\n\n\n/* ---- extension/fold/foldgutter.css ---- */\n\n\n.CodeMirror-foldmarker {\n  color: blue;\n  text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;\n  font-family: arial;\n  line-height: .3;\n  cursor: pointer;\n}\n.CodeMirror-foldgutter {\n  width: .7em;\n}\n.CodeMirror-foldgutter-open,\n.CodeMirror-foldgutter-folded {\n  cursor: pointer;\n}\n.CodeMirror-foldgutter-open:after {\n  content: \"\\25BE\";\n}\n.CodeMirror-foldgutter-folded:after {\n  content: \"\\25B8\";\n}\n\n\n/* ---- extension/hint/show-hint.css ---- */\n\n\n.CodeMirror-hints {\n  position: absolute;\n  z-index: 10;\n  overflow: hidden;\n  list-style: none;\n\n  margin: 0;\n  padding: 2px;\n\n  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); -o-box-shadow: 2px 3px 5px rgba(0,0,0,.2); -ms-box-shadow: 2px 3px 5px rgba(0,0,0,.2); box-shadow: 2px 3px 5px rgba(0,0,0,.2) ;\n  -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ;\n  border: 1px solid silver;\n\n  background: white;\n  font-size: 90%;\n  font-family: monospace;\n\n  max-height: 20em;\n  overflow-y: auto;\n}\n\n.CodeMirror-hint {\n  margin: 0;\n  padding: 0 4px;\n  -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ;\n  white-space: pre;\n  color: black;\n  cursor: pointer;\n}\n\nli.CodeMirror-hint-active {\n  background: #08f;\n  color: white;\n}\n\n\n/* ---- extension/lint/lint.css ---- */\n\n\n/* The lint marker gutter */\n.CodeMirror-lint-markers {\n  width: 16px;\n}\n\n.CodeMirror-lint-tooltip {\n  background-color: #ffd;\n  border: 1px solid black;\n  -webkit-border-radius: 4px 4px 4px 4px; -moz-border-radius: 4px 4px 4px 4px; -o-border-radius: 4px 4px 4px 4px; -ms-border-radius: 4px 4px 4px 4px; border-radius: 4px 4px 4px 4px ;\n  color: black;\n  font-family: monospace;\n  font-size: 10pt;\n  overflow: hidden;\n  padding: 2px 5px;\n  position: fixed;\n  white-space: pre;\n  white-space: pre-wrap;\n  z-index: 100;\n  max-width: 600px;\n  opacity: 0;\n  -webkit-transition: opacity .4s; -moz-transition: opacity .4s; -o-transition: opacity .4s; -ms-transition: opacity .4s; transition: opacity .4s ;\n  -moz-transition: opacity .4s;\n  -webkit-transition: opacity .4s;\n  -o-transition: opacity .4s;\n  -ms-transition: opacity .4s;\n}\n\n.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {\n  background-position: left bottom;\n  background-repeat: repeat-x;\n}\n\n.CodeMirror-lint-mark-error {\n  background-image:\n  url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==\")\n  ;\n}\n\n.CodeMirror-lint-mark-warning {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=\");\n}\n\n.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {\n  background-position: center center;\n  background-repeat: no-repeat;\n  cursor: pointer;\n  display: inline-block;\n  height: 16px;\n  width: 16px;\n  vertical-align: middle;\n  position: relative;\n}\n\n.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {\n  padding-left: 18px;\n  background-position: top left;\n  background-repeat: no-repeat;\n}\n\n.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=\");\n}\n\n.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=\");\n}\n\n.CodeMirror-lint-marker-multiple {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC\");\n  background-repeat: no-repeat;\n  background-position: right bottom;\n  width: 100%; height: 100%;\n}\n\n\n/* ---- extension/scroll/simplescrollbars.css ---- */\n\n\n.CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div {\n  position: absolute;\n  background: #ccc;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;\n  border: 1px solid #bbb;\n  -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ;\n}\n\n.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {\n  position: absolute;\n  z-index: 6;\n  background: #eee;\n}\n\n.CodeMirror-simplescroll-horizontal {\n  bottom: 0; left: 0;\n  height: 8px;\n}\n.CodeMirror-simplescroll-horizontal div {\n  bottom: 0;\n  height: 100%;\n}\n\n.CodeMirror-simplescroll-vertical {\n  right: 0; top: 0;\n  width: 8px;\n}\n.CodeMirror-simplescroll-vertical div {\n  right: 0;\n  width: 100%;\n}\n\n\n.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler {\n  display: none;\n}\n\n.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {\n  position: absolute;\n  background: #bcd;\n  -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ;\n}\n\n.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical {\n  position: absolute;\n  z-index: 6;\n}\n\n.CodeMirror-overlayscroll-horizontal {\n  bottom: 0; left: 0;\n  height: 6px;\n}\n.CodeMirror-overlayscroll-horizontal div {\n  bottom: 0;\n  height: 100%;\n}\n\n.CodeMirror-overlayscroll-vertical {\n  right: 0; top: 0;\n  width: 6px;\n}\n.CodeMirror-overlayscroll-vertical div {\n  right: 0;\n  width: 100%;\n}\n\n\n/* ---- extension/search/matchesonscrollbar.css ---- */\n\n\n.CodeMirror-search-match {\n  background: gold;\n  border-top: 1px solid orange;\n  border-bottom: 1px solid orange;\n  -moz-box-sizing: border-box;\n  -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;\n  opacity: .5;\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/all.js",
    "content": "\n/* ---- base/codemirror.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// This is CodeMirror (https://codemirror.net), a code editor\n// implemented in JavaScript on top of the browser's DOM.\n//\n// You can find some technical background for some of the code below\n// at http://marijnhaverbeke.nl/blog/#cm-internals .\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = global || self, global.CodeMirror = factory());\n}(this, (function () { 'use strict';\n\n  // Kludges for bugs and behavior differences that can't be feature\n  // detected are enabled based on userAgent etc sniffing.\n  var userAgent = navigator.userAgent;\n  var platform = navigator.platform;\n\n  var gecko = /gecko\\/\\d/i.test(userAgent);\n  var ie_upto10 = /MSIE \\d/.test(userAgent);\n  var ie_11up = /Trident\\/(?:[7-9]|\\d{2,})\\..*rv:(\\d+)/.exec(userAgent);\n  var edge = /Edge\\/(\\d+)/.exec(userAgent);\n  var ie = ie_upto10 || ie_11up || edge;\n  var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]);\n  var webkit = !edge && /WebKit\\//.test(userAgent);\n  var qtwebkit = webkit && /Qt\\/\\d+\\.\\d+/.test(userAgent);\n  var chrome = !edge && /Chrome\\//.test(userAgent);\n  var presto = /Opera\\//.test(userAgent);\n  var safari = /Apple Computer/.test(navigator.vendor);\n  var mac_geMountainLion = /Mac OS X 1\\d\\D([8-9]|\\d\\d)\\D/.test(userAgent);\n  var phantom = /PhantomJS/.test(userAgent);\n\n  var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\\/\\w+/.test(userAgent);\n  var android = /Android/.test(userAgent);\n  // This is woefully incomplete. Suggestions for alternative methods welcome.\n  var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);\n  var mac = ios || /Mac/.test(platform);\n  var chromeOS = /\\bCrOS\\b/.test(userAgent);\n  var windows = /win/i.test(platform);\n\n  var presto_version = presto && userAgent.match(/Version\\/(\\d*\\.\\d*)/);\n  if (presto_version) { presto_version = Number(presto_version[1]); }\n  if (presto_version && presto_version >= 15) { presto = false; webkit = true; }\n  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X\n  var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));\n  var captureRightClick = gecko || (ie && ie_version >= 9);\n\n  function classTest(cls) { return new RegExp(\"(^|\\\\s)\" + cls + \"(?:$|\\\\s)\\\\s*\") }\n\n  var rmClass = function(node, cls) {\n    var current = node.className;\n    var match = classTest(cls).exec(current);\n    if (match) {\n      var after = current.slice(match.index + match[0].length);\n      node.className = current.slice(0, match.index) + (after ? match[1] + after : \"\");\n    }\n  };\n\n  function removeChildren(e) {\n    for (var count = e.childNodes.length; count > 0; --count)\n      { e.removeChild(e.firstChild); }\n    return e\n  }\n\n  function removeChildrenAndAdd(parent, e) {\n    return removeChildren(parent).appendChild(e)\n  }\n\n  function elt(tag, content, className, style) {\n    var e = document.createElement(tag);\n    if (className) { e.className = className; }\n    if (style) { e.style.cssText = style; }\n    if (typeof content == \"string\") { e.appendChild(document.createTextNode(content)); }\n    else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } }\n    return e\n  }\n  // wrapper for elt, which removes the elt from the accessibility tree\n  function eltP(tag, content, className, style) {\n    var e = elt(tag, content, className, style);\n    e.setAttribute(\"role\", \"presentation\");\n    return e\n  }\n\n  var range;\n  if (document.createRange) { range = function(node, start, end, endNode) {\n    var r = document.createRange();\n    r.setEnd(endNode || node, end);\n    r.setStart(node, start);\n    return r\n  }; }\n  else { range = function(node, start, end) {\n    var r = document.body.createTextRange();\n    try { r.moveToElementText(node.parentNode); }\n    catch(e) { return r }\n    r.collapse(true);\n    r.moveEnd(\"character\", end);\n    r.moveStart(\"character\", start);\n    return r\n  }; }\n\n  function contains(parent, child) {\n    if (child.nodeType == 3) // Android browser always returns false when child is a textnode\n      { child = child.parentNode; }\n    if (parent.contains)\n      { return parent.contains(child) }\n    do {\n      if (child.nodeType == 11) { child = child.host; }\n      if (child == parent) { return true }\n    } while (child = child.parentNode)\n  }\n\n  function activeElt() {\n    // IE and Edge may throw an \"Unspecified Error\" when accessing document.activeElement.\n    // IE < 10 will throw when accessed while the page is loading or in an iframe.\n    // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.\n    var activeElement;\n    try {\n      activeElement = document.activeElement;\n    } catch(e) {\n      activeElement = document.body || null;\n    }\n    while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)\n      { activeElement = activeElement.shadowRoot.activeElement; }\n    return activeElement\n  }\n\n  function addClass(node, cls) {\n    var current = node.className;\n    if (!classTest(cls).test(current)) { node.className += (current ? \" \" : \"\") + cls; }\n  }\n  function joinClasses(a, b) {\n    var as = a.split(\" \");\n    for (var i = 0; i < as.length; i++)\n      { if (as[i] && !classTest(as[i]).test(b)) { b += \" \" + as[i]; } }\n    return b\n  }\n\n  var selectInput = function(node) { node.select(); };\n  if (ios) // Mobile Safari apparently has a bug where select() is broken.\n    { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; }\n  else if (ie) // Suppress mysterious IE10 errors\n    { selectInput = function(node) { try { node.select(); } catch(_e) {} }; }\n\n  function bind(f) {\n    var args = Array.prototype.slice.call(arguments, 1);\n    return function(){return f.apply(null, args)}\n  }\n\n  function copyObj(obj, target, overwrite) {\n    if (!target) { target = {}; }\n    for (var prop in obj)\n      { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))\n        { target[prop] = obj[prop]; } }\n    return target\n  }\n\n  // Counts the column offset in a string, taking tabs into account.\n  // Used mostly to find indentation.\n  function countColumn(string, end, tabSize, startIndex, startValue) {\n    if (end == null) {\n      end = string.search(/[^\\s\\u00a0]/);\n      if (end == -1) { end = string.length; }\n    }\n    for (var i = startIndex || 0, n = startValue || 0;;) {\n      var nextTab = string.indexOf(\"\\t\", i);\n      if (nextTab < 0 || nextTab >= end)\n        { return n + (end - i) }\n      n += nextTab - i;\n      n += tabSize - (n % tabSize);\n      i = nextTab + 1;\n    }\n  }\n\n  var Delayed = function() {\n    this.id = null;\n    this.f = null;\n    this.time = 0;\n    this.handler = bind(this.onTimeout, this);\n  };\n  Delayed.prototype.onTimeout = function (self) {\n    self.id = 0;\n    if (self.time <= +new Date) {\n      self.f();\n    } else {\n      setTimeout(self.handler, self.time - +new Date);\n    }\n  };\n  Delayed.prototype.set = function (ms, f) {\n    this.f = f;\n    var time = +new Date + ms;\n    if (!this.id || time < this.time) {\n      clearTimeout(this.id);\n      this.id = setTimeout(this.handler, ms);\n      this.time = time;\n    }\n  };\n\n  function indexOf(array, elt) {\n    for (var i = 0; i < array.length; ++i)\n      { if (array[i] == elt) { return i } }\n    return -1\n  }\n\n  // Number of pixels added to scroller and sizer to hide scrollbar\n  var scrollerGap = 50;\n\n  // Returned or thrown by various protocols to signal 'I'm not\n  // handling this'.\n  var Pass = {toString: function(){return \"CodeMirror.Pass\"}};\n\n  // Reused option objects for setSelection & friends\n  var sel_dontScroll = {scroll: false}, sel_mouse = {origin: \"*mouse\"}, sel_move = {origin: \"+move\"};\n\n  // The inverse of countColumn -- find the offset that corresponds to\n  // a particular column.\n  function findColumn(string, goal, tabSize) {\n    for (var pos = 0, col = 0;;) {\n      var nextTab = string.indexOf(\"\\t\", pos);\n      if (nextTab == -1) { nextTab = string.length; }\n      var skipped = nextTab - pos;\n      if (nextTab == string.length || col + skipped >= goal)\n        { return pos + Math.min(skipped, goal - col) }\n      col += nextTab - pos;\n      col += tabSize - (col % tabSize);\n      pos = nextTab + 1;\n      if (col >= goal) { return pos }\n    }\n  }\n\n  var spaceStrs = [\"\"];\n  function spaceStr(n) {\n    while (spaceStrs.length <= n)\n      { spaceStrs.push(lst(spaceStrs) + \" \"); }\n    return spaceStrs[n]\n  }\n\n  function lst(arr) { return arr[arr.length-1] }\n\n  function map(array, f) {\n    var out = [];\n    for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); }\n    return out\n  }\n\n  function insertSorted(array, value, score) {\n    var pos = 0, priority = score(value);\n    while (pos < array.length && score(array[pos]) <= priority) { pos++; }\n    array.splice(pos, 0, value);\n  }\n\n  function nothing() {}\n\n  function createObj(base, props) {\n    var inst;\n    if (Object.create) {\n      inst = Object.create(base);\n    } else {\n      nothing.prototype = base;\n      inst = new nothing();\n    }\n    if (props) { copyObj(props, inst); }\n    return inst\n  }\n\n  var nonASCIISingleCaseWordChar = /[\\u00df\\u0587\\u0590-\\u05f4\\u0600-\\u06ff\\u3040-\\u309f\\u30a0-\\u30ff\\u3400-\\u4db5\\u4e00-\\u9fcc\\uac00-\\ud7af]/;\n  function isWordCharBasic(ch) {\n    return /\\w/.test(ch) || ch > \"\\x80\" &&\n      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))\n  }\n  function isWordChar(ch, helper) {\n    if (!helper) { return isWordCharBasic(ch) }\n    if (helper.source.indexOf(\"\\\\w\") > -1 && isWordCharBasic(ch)) { return true }\n    return helper.test(ch)\n  }\n\n  function isEmpty(obj) {\n    for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }\n    return true\n  }\n\n  // Extending unicode characters. A series of a non-extending char +\n  // any number of extending chars is treated as a single unit as far\n  // as editing and measuring is concerned. This is not fully correct,\n  // since some scripts/fonts/browsers also treat other configurations\n  // of code points as a group.\n  var extendingChars = /[\\u0300-\\u036f\\u0483-\\u0489\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u064b-\\u065e\\u0670\\u06d6-\\u06dc\\u06de-\\u06e4\\u06e7\\u06e8\\u06ea-\\u06ed\\u0711\\u0730-\\u074a\\u07a6-\\u07b0\\u07eb-\\u07f3\\u0816-\\u0819\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0900-\\u0902\\u093c\\u0941-\\u0948\\u094d\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09bc\\u09be\\u09c1-\\u09c4\\u09cd\\u09d7\\u09e2\\u09e3\\u0a01\\u0a02\\u0a3c\\u0a41\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a70\\u0a71\\u0a75\\u0a81\\u0a82\\u0abc\\u0ac1-\\u0ac5\\u0ac7\\u0ac8\\u0acd\\u0ae2\\u0ae3\\u0b01\\u0b3c\\u0b3e\\u0b3f\\u0b41-\\u0b44\\u0b4d\\u0b56\\u0b57\\u0b62\\u0b63\\u0b82\\u0bbe\\u0bc0\\u0bcd\\u0bd7\\u0c3e-\\u0c40\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62\\u0c63\\u0cbc\\u0cbf\\u0cc2\\u0cc6\\u0ccc\\u0ccd\\u0cd5\\u0cd6\\u0ce2\\u0ce3\\u0d3e\\u0d41-\\u0d44\\u0d4d\\u0d57\\u0d62\\u0d63\\u0dca\\u0dcf\\u0dd2-\\u0dd4\\u0dd6\\u0ddf\\u0e31\\u0e34-\\u0e3a\\u0e47-\\u0e4e\\u0eb1\\u0eb4-\\u0eb9\\u0ebb\\u0ebc\\u0ec8-\\u0ecd\\u0f18\\u0f19\\u0f35\\u0f37\\u0f39\\u0f71-\\u0f7e\\u0f80-\\u0f84\\u0f86\\u0f87\\u0f90-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u102d-\\u1030\\u1032-\\u1037\\u1039\\u103a\\u103d\\u103e\\u1058\\u1059\\u105e-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108d\\u109d\\u135f\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17b7-\\u17bd\\u17c6\\u17c9-\\u17d3\\u17dd\\u180b-\\u180d\\u18a9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193b\\u1a17\\u1a18\\u1a56\\u1a58-\\u1a5e\\u1a60\\u1a62\\u1a65-\\u1a6c\\u1a73-\\u1a7c\\u1a7f\\u1b00-\\u1b03\\u1b34\\u1b36-\\u1b3a\\u1b3c\\u1b42\\u1b6b-\\u1b73\\u1b80\\u1b81\\u1ba2-\\u1ba5\\u1ba8\\u1ba9\\u1c2c-\\u1c33\\u1c36\\u1c37\\u1cd0-\\u1cd2\\u1cd4-\\u1ce0\\u1ce2-\\u1ce8\\u1ced\\u1dc0-\\u1de6\\u1dfd-\\u1dff\\u200c\\u200d\\u20d0-\\u20f0\\u2cef-\\u2cf1\\u2de0-\\u2dff\\u302a-\\u302f\\u3099\\u309a\\ua66f-\\ua672\\ua67c\\ua67d\\ua6f0\\ua6f1\\ua802\\ua806\\ua80b\\ua825\\ua826\\ua8c4\\ua8e0-\\ua8f1\\ua926-\\ua92d\\ua947-\\ua951\\ua980-\\ua982\\ua9b3\\ua9b6-\\ua9b9\\ua9bc\\uaa29-\\uaa2e\\uaa31\\uaa32\\uaa35\\uaa36\\uaa43\\uaa4c\\uaab0\\uaab2-\\uaab4\\uaab7\\uaab8\\uaabe\\uaabf\\uaac1\\uabe5\\uabe8\\uabed\\udc00-\\udfff\\ufb1e\\ufe00-\\ufe0f\\ufe20-\\ufe26\\uff9e\\uff9f]/;\n  function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) }\n\n  // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range.\n  function skipExtendingChars(str, pos, dir) {\n    while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; }\n    return pos\n  }\n\n  // Returns the value from the range [`from`; `to`] that satisfies\n  // `pred` and is closest to `from`. Assumes that at least `to`\n  // satisfies `pred`. Supports `from` being greater than `to`.\n  function findFirst(pred, from, to) {\n    // At any point we are certain `to` satisfies `pred`, don't know\n    // whether `from` does.\n    var dir = from > to ? -1 : 1;\n    for (;;) {\n      if (from == to) { return from }\n      var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF);\n      if (mid == from) { return pred(mid) ? from : to }\n      if (pred(mid)) { to = mid; }\n      else { from = mid + dir; }\n    }\n  }\n\n  // BIDI HELPERS\n\n  function iterateBidiSections(order, from, to, f) {\n    if (!order) { return f(from, to, \"ltr\", 0) }\n    var found = false;\n    for (var i = 0; i < order.length; ++i) {\n      var part = order[i];\n      if (part.from < to && part.to > from || from == to && part.to == from) {\n        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? \"rtl\" : \"ltr\", i);\n        found = true;\n      }\n    }\n    if (!found) { f(from, to, \"ltr\"); }\n  }\n\n  var bidiOther = null;\n  function getBidiPartAt(order, ch, sticky) {\n    var found;\n    bidiOther = null;\n    for (var i = 0; i < order.length; ++i) {\n      var cur = order[i];\n      if (cur.from < ch && cur.to > ch) { return i }\n      if (cur.to == ch) {\n        if (cur.from != cur.to && sticky == \"before\") { found = i; }\n        else { bidiOther = i; }\n      }\n      if (cur.from == ch) {\n        if (cur.from != cur.to && sticky != \"before\") { found = i; }\n        else { bidiOther = i; }\n      }\n    }\n    return found != null ? found : bidiOther\n  }\n\n  // Bidirectional ordering algorithm\n  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm\n  // that this (partially) implements.\n\n  // One-char codes used for character types:\n  // L (L):   Left-to-Right\n  // R (R):   Right-to-Left\n  // r (AL):  Right-to-Left Arabic\n  // 1 (EN):  European Number\n  // + (ES):  European Number Separator\n  // % (ET):  European Number Terminator\n  // n (AN):  Arabic Number\n  // , (CS):  Common Number Separator\n  // m (NSM): Non-Spacing Mark\n  // b (BN):  Boundary Neutral\n  // s (B):   Paragraph Separator\n  // t (S):   Segment Separator\n  // w (WS):  Whitespace\n  // N (ON):  Other Neutrals\n\n  // Returns null if characters are ordered as they appear\n  // (left-to-right), or an array of sections ({from, to, level}\n  // objects) in the order in which they occur visually.\n  var bidiOrdering = (function() {\n    // Character types for codepoints 0 to 0xff\n    var lowTypes = \"bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN\";\n    // Character types for codepoints 0x600 to 0x6f9\n    var arabicTypes = \"nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111\";\n    function charType(code) {\n      if (code <= 0xf7) { return lowTypes.charAt(code) }\n      else if (0x590 <= code && code <= 0x5f4) { return \"R\" }\n      else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }\n      else if (0x6ee <= code && code <= 0x8ac) { return \"r\" }\n      else if (0x2000 <= code && code <= 0x200b) { return \"w\" }\n      else if (code == 0x200c) { return \"b\" }\n      else { return \"L\" }\n    }\n\n    var bidiRE = /[\\u0590-\\u05f4\\u0600-\\u06ff\\u0700-\\u08ac]/;\n    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;\n\n    function BidiSpan(level, from, to) {\n      this.level = level;\n      this.from = from; this.to = to;\n    }\n\n    return function(str, direction) {\n      var outerType = direction == \"ltr\" ? \"L\" : \"R\";\n\n      if (str.length == 0 || direction == \"ltr\" && !bidiRE.test(str)) { return false }\n      var len = str.length, types = [];\n      for (var i = 0; i < len; ++i)\n        { types.push(charType(str.charCodeAt(i))); }\n\n      // W1. Examine each non-spacing mark (NSM) in the level run, and\n      // change the type of the NSM to the type of the previous\n      // character. If the NSM is at the start of the level run, it will\n      // get the type of sor.\n      for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) {\n        var type = types[i$1];\n        if (type == \"m\") { types[i$1] = prev; }\n        else { prev = type; }\n      }\n\n      // W2. Search backwards from each instance of a European number\n      // until the first strong type (R, L, AL, or sor) is found. If an\n      // AL is found, change the type of the European number to Arabic\n      // number.\n      // W3. Change all ALs to R.\n      for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) {\n        var type$1 = types[i$2];\n        if (type$1 == \"1\" && cur == \"r\") { types[i$2] = \"n\"; }\n        else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == \"r\") { types[i$2] = \"R\"; } }\n      }\n\n      // W4. A single European separator between two European numbers\n      // changes to a European number. A single common separator between\n      // two numbers of the same type changes to that type.\n      for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {\n        var type$2 = types[i$3];\n        if (type$2 == \"+\" && prev$1 == \"1\" && types[i$3+1] == \"1\") { types[i$3] = \"1\"; }\n        else if (type$2 == \",\" && prev$1 == types[i$3+1] &&\n                 (prev$1 == \"1\" || prev$1 == \"n\")) { types[i$3] = prev$1; }\n        prev$1 = type$2;\n      }\n\n      // W5. A sequence of European terminators adjacent to European\n      // numbers changes to all European numbers.\n      // W6. Otherwise, separators and terminators change to Other\n      // Neutral.\n      for (var i$4 = 0; i$4 < len; ++i$4) {\n        var type$3 = types[i$4];\n        if (type$3 == \",\") { types[i$4] = \"N\"; }\n        else if (type$3 == \"%\") {\n          var end = (void 0);\n          for (end = i$4 + 1; end < len && types[end] == \"%\"; ++end) {}\n          var replace = (i$4 && types[i$4-1] == \"!\") || (end < len && types[end] == \"1\") ? \"1\" : \"N\";\n          for (var j = i$4; j < end; ++j) { types[j] = replace; }\n          i$4 = end - 1;\n        }\n      }\n\n      // W7. Search backwards from each instance of a European number\n      // until the first strong type (R, L, or sor) is found. If an L is\n      // found, then change the type of the European number to L.\n      for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {\n        var type$4 = types[i$5];\n        if (cur$1 == \"L\" && type$4 == \"1\") { types[i$5] = \"L\"; }\n        else if (isStrong.test(type$4)) { cur$1 = type$4; }\n      }\n\n      // N1. A sequence of neutrals takes the direction of the\n      // surrounding strong text if the text on both sides has the same\n      // direction. European and Arabic numbers act as if they were R in\n      // terms of their influence on neutrals. Start-of-level-run (sor)\n      // and end-of-level-run (eor) are used at level run boundaries.\n      // N2. Any remaining neutrals take the embedding direction.\n      for (var i$6 = 0; i$6 < len; ++i$6) {\n        if (isNeutral.test(types[i$6])) {\n          var end$1 = (void 0);\n          for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {}\n          var before = (i$6 ? types[i$6-1] : outerType) == \"L\";\n          var after = (end$1 < len ? types[end$1] : outerType) == \"L\";\n          var replace$1 = before == after ? (before ? \"L\" : \"R\") : outerType;\n          for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; }\n          i$6 = end$1 - 1;\n        }\n      }\n\n      // Here we depart from the documented algorithm, in order to avoid\n      // building up an actual levels array. Since there are only three\n      // levels (0, 1, 2) in an implementation that doesn't take\n      // explicit embedding into account, we can build up the order on\n      // the fly, without following the level-based algorithm.\n      var order = [], m;\n      for (var i$7 = 0; i$7 < len;) {\n        if (countsAsLeft.test(types[i$7])) {\n          var start = i$7;\n          for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}\n          order.push(new BidiSpan(0, start, i$7));\n        } else {\n          var pos = i$7, at = order.length, isRTL = direction == \"rtl\" ? 1 : 0;\n          for (++i$7; i$7 < len && types[i$7] != \"L\"; ++i$7) {}\n          for (var j$2 = pos; j$2 < i$7;) {\n            if (countsAsNum.test(types[j$2])) {\n              if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); at += isRTL; }\n              var nstart = j$2;\n              for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}\n              order.splice(at, 0, new BidiSpan(2, nstart, j$2));\n              at += isRTL;\n              pos = j$2;\n            } else { ++j$2; }\n          }\n          if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); }\n        }\n      }\n      if (direction == \"ltr\") {\n        if (order[0].level == 1 && (m = str.match(/^\\s+/))) {\n          order[0].from = m[0].length;\n          order.unshift(new BidiSpan(0, 0, m[0].length));\n        }\n        if (lst(order).level == 1 && (m = str.match(/\\s+$/))) {\n          lst(order).to -= m[0].length;\n          order.push(new BidiSpan(0, len - m[0].length, len));\n        }\n      }\n\n      return direction == \"rtl\" ? order.reverse() : order\n    }\n  })();\n\n  // Get the bidi ordering for the given line (and cache it). Returns\n  // false for lines that are fully left-to-right, and an array of\n  // BidiSpan objects otherwise.\n  function getOrder(line, direction) {\n    var order = line.order;\n    if (order == null) { order = line.order = bidiOrdering(line.text, direction); }\n    return order\n  }\n\n  // EVENT HANDLING\n\n  // Lightweight event framework. on/off also work on DOM nodes,\n  // registering native DOM handlers.\n\n  var noHandlers = [];\n\n  var on = function(emitter, type, f) {\n    if (emitter.addEventListener) {\n      emitter.addEventListener(type, f, false);\n    } else if (emitter.attachEvent) {\n      emitter.attachEvent(\"on\" + type, f);\n    } else {\n      var map = emitter._handlers || (emitter._handlers = {});\n      map[type] = (map[type] || noHandlers).concat(f);\n    }\n  };\n\n  function getHandlers(emitter, type) {\n    return emitter._handlers && emitter._handlers[type] || noHandlers\n  }\n\n  function off(emitter, type, f) {\n    if (emitter.removeEventListener) {\n      emitter.removeEventListener(type, f, false);\n    } else if (emitter.detachEvent) {\n      emitter.detachEvent(\"on\" + type, f);\n    } else {\n      var map = emitter._handlers, arr = map && map[type];\n      if (arr) {\n        var index = indexOf(arr, f);\n        if (index > -1)\n          { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); }\n      }\n    }\n  }\n\n  function signal(emitter, type /*, values...*/) {\n    var handlers = getHandlers(emitter, type);\n    if (!handlers.length) { return }\n    var args = Array.prototype.slice.call(arguments, 2);\n    for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); }\n  }\n\n  // The DOM events that CodeMirror handles can be overridden by\n  // registering a (non-DOM) handler on the editor for the event name,\n  // and preventDefault-ing the event in that handler.\n  function signalDOMEvent(cm, e, override) {\n    if (typeof e == \"string\")\n      { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; }\n    signal(cm, override || e.type, cm, e);\n    return e_defaultPrevented(e) || e.codemirrorIgnore\n  }\n\n  function signalCursorActivity(cm) {\n    var arr = cm._handlers && cm._handlers.cursorActivity;\n    if (!arr) { return }\n    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);\n    for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1)\n      { set.push(arr[i]); } }\n  }\n\n  function hasHandler(emitter, type) {\n    return getHandlers(emitter, type).length > 0\n  }\n\n  // Add on and off methods to a constructor's prototype, to make\n  // registering events on such objects more convenient.\n  function eventMixin(ctor) {\n    ctor.prototype.on = function(type, f) {on(this, type, f);};\n    ctor.prototype.off = function(type, f) {off(this, type, f);};\n  }\n\n  // Due to the fact that we still support jurassic IE versions, some\n  // compatibility wrappers are needed.\n\n  function e_preventDefault(e) {\n    if (e.preventDefault) { e.preventDefault(); }\n    else { e.returnValue = false; }\n  }\n  function e_stopPropagation(e) {\n    if (e.stopPropagation) { e.stopPropagation(); }\n    else { e.cancelBubble = true; }\n  }\n  function e_defaultPrevented(e) {\n    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false\n  }\n  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}\n\n  function e_target(e) {return e.target || e.srcElement}\n  function e_button(e) {\n    var b = e.which;\n    if (b == null) {\n      if (e.button & 1) { b = 1; }\n      else if (e.button & 2) { b = 3; }\n      else if (e.button & 4) { b = 2; }\n    }\n    if (mac && e.ctrlKey && b == 1) { b = 3; }\n    return b\n  }\n\n  // Detect drag-and-drop\n  var dragAndDrop = function() {\n    // There is *some* kind of drag-and-drop support in IE6-8, but I\n    // couldn't get it to work yet.\n    if (ie && ie_version < 9) { return false }\n    var div = elt('div');\n    return \"draggable\" in div || \"dragDrop\" in div\n  }();\n\n  var zwspSupported;\n  function zeroWidthElement(measure) {\n    if (zwspSupported == null) {\n      var test = elt(\"span\", \"\\u200b\");\n      removeChildrenAndAdd(measure, elt(\"span\", [test, document.createTextNode(\"x\")]));\n      if (measure.firstChild.offsetHeight != 0)\n        { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); }\n    }\n    var node = zwspSupported ? elt(\"span\", \"\\u200b\") :\n      elt(\"span\", \"\\u00a0\", null, \"display: inline-block; width: 1px; margin-right: -1px\");\n    node.setAttribute(\"cm-text\", \"\");\n    return node\n  }\n\n  // Feature-detect IE's crummy client rect reporting for bidi text\n  var badBidiRects;\n  function hasBadBidiRects(measure) {\n    if (badBidiRects != null) { return badBidiRects }\n    var txt = removeChildrenAndAdd(measure, document.createTextNode(\"A\\u062eA\"));\n    var r0 = range(txt, 0, 1).getBoundingClientRect();\n    var r1 = range(txt, 1, 2).getBoundingClientRect();\n    removeChildren(measure);\n    if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780)\n    return badBidiRects = (r1.right - r0.right < 3)\n  }\n\n  // See if \"\".split is the broken IE version, if so, provide an\n  // alternative way to split lines.\n  var splitLinesAuto = \"\\n\\nb\".split(/\\n/).length != 3 ? function (string) {\n    var pos = 0, result = [], l = string.length;\n    while (pos <= l) {\n      var nl = string.indexOf(\"\\n\", pos);\n      if (nl == -1) { nl = string.length; }\n      var line = string.slice(pos, string.charAt(nl - 1) == \"\\r\" ? nl - 1 : nl);\n      var rt = line.indexOf(\"\\r\");\n      if (rt != -1) {\n        result.push(line.slice(0, rt));\n        pos += rt + 1;\n      } else {\n        result.push(line);\n        pos = nl + 1;\n      }\n    }\n    return result\n  } : function (string) { return string.split(/\\r\\n?|\\n/); };\n\n  var hasSelection = window.getSelection ? function (te) {\n    try { return te.selectionStart != te.selectionEnd }\n    catch(e) { return false }\n  } : function (te) {\n    var range;\n    try {range = te.ownerDocument.selection.createRange();}\n    catch(e) {}\n    if (!range || range.parentElement() != te) { return false }\n    return range.compareEndPoints(\"StartToEnd\", range) != 0\n  };\n\n  var hasCopyEvent = (function () {\n    var e = elt(\"div\");\n    if (\"oncopy\" in e) { return true }\n    e.setAttribute(\"oncopy\", \"return;\");\n    return typeof e.oncopy == \"function\"\n  })();\n\n  var badZoomedRects = null;\n  function hasBadZoomedRects(measure) {\n    if (badZoomedRects != null) { return badZoomedRects }\n    var node = removeChildrenAndAdd(measure, elt(\"span\", \"x\"));\n    var normal = node.getBoundingClientRect();\n    var fromRange = range(node, 0, 1).getBoundingClientRect();\n    return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1\n  }\n\n  // Known modes, by name and by MIME\n  var modes = {}, mimeModes = {};\n\n  // Extra arguments are stored as the mode's dependencies, which is\n  // used by (legacy) mechanisms like loadmode.js to automatically\n  // load a mode. (Preferred mechanism is the require/define calls.)\n  function defineMode(name, mode) {\n    if (arguments.length > 2)\n      { mode.dependencies = Array.prototype.slice.call(arguments, 2); }\n    modes[name] = mode;\n  }\n\n  function defineMIME(mime, spec) {\n    mimeModes[mime] = spec;\n  }\n\n  // Given a MIME type, a {name, ...options} config object, or a name\n  // string, return a mode config object.\n  function resolveMode(spec) {\n    if (typeof spec == \"string\" && mimeModes.hasOwnProperty(spec)) {\n      spec = mimeModes[spec];\n    } else if (spec && typeof spec.name == \"string\" && mimeModes.hasOwnProperty(spec.name)) {\n      var found = mimeModes[spec.name];\n      if (typeof found == \"string\") { found = {name: found}; }\n      spec = createObj(found, spec);\n      spec.name = found.name;\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+xml$/.test(spec)) {\n      return resolveMode(\"application/xml\")\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+json$/.test(spec)) {\n      return resolveMode(\"application/json\")\n    }\n    if (typeof spec == \"string\") { return {name: spec} }\n    else { return spec || {name: \"null\"} }\n  }\n\n  // Given a mode spec (anything that resolveMode accepts), find and\n  // initialize an actual mode object.\n  function getMode(options, spec) {\n    spec = resolveMode(spec);\n    var mfactory = modes[spec.name];\n    if (!mfactory) { return getMode(options, \"text/plain\") }\n    var modeObj = mfactory(options, spec);\n    if (modeExtensions.hasOwnProperty(spec.name)) {\n      var exts = modeExtensions[spec.name];\n      for (var prop in exts) {\n        if (!exts.hasOwnProperty(prop)) { continue }\n        if (modeObj.hasOwnProperty(prop)) { modeObj[\"_\" + prop] = modeObj[prop]; }\n        modeObj[prop] = exts[prop];\n      }\n    }\n    modeObj.name = spec.name;\n    if (spec.helperType) { modeObj.helperType = spec.helperType; }\n    if (spec.modeProps) { for (var prop$1 in spec.modeProps)\n      { modeObj[prop$1] = spec.modeProps[prop$1]; } }\n\n    return modeObj\n  }\n\n  // This can be used to attach properties to mode objects from\n  // outside the actual mode definition.\n  var modeExtensions = {};\n  function extendMode(mode, properties) {\n    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});\n    copyObj(properties, exts);\n  }\n\n  function copyState(mode, state) {\n    if (state === true) { return state }\n    if (mode.copyState) { return mode.copyState(state) }\n    var nstate = {};\n    for (var n in state) {\n      var val = state[n];\n      if (val instanceof Array) { val = val.concat([]); }\n      nstate[n] = val;\n    }\n    return nstate\n  }\n\n  // Given a mode and a state (for that mode), find the inner mode and\n  // state at the position that the state refers to.\n  function innerMode(mode, state) {\n    var info;\n    while (mode.innerMode) {\n      info = mode.innerMode(state);\n      if (!info || info.mode == mode) { break }\n      state = info.state;\n      mode = info.mode;\n    }\n    return info || {mode: mode, state: state}\n  }\n\n  function startState(mode, a1, a2) {\n    return mode.startState ? mode.startState(a1, a2) : true\n  }\n\n  // STRING STREAM\n\n  // Fed to the mode parsers, provides helper functions to make\n  // parsers more succinct.\n\n  var StringStream = function(string, tabSize, lineOracle) {\n    this.pos = this.start = 0;\n    this.string = string;\n    this.tabSize = tabSize || 8;\n    this.lastColumnPos = this.lastColumnValue = 0;\n    this.lineStart = 0;\n    this.lineOracle = lineOracle;\n  };\n\n  StringStream.prototype.eol = function () {return this.pos >= this.string.length};\n  StringStream.prototype.sol = function () {return this.pos == this.lineStart};\n  StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined};\n  StringStream.prototype.next = function () {\n    if (this.pos < this.string.length)\n      { return this.string.charAt(this.pos++) }\n  };\n  StringStream.prototype.eat = function (match) {\n    var ch = this.string.charAt(this.pos);\n    var ok;\n    if (typeof match == \"string\") { ok = ch == match; }\n    else { ok = ch && (match.test ? match.test(ch) : match(ch)); }\n    if (ok) {++this.pos; return ch}\n  };\n  StringStream.prototype.eatWhile = function (match) {\n    var start = this.pos;\n    while (this.eat(match)){}\n    return this.pos > start\n  };\n  StringStream.prototype.eatSpace = function () {\n    var start = this.pos;\n    while (/[\\s\\u00a0]/.test(this.string.charAt(this.pos))) { ++this.pos; }\n    return this.pos > start\n  };\n  StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;};\n  StringStream.prototype.skipTo = function (ch) {\n    var found = this.string.indexOf(ch, this.pos);\n    if (found > -1) {this.pos = found; return true}\n  };\n  StringStream.prototype.backUp = function (n) {this.pos -= n;};\n  StringStream.prototype.column = function () {\n    if (this.lastColumnPos < this.start) {\n      this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);\n      this.lastColumnPos = this.start;\n    }\n    return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)\n  };\n  StringStream.prototype.indentation = function () {\n    return countColumn(this.string, null, this.tabSize) -\n      (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)\n  };\n  StringStream.prototype.match = function (pattern, consume, caseInsensitive) {\n    if (typeof pattern == \"string\") {\n      var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; };\n      var substr = this.string.substr(this.pos, pattern.length);\n      if (cased(substr) == cased(pattern)) {\n        if (consume !== false) { this.pos += pattern.length; }\n        return true\n      }\n    } else {\n      var match = this.string.slice(this.pos).match(pattern);\n      if (match && match.index > 0) { return null }\n      if (match && consume !== false) { this.pos += match[0].length; }\n      return match\n    }\n  };\n  StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};\n  StringStream.prototype.hideFirstChars = function (n, inner) {\n    this.lineStart += n;\n    try { return inner() }\n    finally { this.lineStart -= n; }\n  };\n  StringStream.prototype.lookAhead = function (n) {\n    var oracle = this.lineOracle;\n    return oracle && oracle.lookAhead(n)\n  };\n  StringStream.prototype.baseToken = function () {\n    var oracle = this.lineOracle;\n    return oracle && oracle.baseToken(this.pos)\n  };\n\n  // Find the line object corresponding to the given line number.\n  function getLine(doc, n) {\n    n -= doc.first;\n    if (n < 0 || n >= doc.size) { throw new Error(\"There is no line \" + (n + doc.first) + \" in the document.\") }\n    var chunk = doc;\n    while (!chunk.lines) {\n      for (var i = 0;; ++i) {\n        var child = chunk.children[i], sz = child.chunkSize();\n        if (n < sz) { chunk = child; break }\n        n -= sz;\n      }\n    }\n    return chunk.lines[n]\n  }\n\n  // Get the part of a document between two positions, as an array of\n  // strings.\n  function getBetween(doc, start, end) {\n    var out = [], n = start.line;\n    doc.iter(start.line, end.line + 1, function (line) {\n      var text = line.text;\n      if (n == end.line) { text = text.slice(0, end.ch); }\n      if (n == start.line) { text = text.slice(start.ch); }\n      out.push(text);\n      ++n;\n    });\n    return out\n  }\n  // Get the lines between from and to, as array of strings.\n  function getLines(doc, from, to) {\n    var out = [];\n    doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value\n    return out\n  }\n\n  // Update the height of a line, propagating the height change\n  // upwards to parent nodes.\n  function updateLineHeight(line, height) {\n    var diff = height - line.height;\n    if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } }\n  }\n\n  // Given a line object, find its line number by walking up through\n  // its parent links.\n  function lineNo(line) {\n    if (line.parent == null) { return null }\n    var cur = line.parent, no = indexOf(cur.lines, line);\n    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {\n      for (var i = 0;; ++i) {\n        if (chunk.children[i] == cur) { break }\n        no += chunk.children[i].chunkSize();\n      }\n    }\n    return no + cur.first\n  }\n\n  // Find the line at the given vertical position, using the height\n  // information in the document tree.\n  function lineAtHeight(chunk, h) {\n    var n = chunk.first;\n    outer: do {\n      for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) {\n        var child = chunk.children[i$1], ch = child.height;\n        if (h < ch) { chunk = child; continue outer }\n        h -= ch;\n        n += child.chunkSize();\n      }\n      return n\n    } while (!chunk.lines)\n    var i = 0;\n    for (; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i], lh = line.height;\n      if (h < lh) { break }\n      h -= lh;\n    }\n    return n + i\n  }\n\n  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}\n\n  function lineNumberFor(options, i) {\n    return String(options.lineNumberFormatter(i + options.firstLineNumber))\n  }\n\n  // A Pos instance represents a position within the text.\n  function Pos(line, ch, sticky) {\n    if ( sticky === void 0 ) sticky = null;\n\n    if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }\n    this.line = line;\n    this.ch = ch;\n    this.sticky = sticky;\n  }\n\n  // Compare two positions, return 0 if they are the same, a negative\n  // number when a is less, and a positive number otherwise.\n  function cmp(a, b) { return a.line - b.line || a.ch - b.ch }\n\n  function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }\n\n  function copyPos(x) {return Pos(x.line, x.ch)}\n  function maxPos(a, b) { return cmp(a, b) < 0 ? b : a }\n  function minPos(a, b) { return cmp(a, b) < 0 ? a : b }\n\n  // Most of the external API clips given positions to make sure they\n  // actually exist within the document.\n  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))}\n  function clipPos(doc, pos) {\n    if (pos.line < doc.first) { return Pos(doc.first, 0) }\n    var last = doc.first + doc.size - 1;\n    if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) }\n    return clipToLen(pos, getLine(doc, pos.line).text.length)\n  }\n  function clipToLen(pos, linelen) {\n    var ch = pos.ch;\n    if (ch == null || ch > linelen) { return Pos(pos.line, linelen) }\n    else if (ch < 0) { return Pos(pos.line, 0) }\n    else { return pos }\n  }\n  function clipPosArray(doc, array) {\n    var out = [];\n    for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); }\n    return out\n  }\n\n  var SavedContext = function(state, lookAhead) {\n    this.state = state;\n    this.lookAhead = lookAhead;\n  };\n\n  var Context = function(doc, state, line, lookAhead) {\n    this.state = state;\n    this.doc = doc;\n    this.line = line;\n    this.maxLookAhead = lookAhead || 0;\n    this.baseTokens = null;\n    this.baseTokenPos = 1;\n  };\n\n  Context.prototype.lookAhead = function (n) {\n    var line = this.doc.getLine(this.line + n);\n    if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; }\n    return line\n  };\n\n  Context.prototype.baseToken = function (n) {\n    if (!this.baseTokens) { return null }\n    while (this.baseTokens[this.baseTokenPos] <= n)\n      { this.baseTokenPos += 2; }\n    var type = this.baseTokens[this.baseTokenPos + 1];\n    return {type: type && type.replace(/( |^)overlay .*/, \"\"),\n            size: this.baseTokens[this.baseTokenPos] - n}\n  };\n\n  Context.prototype.nextLine = function () {\n    this.line++;\n    if (this.maxLookAhead > 0) { this.maxLookAhead--; }\n  };\n\n  Context.fromSaved = function (doc, saved, line) {\n    if (saved instanceof SavedContext)\n      { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }\n    else\n      { return new Context(doc, copyState(doc.mode, saved), line) }\n  };\n\n  Context.prototype.save = function (copy) {\n    var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state;\n    return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state\n  };\n\n\n  // Compute a style array (an array starting with a mode generation\n  // -- for invalidation -- followed by pairs of end positions and\n  // style strings), which is used to highlight the tokens on the\n  // line.\n  function highlightLine(cm, line, context, forceToEnd) {\n    // A styles array always starts with a number identifying the\n    // mode/overlays that it is based on (for easy invalidation).\n    var st = [cm.state.modeGen], lineClasses = {};\n    // Compute the base array of styles\n    runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },\n            lineClasses, forceToEnd);\n    var state = context.state;\n\n    // Run overlays, adjust style array.\n    var loop = function ( o ) {\n      context.baseTokens = st;\n      var overlay = cm.state.overlays[o], i = 1, at = 0;\n      context.state = true;\n      runMode(cm, line.text, overlay.mode, context, function (end, style) {\n        var start = i;\n        // Ensure there's a token end at the current position, and that i points at it\n        while (at < end) {\n          var i_end = st[i];\n          if (i_end > end)\n            { st.splice(i, 1, end, st[i+1], i_end); }\n          i += 2;\n          at = Math.min(end, i_end);\n        }\n        if (!style) { return }\n        if (overlay.opaque) {\n          st.splice(start, i - start, end, \"overlay \" + style);\n          i = start + 2;\n        } else {\n          for (; start < i; start += 2) {\n            var cur = st[start+1];\n            st[start+1] = (cur ? cur + \" \" : \"\") + \"overlay \" + style;\n          }\n        }\n      }, lineClasses);\n      context.state = state;\n      context.baseTokens = null;\n      context.baseTokenPos = 1;\n    };\n\n    for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );\n\n    return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}\n  }\n\n  function getLineStyles(cm, line, updateFrontier) {\n    if (!line.styles || line.styles[0] != cm.state.modeGen) {\n      var context = getContextBefore(cm, lineNo(line));\n      var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state);\n      var result = highlightLine(cm, line, context);\n      if (resetState) { context.state = resetState; }\n      line.stateAfter = context.save(!resetState);\n      line.styles = result.styles;\n      if (result.classes) { line.styleClasses = result.classes; }\n      else if (line.styleClasses) { line.styleClasses = null; }\n      if (updateFrontier === cm.doc.highlightFrontier)\n        { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); }\n    }\n    return line.styles\n  }\n\n  function getContextBefore(cm, n, precise) {\n    var doc = cm.doc, display = cm.display;\n    if (!doc.mode.startState) { return new Context(doc, true, n) }\n    var start = findStartLine(cm, n, precise);\n    var saved = start > doc.first && getLine(doc, start - 1).stateAfter;\n    var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start);\n\n    doc.iter(start, n, function (line) {\n      processLine(cm, line.text, context);\n      var pos = context.line;\n      line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null;\n      context.nextLine();\n    });\n    if (precise) { doc.modeFrontier = context.line; }\n    return context\n  }\n\n  // Lightweight form of highlight -- proceed over this line and\n  // update state, but don't save a style array. Used for lines that\n  // aren't currently visible.\n  function processLine(cm, text, context, startAt) {\n    var mode = cm.doc.mode;\n    var stream = new StringStream(text, cm.options.tabSize, context);\n    stream.start = stream.pos = startAt || 0;\n    if (text == \"\") { callBlankLine(mode, context.state); }\n    while (!stream.eol()) {\n      readToken(mode, stream, context.state);\n      stream.start = stream.pos;\n    }\n  }\n\n  function callBlankLine(mode, state) {\n    if (mode.blankLine) { return mode.blankLine(state) }\n    if (!mode.innerMode) { return }\n    var inner = innerMode(mode, state);\n    if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }\n  }\n\n  function readToken(mode, stream, state, inner) {\n    for (var i = 0; i < 10; i++) {\n      if (inner) { inner[0] = innerMode(mode, state).mode; }\n      var style = mode.token(stream, state);\n      if (stream.pos > stream.start) { return style }\n    }\n    throw new Error(\"Mode \" + mode.name + \" failed to advance stream.\")\n  }\n\n  var Token = function(stream, type, state) {\n    this.start = stream.start; this.end = stream.pos;\n    this.string = stream.current();\n    this.type = type || null;\n    this.state = state;\n  };\n\n  // Utility for getTokenAt and getLineTokens\n  function takeToken(cm, pos, precise, asArray) {\n    var doc = cm.doc, mode = doc.mode, style;\n    pos = clipPos(doc, pos);\n    var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise);\n    var stream = new StringStream(line.text, cm.options.tabSize, context), tokens;\n    if (asArray) { tokens = []; }\n    while ((asArray || stream.pos < pos.ch) && !stream.eol()) {\n      stream.start = stream.pos;\n      style = readToken(mode, stream, context.state);\n      if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); }\n    }\n    return asArray ? tokens : new Token(stream, style, context.state)\n  }\n\n  function extractLineClasses(type, output) {\n    if (type) { for (;;) {\n      var lineClass = type.match(/(?:^|\\s+)line-(background-)?(\\S+)/);\n      if (!lineClass) { break }\n      type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);\n      var prop = lineClass[1] ? \"bgClass\" : \"textClass\";\n      if (output[prop] == null)\n        { output[prop] = lineClass[2]; }\n      else if (!(new RegExp(\"(?:^|\\\\s)\" + lineClass[2] + \"(?:$|\\\\s)\")).test(output[prop]))\n        { output[prop] += \" \" + lineClass[2]; }\n    } }\n    return type\n  }\n\n  // Run the given mode's parser over a line, calling f for each token.\n  function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) {\n    var flattenSpans = mode.flattenSpans;\n    if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; }\n    var curStart = 0, curStyle = null;\n    var stream = new StringStream(text, cm.options.tabSize, context), style;\n    var inner = cm.options.addModeClass && [null];\n    if (text == \"\") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); }\n    while (!stream.eol()) {\n      if (stream.pos > cm.options.maxHighlightLength) {\n        flattenSpans = false;\n        if (forceToEnd) { processLine(cm, text, context, stream.pos); }\n        stream.pos = text.length;\n        style = null;\n      } else {\n        style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses);\n      }\n      if (inner) {\n        var mName = inner[0].name;\n        if (mName) { style = \"m-\" + (style ? mName + \" \" + style : mName); }\n      }\n      if (!flattenSpans || curStyle != style) {\n        while (curStart < stream.start) {\n          curStart = Math.min(stream.start, curStart + 5000);\n          f(curStart, curStyle);\n        }\n        curStyle = style;\n      }\n      stream.start = stream.pos;\n    }\n    while (curStart < stream.pos) {\n      // Webkit seems to refuse to render text nodes longer than 57444\n      // characters, and returns inaccurate measurements in nodes\n      // starting around 5000 chars.\n      var pos = Math.min(stream.pos, curStart + 5000);\n      f(pos, curStyle);\n      curStart = pos;\n    }\n  }\n\n  // Finds the line to start with when starting a parse. Tries to\n  // find a line with a stateAfter, so that it can start with a\n  // valid state. If that fails, it returns the line with the\n  // smallest indentation, which tends to need the least context to\n  // parse correctly.\n  function findStartLine(cm, n, precise) {\n    var minindent, minline, doc = cm.doc;\n    var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);\n    for (var search = n; search > lim; --search) {\n      if (search <= doc.first) { return doc.first }\n      var line = getLine(doc, search - 1), after = line.stateAfter;\n      if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier))\n        { return search }\n      var indented = countColumn(line.text, null, cm.options.tabSize);\n      if (minline == null || minindent > indented) {\n        minline = search - 1;\n        minindent = indented;\n      }\n    }\n    return minline\n  }\n\n  function retreatFrontier(doc, n) {\n    doc.modeFrontier = Math.min(doc.modeFrontier, n);\n    if (doc.highlightFrontier < n - 10) { return }\n    var start = doc.first;\n    for (var line = n - 1; line > start; line--) {\n      var saved = getLine(doc, line).stateAfter;\n      // change is on 3\n      // state on line 1 looked ahead 2 -- so saw 3\n      // test 1 + 2 < 3 should cover this\n      if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) {\n        start = line + 1;\n        break\n      }\n    }\n    doc.highlightFrontier = Math.min(doc.highlightFrontier, start);\n  }\n\n  // Optimize some code when these features are not used.\n  var sawReadOnlySpans = false, sawCollapsedSpans = false;\n\n  function seeReadOnlySpans() {\n    sawReadOnlySpans = true;\n  }\n\n  function seeCollapsedSpans() {\n    sawCollapsedSpans = true;\n  }\n\n  // TEXTMARKER SPANS\n\n  function MarkedSpan(marker, from, to) {\n    this.marker = marker;\n    this.from = from; this.to = to;\n  }\n\n  // Search an array of spans for a span matching the given marker.\n  function getMarkedSpanFor(spans, marker) {\n    if (spans) { for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.marker == marker) { return span }\n    } }\n  }\n  // Remove a span from an array, returning undefined if no spans are\n  // left (we don't store arrays for lines without spans).\n  function removeMarkedSpan(spans, span) {\n    var r;\n    for (var i = 0; i < spans.length; ++i)\n      { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } }\n    return r\n  }\n  // Add a span to a line.\n  function addMarkedSpan(line, span) {\n    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];\n    span.marker.attachLine(line);\n  }\n\n  // Used for the algorithm that adjusts markers for a change in the\n  // document. These functions cut an array of spans at a given\n  // character position, returning an array of remaining chunks (or\n  // undefined if nothing remains).\n  function markedSpansBefore(old, startCh, isInsert) {\n    var nw;\n    if (old) { for (var i = 0; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);\n      if (startsBefore || span.from == startCh && marker.type == \"bookmark\" && (!isInsert || !span.marker.insertLeft)) {\n        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh)\n        ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));\n      }\n    } }\n    return nw\n  }\n  function markedSpansAfter(old, endCh, isInsert) {\n    var nw;\n    if (old) { for (var i = 0; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);\n      if (endsAfter || span.from == endCh && marker.type == \"bookmark\" && (!isInsert || span.marker.insertLeft)) {\n        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh)\n        ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,\n                                              span.to == null ? null : span.to - endCh));\n      }\n    } }\n    return nw\n  }\n\n  // Given a change object, compute the new set of marker spans that\n  // cover the line in which the change took place. Removes spans\n  // entirely within the change, reconnects spans belonging to the\n  // same marker that appear on both sides of the change, and cuts off\n  // spans partially within the change. Returns an array of span\n  // arrays with one element for each line in (after) the change.\n  function stretchSpansOverChange(doc, change) {\n    if (change.full) { return null }\n    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;\n    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;\n    if (!oldFirst && !oldLast) { return null }\n\n    var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;\n    // Get the spans that 'stick out' on both sides\n    var first = markedSpansBefore(oldFirst, startCh, isInsert);\n    var last = markedSpansAfter(oldLast, endCh, isInsert);\n\n    // Next, merge those two ends\n    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);\n    if (first) {\n      // Fix up .to properties of first\n      for (var i = 0; i < first.length; ++i) {\n        var span = first[i];\n        if (span.to == null) {\n          var found = getMarkedSpanFor(last, span.marker);\n          if (!found) { span.to = startCh; }\n          else if (sameLine) { span.to = found.to == null ? null : found.to + offset; }\n        }\n      }\n    }\n    if (last) {\n      // Fix up .from in last (or move them into first in case of sameLine)\n      for (var i$1 = 0; i$1 < last.length; ++i$1) {\n        var span$1 = last[i$1];\n        if (span$1.to != null) { span$1.to += offset; }\n        if (span$1.from == null) {\n          var found$1 = getMarkedSpanFor(first, span$1.marker);\n          if (!found$1) {\n            span$1.from = offset;\n            if (sameLine) { (first || (first = [])).push(span$1); }\n          }\n        } else {\n          span$1.from += offset;\n          if (sameLine) { (first || (first = [])).push(span$1); }\n        }\n      }\n    }\n    // Make sure we didn't create any zero-length spans\n    if (first) { first = clearEmptySpans(first); }\n    if (last && last != first) { last = clearEmptySpans(last); }\n\n    var newMarkers = [first];\n    if (!sameLine) {\n      // Fill gap with whole-line-spans\n      var gap = change.text.length - 2, gapMarkers;\n      if (gap > 0 && first)\n        { for (var i$2 = 0; i$2 < first.length; ++i$2)\n          { if (first[i$2].to == null)\n            { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } }\n      for (var i$3 = 0; i$3 < gap; ++i$3)\n        { newMarkers.push(gapMarkers); }\n      newMarkers.push(last);\n    }\n    return newMarkers\n  }\n\n  // Remove spans that are empty and don't have a clearWhenEmpty\n  // option of false.\n  function clearEmptySpans(spans) {\n    for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)\n        { spans.splice(i--, 1); }\n    }\n    if (!spans.length) { return null }\n    return spans\n  }\n\n  // Used to 'clip' out readOnly ranges when making a change.\n  function removeReadOnlyRanges(doc, from, to) {\n    var markers = null;\n    doc.iter(from.line, to.line + 1, function (line) {\n      if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {\n        var mark = line.markedSpans[i].marker;\n        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))\n          { (markers || (markers = [])).push(mark); }\n      } }\n    });\n    if (!markers) { return null }\n    var parts = [{from: from, to: to}];\n    for (var i = 0; i < markers.length; ++i) {\n      var mk = markers[i], m = mk.find(0);\n      for (var j = 0; j < parts.length; ++j) {\n        var p = parts[j];\n        if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }\n        var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);\n        if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)\n          { newParts.push({from: p.from, to: m.from}); }\n        if (dto > 0 || !mk.inclusiveRight && !dto)\n          { newParts.push({from: m.to, to: p.to}); }\n        parts.splice.apply(parts, newParts);\n        j += newParts.length - 3;\n      }\n    }\n    return parts\n  }\n\n  // Connect or disconnect spans from a line.\n  function detachMarkedSpans(line) {\n    var spans = line.markedSpans;\n    if (!spans) { return }\n    for (var i = 0; i < spans.length; ++i)\n      { spans[i].marker.detachLine(line); }\n    line.markedSpans = null;\n  }\n  function attachMarkedSpans(line, spans) {\n    if (!spans) { return }\n    for (var i = 0; i < spans.length; ++i)\n      { spans[i].marker.attachLine(line); }\n    line.markedSpans = spans;\n  }\n\n  // Helpers used when computing which overlapping collapsed span\n  // counts as the larger one.\n  function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }\n  function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }\n\n  // Returns a number indicating which of two overlapping collapsed\n  // spans is larger (and thus includes the other). Falls back to\n  // comparing ids when the spans cover exactly the same range.\n  function compareCollapsedMarkers(a, b) {\n    var lenDiff = a.lines.length - b.lines.length;\n    if (lenDiff != 0) { return lenDiff }\n    var aPos = a.find(), bPos = b.find();\n    var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);\n    if (fromCmp) { return -fromCmp }\n    var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);\n    if (toCmp) { return toCmp }\n    return b.id - a.id\n  }\n\n  // Find out whether a line ends or starts in a collapsed span. If\n  // so, return the marker for that span.\n  function collapsedSpanAtSide(line, start) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&\n          (!found || compareCollapsedMarkers(found, sp.marker) < 0))\n        { found = sp.marker; }\n    } }\n    return found\n  }\n  function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }\n  function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }\n\n  function collapsedSpanAround(line, ch) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) { for (var i = 0; i < sps.length; ++i) {\n      var sp = sps[i];\n      if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) &&\n          (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; }\n    } }\n    return found\n  }\n\n  // Test whether there exists a collapsed span that partially\n  // overlaps (covers the start or end, but not both) of a new span.\n  // Such overlap is not allowed.\n  function conflictingCollapsedRange(doc, lineNo, from, to, marker) {\n    var line = getLine(doc, lineNo);\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) { for (var i = 0; i < sps.length; ++i) {\n      var sp = sps[i];\n      if (!sp.marker.collapsed) { continue }\n      var found = sp.marker.find(0);\n      var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);\n      var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);\n      if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }\n      if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||\n          fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))\n        { return true }\n    } }\n  }\n\n  // A visual line is a line as drawn on the screen. Folding, for\n  // example, can cause multiple logical lines to appear on the same\n  // visual line. This finds the start of the visual line that the\n  // given line is part of (usually that is the line itself).\n  function visualLine(line) {\n    var merged;\n    while (merged = collapsedSpanAtStart(line))\n      { line = merged.find(-1, true).line; }\n    return line\n  }\n\n  function visualLineEnd(line) {\n    var merged;\n    while (merged = collapsedSpanAtEnd(line))\n      { line = merged.find(1, true).line; }\n    return line\n  }\n\n  // Returns an array of logical lines that continue the visual line\n  // started by the argument, or undefined if there are no such lines.\n  function visualLineContinued(line) {\n    var merged, lines;\n    while (merged = collapsedSpanAtEnd(line)) {\n      line = merged.find(1, true).line\n      ;(lines || (lines = [])).push(line);\n    }\n    return lines\n  }\n\n  // Get the line number of the start of the visual line that the\n  // given line number is part of.\n  function visualLineNo(doc, lineN) {\n    var line = getLine(doc, lineN), vis = visualLine(line);\n    if (line == vis) { return lineN }\n    return lineNo(vis)\n  }\n\n  // Get the line number of the start of the next visual line after\n  // the given line.\n  function visualLineEndNo(doc, lineN) {\n    if (lineN > doc.lastLine()) { return lineN }\n    var line = getLine(doc, lineN), merged;\n    if (!lineIsHidden(doc, line)) { return lineN }\n    while (merged = collapsedSpanAtEnd(line))\n      { line = merged.find(1, true).line; }\n    return lineNo(line) + 1\n  }\n\n  // Compute whether a line is hidden. Lines count as hidden when they\n  // are part of a visual line that starts with another line, or when\n  // they are entirely covered by collapsed, non-widget span.\n  function lineIsHidden(doc, line) {\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (!sp.marker.collapsed) { continue }\n      if (sp.from == null) { return true }\n      if (sp.marker.widgetNode) { continue }\n      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))\n        { return true }\n    } }\n  }\n  function lineIsHiddenInner(doc, line, span) {\n    if (span.to == null) {\n      var end = span.marker.find(1, true);\n      return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker))\n    }\n    if (span.marker.inclusiveRight && span.to == line.text.length)\n      { return true }\n    for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {\n      sp = line.markedSpans[i];\n      if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&\n          (sp.to == null || sp.to != span.from) &&\n          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&\n          lineIsHiddenInner(doc, line, sp)) { return true }\n    }\n  }\n\n  // Find the height above the given line.\n  function heightAtLine(lineObj) {\n    lineObj = visualLine(lineObj);\n\n    var h = 0, chunk = lineObj.parent;\n    for (var i = 0; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i];\n      if (line == lineObj) { break }\n      else { h += line.height; }\n    }\n    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {\n      for (var i$1 = 0; i$1 < p.children.length; ++i$1) {\n        var cur = p.children[i$1];\n        if (cur == chunk) { break }\n        else { h += cur.height; }\n      }\n    }\n    return h\n  }\n\n  // Compute the character length of a line, taking into account\n  // collapsed ranges (see markText) that might hide parts, and join\n  // other lines onto it.\n  function lineLength(line) {\n    if (line.height == 0) { return 0 }\n    var len = line.text.length, merged, cur = line;\n    while (merged = collapsedSpanAtStart(cur)) {\n      var found = merged.find(0, true);\n      cur = found.from.line;\n      len += found.from.ch - found.to.ch;\n    }\n    cur = line;\n    while (merged = collapsedSpanAtEnd(cur)) {\n      var found$1 = merged.find(0, true);\n      len -= cur.text.length - found$1.from.ch;\n      cur = found$1.to.line;\n      len += cur.text.length - found$1.to.ch;\n    }\n    return len\n  }\n\n  // Find the longest line in the document.\n  function findMaxLine(cm) {\n    var d = cm.display, doc = cm.doc;\n    d.maxLine = getLine(doc, doc.first);\n    d.maxLineLength = lineLength(d.maxLine);\n    d.maxLineChanged = true;\n    doc.iter(function (line) {\n      var len = lineLength(line);\n      if (len > d.maxLineLength) {\n        d.maxLineLength = len;\n        d.maxLine = line;\n      }\n    });\n  }\n\n  // LINE DATA STRUCTURE\n\n  // Line objects. These hold state related to a line, including\n  // highlighting info (the styles array).\n  var Line = function(text, markedSpans, estimateHeight) {\n    this.text = text;\n    attachMarkedSpans(this, markedSpans);\n    this.height = estimateHeight ? estimateHeight(this) : 1;\n  };\n\n  Line.prototype.lineNo = function () { return lineNo(this) };\n  eventMixin(Line);\n\n  // Change the content (text, markers) of a line. Automatically\n  // invalidates cached information and tries to re-estimate the\n  // line's height.\n  function updateLine(line, text, markedSpans, estimateHeight) {\n    line.text = text;\n    if (line.stateAfter) { line.stateAfter = null; }\n    if (line.styles) { line.styles = null; }\n    if (line.order != null) { line.order = null; }\n    detachMarkedSpans(line);\n    attachMarkedSpans(line, markedSpans);\n    var estHeight = estimateHeight ? estimateHeight(line) : 1;\n    if (estHeight != line.height) { updateLineHeight(line, estHeight); }\n  }\n\n  // Detach a line from the document tree and its markers.\n  function cleanUpLine(line) {\n    line.parent = null;\n    detachMarkedSpans(line);\n  }\n\n  // Convert a style as returned by a mode (either null, or a string\n  // containing one or more styles) to a CSS style. This is cached,\n  // and also looks for line-wide styles.\n  var styleToClassCache = {}, styleToClassCacheWithMode = {};\n  function interpretTokenStyle(style, options) {\n    if (!style || /^\\s*$/.test(style)) { return null }\n    var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;\n    return cache[style] ||\n      (cache[style] = style.replace(/\\S+/g, \"cm-$&\"))\n  }\n\n  // Render the DOM representation of the text of a line. Also builds\n  // up a 'line map', which points at the DOM nodes that represent\n  // specific stretches of text, and is used by the measuring code.\n  // The returned object contains the DOM node, this map, and\n  // information about line-wide styles that were set by the mode.\n  function buildLineContent(cm, lineView) {\n    // The padding-right forces the element to have a 'border', which\n    // is needed on Webkit to be able to get line-level bounding\n    // rectangles for it (in measureChar).\n    var content = eltP(\"span\", null, null, webkit ? \"padding-right: .1px\" : null);\n    var builder = {pre: eltP(\"pre\", [content], \"CodeMirror-line\"), content: content,\n                   col: 0, pos: 0, cm: cm,\n                   trailingSpace: false,\n                   splitSpaces: cm.getOption(\"lineWrapping\")};\n    lineView.measure = {};\n\n    // Iterate over the logical lines that make up this visual line.\n    for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {\n      var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0);\n      builder.pos = 0;\n      builder.addToken = buildToken;\n      // Optionally wire in some hacks into the token-rendering\n      // algorithm, to deal with browser quirks.\n      if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))\n        { builder.addToken = buildTokenBadBidi(builder.addToken, order); }\n      builder.map = [];\n      var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);\n      insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));\n      if (line.styleClasses) {\n        if (line.styleClasses.bgClass)\n          { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || \"\"); }\n        if (line.styleClasses.textClass)\n          { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || \"\"); }\n      }\n\n      // Ensure at least a single node is present, for measuring.\n      if (builder.map.length == 0)\n        { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); }\n\n      // Store the map and a cache object for the current logical line\n      if (i == 0) {\n        lineView.measure.map = builder.map;\n        lineView.measure.cache = {};\n      } else {\n  (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)\n        ;(lineView.measure.caches || (lineView.measure.caches = [])).push({});\n      }\n    }\n\n    // See issue #2901\n    if (webkit) {\n      var last = builder.content.lastChild;\n      if (/\\bcm-tab\\b/.test(last.className) || (last.querySelector && last.querySelector(\".cm-tab\")))\n        { builder.content.className = \"cm-tab-wrap-hack\"; }\n    }\n\n    signal(cm, \"renderLine\", cm, lineView.line, builder.pre);\n    if (builder.pre.className)\n      { builder.textClass = joinClasses(builder.pre.className, builder.textClass || \"\"); }\n\n    return builder\n  }\n\n  function defaultSpecialCharPlaceholder(ch) {\n    var token = elt(\"span\", \"\\u2022\", \"cm-invalidchar\");\n    token.title = \"\\\\u\" + ch.charCodeAt(0).toString(16);\n    token.setAttribute(\"aria-label\", token.title);\n    return token\n  }\n\n  // Build up the DOM representation for a single token, and add it to\n  // the line map. Takes care to render special characters separately.\n  function buildToken(builder, text, style, startStyle, endStyle, css, attributes) {\n    if (!text) { return }\n    var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text;\n    var special = builder.cm.state.specialChars, mustWrap = false;\n    var content;\n    if (!special.test(text)) {\n      builder.col += text.length;\n      content = document.createTextNode(displayText);\n      builder.map.push(builder.pos, builder.pos + text.length, content);\n      if (ie && ie_version < 9) { mustWrap = true; }\n      builder.pos += text.length;\n    } else {\n      content = document.createDocumentFragment();\n      var pos = 0;\n      while (true) {\n        special.lastIndex = pos;\n        var m = special.exec(text);\n        var skipped = m ? m.index - pos : text.length - pos;\n        if (skipped) {\n          var txt = document.createTextNode(displayText.slice(pos, pos + skipped));\n          if (ie && ie_version < 9) { content.appendChild(elt(\"span\", [txt])); }\n          else { content.appendChild(txt); }\n          builder.map.push(builder.pos, builder.pos + skipped, txt);\n          builder.col += skipped;\n          builder.pos += skipped;\n        }\n        if (!m) { break }\n        pos += skipped + 1;\n        var txt$1 = (void 0);\n        if (m[0] == \"\\t\") {\n          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;\n          txt$1 = content.appendChild(elt(\"span\", spaceStr(tabWidth), \"cm-tab\"));\n          txt$1.setAttribute(\"role\", \"presentation\");\n          txt$1.setAttribute(\"cm-text\", \"\\t\");\n          builder.col += tabWidth;\n        } else if (m[0] == \"\\r\" || m[0] == \"\\n\") {\n          txt$1 = content.appendChild(elt(\"span\", m[0] == \"\\r\" ? \"\\u240d\" : \"\\u2424\", \"cm-invalidchar\"));\n          txt$1.setAttribute(\"cm-text\", m[0]);\n          builder.col += 1;\n        } else {\n          txt$1 = builder.cm.options.specialCharPlaceholder(m[0]);\n          txt$1.setAttribute(\"cm-text\", m[0]);\n          if (ie && ie_version < 9) { content.appendChild(elt(\"span\", [txt$1])); }\n          else { content.appendChild(txt$1); }\n          builder.col += 1;\n        }\n        builder.map.push(builder.pos, builder.pos + 1, txt$1);\n        builder.pos++;\n      }\n    }\n    builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32;\n    if (style || startStyle || endStyle || mustWrap || css) {\n      var fullStyle = style || \"\";\n      if (startStyle) { fullStyle += startStyle; }\n      if (endStyle) { fullStyle += endStyle; }\n      var token = elt(\"span\", [content], fullStyle, css);\n      if (attributes) {\n        for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != \"style\" && attr != \"class\")\n          { token.setAttribute(attr, attributes[attr]); } }\n      }\n      return builder.content.appendChild(token)\n    }\n    builder.content.appendChild(content);\n  }\n\n  // Change some spaces to NBSP to prevent the browser from collapsing\n  // trailing spaces at the end of a line when rendering text (issue #1362).\n  function splitSpaces(text, trailingBefore) {\n    if (text.length > 1 && !/  /.test(text)) { return text }\n    var spaceBefore = trailingBefore, result = \"\";\n    for (var i = 0; i < text.length; i++) {\n      var ch = text.charAt(i);\n      if (ch == \" \" && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))\n        { ch = \"\\u00a0\"; }\n      result += ch;\n      spaceBefore = ch == \" \";\n    }\n    return result\n  }\n\n  // Work around nonsense dimensions being reported for stretches of\n  // right-to-left text.\n  function buildTokenBadBidi(inner, order) {\n    return function (builder, text, style, startStyle, endStyle, css, attributes) {\n      style = style ? style + \" cm-force-border\" : \"cm-force-border\";\n      var start = builder.pos, end = start + text.length;\n      for (;;) {\n        // Find the part that overlaps with the start of this text\n        var part = (void 0);\n        for (var i = 0; i < order.length; i++) {\n          part = order[i];\n          if (part.to > start && part.from <= start) { break }\n        }\n        if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) }\n        inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes);\n        startStyle = null;\n        text = text.slice(part.to - start);\n        start = part.to;\n      }\n    }\n  }\n\n  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {\n    var widget = !ignoreWidget && marker.widgetNode;\n    if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); }\n    if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {\n      if (!widget)\n        { widget = builder.content.appendChild(document.createElement(\"span\")); }\n      widget.setAttribute(\"cm-marker\", marker.id);\n    }\n    if (widget) {\n      builder.cm.display.input.setUneditable(widget);\n      builder.content.appendChild(widget);\n    }\n    builder.pos += size;\n    builder.trailingSpace = false;\n  }\n\n  // Outputs a number of spans to make up a line, taking highlighting\n  // and marked text into account.\n  function insertLineContent(line, builder, styles) {\n    var spans = line.markedSpans, allText = line.text, at = 0;\n    if (!spans) {\n      for (var i$1 = 1; i$1 < styles.length; i$1+=2)\n        { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); }\n      return\n    }\n\n    var len = allText.length, pos = 0, i = 1, text = \"\", style, css;\n    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes;\n    for (;;) {\n      if (nextChange == pos) { // Update current marker set\n        spanStyle = spanEndStyle = spanStartStyle = css = \"\";\n        attributes = null;\n        collapsed = null; nextChange = Infinity;\n        var foundBookmarks = [], endStyles = (void 0);\n        for (var j = 0; j < spans.length; ++j) {\n          var sp = spans[j], m = sp.marker;\n          if (m.type == \"bookmark\" && sp.from == pos && m.widgetNode) {\n            foundBookmarks.push(m);\n          } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {\n            if (sp.to != null && sp.to != pos && nextChange > sp.to) {\n              nextChange = sp.to;\n              spanEndStyle = \"\";\n            }\n            if (m.className) { spanStyle += \" \" + m.className; }\n            if (m.css) { css = (css ? css + \";\" : \"\") + m.css; }\n            if (m.startStyle && sp.from == pos) { spanStartStyle += \" \" + m.startStyle; }\n            if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); }\n            // support for the old title property\n            // https://github.com/codemirror/CodeMirror/pull/5673\n            if (m.title) { (attributes || (attributes = {})).title = m.title; }\n            if (m.attributes) {\n              for (var attr in m.attributes)\n                { (attributes || (attributes = {}))[attr] = m.attributes[attr]; }\n            }\n            if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))\n              { collapsed = sp; }\n          } else if (sp.from > pos && nextChange > sp.from) {\n            nextChange = sp.from;\n          }\n        }\n        if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)\n          { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += \" \" + endStyles[j$1]; } } }\n\n        if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2)\n          { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } }\n        if (collapsed && (collapsed.from || 0) == pos) {\n          buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,\n                             collapsed.marker, collapsed.from == null);\n          if (collapsed.to == null) { return }\n          if (collapsed.to == pos) { collapsed = false; }\n        }\n      }\n      if (pos >= len) { break }\n\n      var upto = Math.min(len, nextChange);\n      while (true) {\n        if (text) {\n          var end = pos + text.length;\n          if (!collapsed) {\n            var tokenText = end > upto ? text.slice(0, upto - pos) : text;\n            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,\n                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : \"\", css, attributes);\n          }\n          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}\n          pos = end;\n          spanStartStyle = \"\";\n        }\n        text = allText.slice(at, at = styles[i++]);\n        style = interpretTokenStyle(styles[i++], builder.cm.options);\n      }\n    }\n  }\n\n\n  // These objects are used to represent the visible (currently drawn)\n  // part of the document. A LineView may correspond to multiple\n  // logical lines, if those are connected by collapsed ranges.\n  function LineView(doc, line, lineN) {\n    // The starting line\n    this.line = line;\n    // Continuing lines, if any\n    this.rest = visualLineContinued(line);\n    // Number of logical lines in this visual line\n    this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;\n    this.node = this.text = null;\n    this.hidden = lineIsHidden(doc, line);\n  }\n\n  // Create a range of LineView objects for the given lines.\n  function buildViewArray(cm, from, to) {\n    var array = [], nextPos;\n    for (var pos = from; pos < to; pos = nextPos) {\n      var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);\n      nextPos = pos + view.size;\n      array.push(view);\n    }\n    return array\n  }\n\n  var operationGroup = null;\n\n  function pushOperation(op) {\n    if (operationGroup) {\n      operationGroup.ops.push(op);\n    } else {\n      op.ownsGroup = operationGroup = {\n        ops: [op],\n        delayedCallbacks: []\n      };\n    }\n  }\n\n  function fireCallbacksForOps(group) {\n    // Calls delayed callbacks and cursorActivity handlers until no\n    // new ones appear\n    var callbacks = group.delayedCallbacks, i = 0;\n    do {\n      for (; i < callbacks.length; i++)\n        { callbacks[i].call(null); }\n      for (var j = 0; j < group.ops.length; j++) {\n        var op = group.ops[j];\n        if (op.cursorActivityHandlers)\n          { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)\n            { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } }\n      }\n    } while (i < callbacks.length)\n  }\n\n  function finishOperation(op, endCb) {\n    var group = op.ownsGroup;\n    if (!group) { return }\n\n    try { fireCallbacksForOps(group); }\n    finally {\n      operationGroup = null;\n      endCb(group);\n    }\n  }\n\n  var orphanDelayedCallbacks = null;\n\n  // Often, we want to signal events at a point where we are in the\n  // middle of some work, but don't want the handler to start calling\n  // other methods on the editor, which might be in an inconsistent\n  // state or simply not expect any other events to happen.\n  // signalLater looks whether there are any handlers, and schedules\n  // them to be executed when the last operation ends, or, if no\n  // operation is active, when a timeout fires.\n  function signalLater(emitter, type /*, values...*/) {\n    var arr = getHandlers(emitter, type);\n    if (!arr.length) { return }\n    var args = Array.prototype.slice.call(arguments, 2), list;\n    if (operationGroup) {\n      list = operationGroup.delayedCallbacks;\n    } else if (orphanDelayedCallbacks) {\n      list = orphanDelayedCallbacks;\n    } else {\n      list = orphanDelayedCallbacks = [];\n      setTimeout(fireOrphanDelayed, 0);\n    }\n    var loop = function ( i ) {\n      list.push(function () { return arr[i].apply(null, args); });\n    };\n\n    for (var i = 0; i < arr.length; ++i)\n      loop( i );\n  }\n\n  function fireOrphanDelayed() {\n    var delayed = orphanDelayedCallbacks;\n    orphanDelayedCallbacks = null;\n    for (var i = 0; i < delayed.length; ++i) { delayed[i](); }\n  }\n\n  // When an aspect of a line changes, a string is added to\n  // lineView.changes. This updates the relevant part of the line's\n  // DOM structure.\n  function updateLineForChanges(cm, lineView, lineN, dims) {\n    for (var j = 0; j < lineView.changes.length; j++) {\n      var type = lineView.changes[j];\n      if (type == \"text\") { updateLineText(cm, lineView); }\n      else if (type == \"gutter\") { updateLineGutter(cm, lineView, lineN, dims); }\n      else if (type == \"class\") { updateLineClasses(cm, lineView); }\n      else if (type == \"widget\") { updateLineWidgets(cm, lineView, dims); }\n    }\n    lineView.changes = null;\n  }\n\n  // Lines with gutter elements, widgets or a background class need to\n  // be wrapped, and have the extra elements added to the wrapper div\n  function ensureLineWrapped(lineView) {\n    if (lineView.node == lineView.text) {\n      lineView.node = elt(\"div\", null, null, \"position: relative\");\n      if (lineView.text.parentNode)\n        { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); }\n      lineView.node.appendChild(lineView.text);\n      if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; }\n    }\n    return lineView.node\n  }\n\n  function updateLineBackground(cm, lineView) {\n    var cls = lineView.bgClass ? lineView.bgClass + \" \" + (lineView.line.bgClass || \"\") : lineView.line.bgClass;\n    if (cls) { cls += \" CodeMirror-linebackground\"; }\n    if (lineView.background) {\n      if (cls) { lineView.background.className = cls; }\n      else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }\n    } else if (cls) {\n      var wrap = ensureLineWrapped(lineView);\n      lineView.background = wrap.insertBefore(elt(\"div\", null, cls), wrap.firstChild);\n      cm.display.input.setUneditable(lineView.background);\n    }\n  }\n\n  // Wrapper around buildLineContent which will reuse the structure\n  // in display.externalMeasured when possible.\n  function getLineContent(cm, lineView) {\n    var ext = cm.display.externalMeasured;\n    if (ext && ext.line == lineView.line) {\n      cm.display.externalMeasured = null;\n      lineView.measure = ext.measure;\n      return ext.built\n    }\n    return buildLineContent(cm, lineView)\n  }\n\n  // Redraw the line's text. Interacts with the background and text\n  // classes because the mode may output tokens that influence these\n  // classes.\n  function updateLineText(cm, lineView) {\n    var cls = lineView.text.className;\n    var built = getLineContent(cm, lineView);\n    if (lineView.text == lineView.node) { lineView.node = built.pre; }\n    lineView.text.parentNode.replaceChild(built.pre, lineView.text);\n    lineView.text = built.pre;\n    if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {\n      lineView.bgClass = built.bgClass;\n      lineView.textClass = built.textClass;\n      updateLineClasses(cm, lineView);\n    } else if (cls) {\n      lineView.text.className = cls;\n    }\n  }\n\n  function updateLineClasses(cm, lineView) {\n    updateLineBackground(cm, lineView);\n    if (lineView.line.wrapClass)\n      { ensureLineWrapped(lineView).className = lineView.line.wrapClass; }\n    else if (lineView.node != lineView.text)\n      { lineView.node.className = \"\"; }\n    var textClass = lineView.textClass ? lineView.textClass + \" \" + (lineView.line.textClass || \"\") : lineView.line.textClass;\n    lineView.text.className = textClass || \"\";\n  }\n\n  function updateLineGutter(cm, lineView, lineN, dims) {\n    if (lineView.gutter) {\n      lineView.node.removeChild(lineView.gutter);\n      lineView.gutter = null;\n    }\n    if (lineView.gutterBackground) {\n      lineView.node.removeChild(lineView.gutterBackground);\n      lineView.gutterBackground = null;\n    }\n    if (lineView.line.gutterClass) {\n      var wrap = ensureLineWrapped(lineView);\n      lineView.gutterBackground = elt(\"div\", null, \"CodeMirror-gutter-background \" + lineView.line.gutterClass,\n                                      (\"left: \" + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px; width: \" + (dims.gutterTotalWidth) + \"px\"));\n      cm.display.input.setUneditable(lineView.gutterBackground);\n      wrap.insertBefore(lineView.gutterBackground, lineView.text);\n    }\n    var markers = lineView.line.gutterMarkers;\n    if (cm.options.lineNumbers || markers) {\n      var wrap$1 = ensureLineWrapped(lineView);\n      var gutterWrap = lineView.gutter = elt(\"div\", null, \"CodeMirror-gutter-wrapper\", (\"left: \" + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px\"));\n      cm.display.input.setUneditable(gutterWrap);\n      wrap$1.insertBefore(gutterWrap, lineView.text);\n      if (lineView.line.gutterClass)\n        { gutterWrap.className += \" \" + lineView.line.gutterClass; }\n      if (cm.options.lineNumbers && (!markers || !markers[\"CodeMirror-linenumbers\"]))\n        { lineView.lineNumber = gutterWrap.appendChild(\n          elt(\"div\", lineNumberFor(cm.options, lineN),\n              \"CodeMirror-linenumber CodeMirror-gutter-elt\",\n              (\"left: \" + (dims.gutterLeft[\"CodeMirror-linenumbers\"]) + \"px; width: \" + (cm.display.lineNumInnerWidth) + \"px\"))); }\n      if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) {\n        var id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id];\n        if (found)\n          { gutterWrap.appendChild(elt(\"div\", [found], \"CodeMirror-gutter-elt\",\n                                     (\"left: \" + (dims.gutterLeft[id]) + \"px; width: \" + (dims.gutterWidth[id]) + \"px\"))); }\n      } }\n    }\n  }\n\n  function updateLineWidgets(cm, lineView, dims) {\n    if (lineView.alignable) { lineView.alignable = null; }\n    var isWidget = classTest(\"CodeMirror-linewidget\");\n    for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {\n      next = node.nextSibling;\n      if (isWidget.test(node.className)) { lineView.node.removeChild(node); }\n    }\n    insertLineWidgets(cm, lineView, dims);\n  }\n\n  // Build a line's DOM representation from scratch\n  function buildLineElement(cm, lineView, lineN, dims) {\n    var built = getLineContent(cm, lineView);\n    lineView.text = lineView.node = built.pre;\n    if (built.bgClass) { lineView.bgClass = built.bgClass; }\n    if (built.textClass) { lineView.textClass = built.textClass; }\n\n    updateLineClasses(cm, lineView);\n    updateLineGutter(cm, lineView, lineN, dims);\n    insertLineWidgets(cm, lineView, dims);\n    return lineView.node\n  }\n\n  // A lineView may contain multiple logical lines (when merged by\n  // collapsed spans). The widgets for all of them need to be drawn.\n  function insertLineWidgets(cm, lineView, dims) {\n    insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);\n    if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)\n      { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } }\n  }\n\n  function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {\n    if (!line.widgets) { return }\n    var wrap = ensureLineWrapped(lineView);\n    for (var i = 0, ws = line.widgets; i < ws.length; ++i) {\n      var widget = ws[i], node = elt(\"div\", [widget.node], \"CodeMirror-linewidget\" + (widget.className ? \" \" + widget.className : \"\"));\n      if (!widget.handleMouseEvents) { node.setAttribute(\"cm-ignore-events\", \"true\"); }\n      positionLineWidget(widget, node, lineView, dims);\n      cm.display.input.setUneditable(node);\n      if (allowAbove && widget.above)\n        { wrap.insertBefore(node, lineView.gutter || lineView.text); }\n      else\n        { wrap.appendChild(node); }\n      signalLater(widget, \"redraw\");\n    }\n  }\n\n  function positionLineWidget(widget, node, lineView, dims) {\n    if (widget.noHScroll) {\n  (lineView.alignable || (lineView.alignable = [])).push(node);\n      var width = dims.wrapperWidth;\n      node.style.left = dims.fixedPos + \"px\";\n      if (!widget.coverGutter) {\n        width -= dims.gutterTotalWidth;\n        node.style.paddingLeft = dims.gutterTotalWidth + \"px\";\n      }\n      node.style.width = width + \"px\";\n    }\n    if (widget.coverGutter) {\n      node.style.zIndex = 5;\n      node.style.position = \"relative\";\n      if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + \"px\"; }\n    }\n  }\n\n  function widgetHeight(widget) {\n    if (widget.height != null) { return widget.height }\n    var cm = widget.doc.cm;\n    if (!cm) { return 0 }\n    if (!contains(document.body, widget.node)) {\n      var parentStyle = \"position: relative;\";\n      if (widget.coverGutter)\n        { parentStyle += \"margin-left: -\" + cm.display.gutters.offsetWidth + \"px;\"; }\n      if (widget.noHScroll)\n        { parentStyle += \"width: \" + cm.display.wrapper.clientWidth + \"px;\"; }\n      removeChildrenAndAdd(cm.display.measure, elt(\"div\", [widget.node], null, parentStyle));\n    }\n    return widget.height = widget.node.parentNode.offsetHeight\n  }\n\n  // Return true when the given mouse event happened in a widget\n  function eventInWidget(display, e) {\n    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {\n      if (!n || (n.nodeType == 1 && n.getAttribute(\"cm-ignore-events\") == \"true\") ||\n          (n.parentNode == display.sizer && n != display.mover))\n        { return true }\n    }\n  }\n\n  // POSITION MEASUREMENT\n\n  function paddingTop(display) {return display.lineSpace.offsetTop}\n  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}\n  function paddingH(display) {\n    if (display.cachedPaddingH) { return display.cachedPaddingH }\n    var e = removeChildrenAndAdd(display.measure, elt(\"pre\", \"x\", \"CodeMirror-line-like\"));\n    var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;\n    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};\n    if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; }\n    return data\n  }\n\n  function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }\n  function displayWidth(cm) {\n    return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth\n  }\n  function displayHeight(cm) {\n    return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight\n  }\n\n  // Ensure the lineView.wrapping.heights array is populated. This is\n  // an array of bottom offsets for the lines that make up a drawn\n  // line. When lineWrapping is on, there might be more than one\n  // height.\n  function ensureLineHeights(cm, lineView, rect) {\n    var wrapping = cm.options.lineWrapping;\n    var curWidth = wrapping && displayWidth(cm);\n    if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {\n      var heights = lineView.measure.heights = [];\n      if (wrapping) {\n        lineView.measure.width = curWidth;\n        var rects = lineView.text.firstChild.getClientRects();\n        for (var i = 0; i < rects.length - 1; i++) {\n          var cur = rects[i], next = rects[i + 1];\n          if (Math.abs(cur.bottom - next.bottom) > 2)\n            { heights.push((cur.bottom + next.top) / 2 - rect.top); }\n        }\n      }\n      heights.push(rect.bottom - rect.top);\n    }\n  }\n\n  // Find a line map (mapping character offsets to text nodes) and a\n  // measurement cache for the given line number. (A line view might\n  // contain multiple lines when collapsed ranges are present.)\n  function mapFromLineView(lineView, line, lineN) {\n    if (lineView.line == line)\n      { return {map: lineView.measure.map, cache: lineView.measure.cache} }\n    for (var i = 0; i < lineView.rest.length; i++)\n      { if (lineView.rest[i] == line)\n        { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }\n    for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)\n      { if (lineNo(lineView.rest[i$1]) > lineN)\n        { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }\n  }\n\n  // Render a line into the hidden node display.externalMeasured. Used\n  // when measurement is needed for a line that's not in the viewport.\n  function updateExternalMeasurement(cm, line) {\n    line = visualLine(line);\n    var lineN = lineNo(line);\n    var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);\n    view.lineN = lineN;\n    var built = view.built = buildLineContent(cm, view);\n    view.text = built.pre;\n    removeChildrenAndAdd(cm.display.lineMeasure, built.pre);\n    return view\n  }\n\n  // Get a {top, bottom, left, right} box (in line-local coordinates)\n  // for a given character.\n  function measureChar(cm, line, ch, bias) {\n    return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)\n  }\n\n  // Find a line view that corresponds to the given line number.\n  function findViewForLine(cm, lineN) {\n    if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)\n      { return cm.display.view[findViewIndex(cm, lineN)] }\n    var ext = cm.display.externalMeasured;\n    if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)\n      { return ext }\n  }\n\n  // Measurement can be split in two steps, the set-up work that\n  // applies to the whole line, and the measurement of the actual\n  // character. Functions like coordsChar, that need to do a lot of\n  // measurements in a row, can thus ensure that the set-up work is\n  // only done once.\n  function prepareMeasureForLine(cm, line) {\n    var lineN = lineNo(line);\n    var view = findViewForLine(cm, lineN);\n    if (view && !view.text) {\n      view = null;\n    } else if (view && view.changes) {\n      updateLineForChanges(cm, view, lineN, getDimensions(cm));\n      cm.curOp.forceUpdate = true;\n    }\n    if (!view)\n      { view = updateExternalMeasurement(cm, line); }\n\n    var info = mapFromLineView(view, line, lineN);\n    return {\n      line: line, view: view, rect: null,\n      map: info.map, cache: info.cache, before: info.before,\n      hasHeights: false\n    }\n  }\n\n  // Given a prepared measurement object, measures the position of an\n  // actual character (or fetches it from the cache).\n  function measureCharPrepared(cm, prepared, ch, bias, varHeight) {\n    if (prepared.before) { ch = -1; }\n    var key = ch + (bias || \"\"), found;\n    if (prepared.cache.hasOwnProperty(key)) {\n      found = prepared.cache[key];\n    } else {\n      if (!prepared.rect)\n        { prepared.rect = prepared.view.text.getBoundingClientRect(); }\n      if (!prepared.hasHeights) {\n        ensureLineHeights(cm, prepared.view, prepared.rect);\n        prepared.hasHeights = true;\n      }\n      found = measureCharInner(cm, prepared, ch, bias);\n      if (!found.bogus) { prepared.cache[key] = found; }\n    }\n    return {left: found.left, right: found.right,\n            top: varHeight ? found.rtop : found.top,\n            bottom: varHeight ? found.rbottom : found.bottom}\n  }\n\n  var nullRect = {left: 0, right: 0, top: 0, bottom: 0};\n\n  function nodeAndOffsetInLineMap(map, ch, bias) {\n    var node, start, end, collapse, mStart, mEnd;\n    // First, search the line map for the text node corresponding to,\n    // or closest to, the target character.\n    for (var i = 0; i < map.length; i += 3) {\n      mStart = map[i];\n      mEnd = map[i + 1];\n      if (ch < mStart) {\n        start = 0; end = 1;\n        collapse = \"left\";\n      } else if (ch < mEnd) {\n        start = ch - mStart;\n        end = start + 1;\n      } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {\n        end = mEnd - mStart;\n        start = end - 1;\n        if (ch >= mEnd) { collapse = \"right\"; }\n      }\n      if (start != null) {\n        node = map[i + 2];\n        if (mStart == mEnd && bias == (node.insertLeft ? \"left\" : \"right\"))\n          { collapse = bias; }\n        if (bias == \"left\" && start == 0)\n          { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {\n            node = map[(i -= 3) + 2];\n            collapse = \"left\";\n          } }\n        if (bias == \"right\" && start == mEnd - mStart)\n          { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {\n            node = map[(i += 3) + 2];\n            collapse = \"right\";\n          } }\n        break\n      }\n    }\n    return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}\n  }\n\n  function getUsefulRect(rects, bias) {\n    var rect = nullRect;\n    if (bias == \"left\") { for (var i = 0; i < rects.length; i++) {\n      if ((rect = rects[i]).left != rect.right) { break }\n    } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {\n      if ((rect = rects[i$1]).left != rect.right) { break }\n    } }\n    return rect\n  }\n\n  function measureCharInner(cm, prepared, ch, bias) {\n    var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);\n    var node = place.node, start = place.start, end = place.end, collapse = place.collapse;\n\n    var rect;\n    if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.\n      for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned\n        while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; }\n        while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; }\n        if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)\n          { rect = node.parentNode.getBoundingClientRect(); }\n        else\n          { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); }\n        if (rect.left || rect.right || start == 0) { break }\n        end = start;\n        start = start - 1;\n        collapse = \"right\";\n      }\n      if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); }\n    } else { // If it is a widget, simply get the box for the whole widget.\n      if (start > 0) { collapse = bias = \"right\"; }\n      var rects;\n      if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)\n        { rect = rects[bias == \"right\" ? rects.length - 1 : 0]; }\n      else\n        { rect = node.getBoundingClientRect(); }\n    }\n    if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {\n      var rSpan = node.parentNode.getClientRects()[0];\n      if (rSpan)\n        { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; }\n      else\n        { rect = nullRect; }\n    }\n\n    var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;\n    var mid = (rtop + rbot) / 2;\n    var heights = prepared.view.measure.heights;\n    var i = 0;\n    for (; i < heights.length - 1; i++)\n      { if (mid < heights[i]) { break } }\n    var top = i ? heights[i - 1] : 0, bot = heights[i];\n    var result = {left: (collapse == \"right\" ? rect.right : rect.left) - prepared.rect.left,\n                  right: (collapse == \"left\" ? rect.left : rect.right) - prepared.rect.left,\n                  top: top, bottom: bot};\n    if (!rect.left && !rect.right) { result.bogus = true; }\n    if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }\n\n    return result\n  }\n\n  // Work around problem with bounding client rects on ranges being\n  // returned incorrectly when zoomed on IE10 and below.\n  function maybeUpdateRectForZooming(measure, rect) {\n    if (!window.screen || screen.logicalXDPI == null ||\n        screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))\n      { return rect }\n    var scaleX = screen.logicalXDPI / screen.deviceXDPI;\n    var scaleY = screen.logicalYDPI / screen.deviceYDPI;\n    return {left: rect.left * scaleX, right: rect.right * scaleX,\n            top: rect.top * scaleY, bottom: rect.bottom * scaleY}\n  }\n\n  function clearLineMeasurementCacheFor(lineView) {\n    if (lineView.measure) {\n      lineView.measure.cache = {};\n      lineView.measure.heights = null;\n      if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)\n        { lineView.measure.caches[i] = {}; } }\n    }\n  }\n\n  function clearLineMeasurementCache(cm) {\n    cm.display.externalMeasure = null;\n    removeChildren(cm.display.lineMeasure);\n    for (var i = 0; i < cm.display.view.length; i++)\n      { clearLineMeasurementCacheFor(cm.display.view[i]); }\n  }\n\n  function clearCaches(cm) {\n    clearLineMeasurementCache(cm);\n    cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;\n    if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; }\n    cm.display.lineNumChars = null;\n  }\n\n  function pageScrollX() {\n    // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206\n    // which causes page_Offset and bounding client rects to use\n    // different reference viewports and invalidate our calculations.\n    if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }\n    return window.pageXOffset || (document.documentElement || document.body).scrollLeft\n  }\n  function pageScrollY() {\n    if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }\n    return window.pageYOffset || (document.documentElement || document.body).scrollTop\n  }\n\n  function widgetTopHeight(lineObj) {\n    var height = 0;\n    if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above)\n      { height += widgetHeight(lineObj.widgets[i]); } } }\n    return height\n  }\n\n  // Converts a {top, bottom, left, right} box from line-local\n  // coordinates into another coordinate system. Context may be one of\n  // \"line\", \"div\" (display.lineDiv), \"local\"./null (editor), \"window\",\n  // or \"page\".\n  function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {\n    if (!includeWidgets) {\n      var height = widgetTopHeight(lineObj);\n      rect.top += height; rect.bottom += height;\n    }\n    if (context == \"line\") { return rect }\n    if (!context) { context = \"local\"; }\n    var yOff = heightAtLine(lineObj);\n    if (context == \"local\") { yOff += paddingTop(cm.display); }\n    else { yOff -= cm.display.viewOffset; }\n    if (context == \"page\" || context == \"window\") {\n      var lOff = cm.display.lineSpace.getBoundingClientRect();\n      yOff += lOff.top + (context == \"window\" ? 0 : pageScrollY());\n      var xOff = lOff.left + (context == \"window\" ? 0 : pageScrollX());\n      rect.left += xOff; rect.right += xOff;\n    }\n    rect.top += yOff; rect.bottom += yOff;\n    return rect\n  }\n\n  // Coverts a box from \"div\" coords to another coordinate system.\n  // Context may be \"window\", \"page\", \"div\", or \"local\"./null.\n  function fromCoordSystem(cm, coords, context) {\n    if (context == \"div\") { return coords }\n    var left = coords.left, top = coords.top;\n    // First move into \"page\" coordinate system\n    if (context == \"page\") {\n      left -= pageScrollX();\n      top -= pageScrollY();\n    } else if (context == \"local\" || !context) {\n      var localBox = cm.display.sizer.getBoundingClientRect();\n      left += localBox.left;\n      top += localBox.top;\n    }\n\n    var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();\n    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}\n  }\n\n  function charCoords(cm, pos, context, lineObj, bias) {\n    if (!lineObj) { lineObj = getLine(cm.doc, pos.line); }\n    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context)\n  }\n\n  // Returns a box for a given cursor position, which may have an\n  // 'other' property containing the position of the secondary cursor\n  // on a bidi boundary.\n  // A cursor Pos(line, char, \"before\") is on the same visual line as `char - 1`\n  // and after `char - 1` in writing order of `char - 1`\n  // A cursor Pos(line, char, \"after\") is on the same visual line as `char`\n  // and before `char` in writing order of `char`\n  // Examples (upper-case letters are RTL, lower-case are LTR):\n  //     Pos(0, 1, ...)\n  //     before   after\n  // ab     a|b     a|b\n  // aB     a|B     aB|\n  // Ab     |Ab     A|b\n  // AB     B|A     B|A\n  // Every position after the last character on a line is considered to stick\n  // to the last character on the line.\n  function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {\n    lineObj = lineObj || getLine(cm.doc, pos.line);\n    if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }\n    function get(ch, right) {\n      var m = measureCharPrepared(cm, preparedMeasure, ch, right ? \"right\" : \"left\", varHeight);\n      if (right) { m.left = m.right; } else { m.right = m.left; }\n      return intoCoordSystem(cm, lineObj, m, context)\n    }\n    var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky;\n    if (ch >= lineObj.text.length) {\n      ch = lineObj.text.length;\n      sticky = \"before\";\n    } else if (ch <= 0) {\n      ch = 0;\n      sticky = \"after\";\n    }\n    if (!order) { return get(sticky == \"before\" ? ch - 1 : ch, sticky == \"before\") }\n\n    function getBidi(ch, partPos, invert) {\n      var part = order[partPos], right = part.level == 1;\n      return get(invert ? ch - 1 : ch, right != invert)\n    }\n    var partPos = getBidiPartAt(order, ch, sticky);\n    var other = bidiOther;\n    var val = getBidi(ch, partPos, sticky == \"before\");\n    if (other != null) { val.other = getBidi(ch, other, sticky != \"before\"); }\n    return val\n  }\n\n  // Used to cheaply estimate the coordinates for a position. Used for\n  // intermediate scroll updates.\n  function estimateCoords(cm, pos) {\n    var left = 0;\n    pos = clipPos(cm.doc, pos);\n    if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; }\n    var lineObj = getLine(cm.doc, pos.line);\n    var top = heightAtLine(lineObj) + paddingTop(cm.display);\n    return {left: left, right: left, top: top, bottom: top + lineObj.height}\n  }\n\n  // Positions returned by coordsChar contain some extra information.\n  // xRel is the relative x position of the input coordinates compared\n  // to the found position (so xRel > 0 means the coordinates are to\n  // the right of the character position, for example). When outside\n  // is true, that means the coordinates lie outside the line's\n  // vertical range.\n  function PosWithInfo(line, ch, sticky, outside, xRel) {\n    var pos = Pos(line, ch, sticky);\n    pos.xRel = xRel;\n    if (outside) { pos.outside = outside; }\n    return pos\n  }\n\n  // Compute the character position closest to the given coordinates.\n  // Input must be lineSpace-local (\"div\" coordinate system).\n  function coordsChar(cm, x, y) {\n    var doc = cm.doc;\n    y += cm.display.viewOffset;\n    if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -1) }\n    var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;\n    if (lineN > last)\n      { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) }\n    if (x < 0) { x = 0; }\n\n    var lineObj = getLine(doc, lineN);\n    for (;;) {\n      var found = coordsCharInner(cm, lineObj, lineN, x, y);\n      var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0));\n      if (!collapsed) { return found }\n      var rangeEnd = collapsed.find(1);\n      if (rangeEnd.line == lineN) { return rangeEnd }\n      lineObj = getLine(doc, lineN = rangeEnd.line);\n    }\n  }\n\n  function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {\n    y -= widgetTopHeight(lineObj);\n    var end = lineObj.text.length;\n    var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0);\n    end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end);\n    return {begin: begin, end: end}\n  }\n\n  function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {\n    if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }\n    var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), \"line\").top;\n    return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)\n  }\n\n  // Returns true if the given side of a box is after the given\n  // coordinates, in top-to-bottom, left-to-right order.\n  function boxIsAfter(box, x, y, left) {\n    return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x\n  }\n\n  function coordsCharInner(cm, lineObj, lineNo, x, y) {\n    // Move y into line-local coordinate space\n    y -= heightAtLine(lineObj);\n    var preparedMeasure = prepareMeasureForLine(cm, lineObj);\n    // When directly calling `measureCharPrepared`, we have to adjust\n    // for the widgets at this line.\n    var widgetHeight = widgetTopHeight(lineObj);\n    var begin = 0, end = lineObj.text.length, ltr = true;\n\n    var order = getOrder(lineObj, cm.doc.direction);\n    // If the line isn't plain left-to-right text, first figure out\n    // which bidi section the coordinates fall into.\n    if (order) {\n      var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)\n                   (cm, lineObj, lineNo, preparedMeasure, order, x, y);\n      ltr = part.level != 1;\n      // The awkward -1 offsets are needed because findFirst (called\n      // on these below) will treat its first bound as inclusive,\n      // second as exclusive, but we want to actually address the\n      // characters in the part's range\n      begin = ltr ? part.from : part.to - 1;\n      end = ltr ? part.to : part.from - 1;\n    }\n\n    // A binary search to find the first character whose bounding box\n    // starts after the coordinates. If we run across any whose box wrap\n    // the coordinates, store that.\n    var chAround = null, boxAround = null;\n    var ch = findFirst(function (ch) {\n      var box = measureCharPrepared(cm, preparedMeasure, ch);\n      box.top += widgetHeight; box.bottom += widgetHeight;\n      if (!boxIsAfter(box, x, y, false)) { return false }\n      if (box.top <= y && box.left <= x) {\n        chAround = ch;\n        boxAround = box;\n      }\n      return true\n    }, begin, end);\n\n    var baseX, sticky, outside = false;\n    // If a box around the coordinates was found, use that\n    if (boxAround) {\n      // Distinguish coordinates nearer to the left or right side of the box\n      var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr;\n      ch = chAround + (atStart ? 0 : 1);\n      sticky = atStart ? \"after\" : \"before\";\n      baseX = atLeft ? boxAround.left : boxAround.right;\n    } else {\n      // (Adjust for extended bound, if necessary.)\n      if (!ltr && (ch == end || ch == begin)) { ch++; }\n      // To determine which side to associate with, get the box to the\n      // left of the character and compare it's vertical position to the\n      // coordinates\n      sticky = ch == 0 ? \"after\" : ch == lineObj.text.length ? \"before\" :\n        (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ?\n        \"after\" : \"before\";\n      // Now get accurate coordinates for this place, in order to get a\n      // base X position\n      var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), \"line\", lineObj, preparedMeasure);\n      baseX = coords.left;\n      outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0;\n    }\n\n    ch = skipExtendingChars(lineObj.text, ch, 1);\n    return PosWithInfo(lineNo, ch, sticky, outside, x - baseX)\n  }\n\n  function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {\n    // Bidi parts are sorted left-to-right, and in a non-line-wrapping\n    // situation, we can take this ordering to correspond to the visual\n    // ordering. This finds the first part whose end is after the given\n    // coordinates.\n    var index = findFirst(function (i) {\n      var part = order[i], ltr = part.level != 1;\n      return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? \"before\" : \"after\"),\n                                     \"line\", lineObj, preparedMeasure), x, y, true)\n    }, 0, order.length - 1);\n    var part = order[index];\n    // If this isn't the first part, the part's start is also after\n    // the coordinates, and the coordinates aren't on the same line as\n    // that start, move one part back.\n    if (index > 0) {\n      var ltr = part.level != 1;\n      var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? \"after\" : \"before\"),\n                               \"line\", lineObj, preparedMeasure);\n      if (boxIsAfter(start, x, y, true) && start.top > y)\n        { part = order[index - 1]; }\n    }\n    return part\n  }\n\n  function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {\n    // In a wrapped line, rtl text on wrapping boundaries can do things\n    // that don't correspond to the ordering in our `order` array at\n    // all, so a binary search doesn't work, and we want to return a\n    // part that only spans one line so that the binary search in\n    // coordsCharInner is safe. As such, we first find the extent of the\n    // wrapped line, and then do a flat search in which we discard any\n    // spans that aren't on the line.\n    var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);\n    var begin = ref.begin;\n    var end = ref.end;\n    if (/\\s/.test(lineObj.text.charAt(end - 1))) { end--; }\n    var part = null, closestDist = null;\n    for (var i = 0; i < order.length; i++) {\n      var p = order[i];\n      if (p.from >= end || p.to <= begin) { continue }\n      var ltr = p.level != 1;\n      var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right;\n      // Weigh against spans ending before this, so that they are only\n      // picked if nothing ends after\n      var dist = endX < x ? x - endX + 1e9 : endX - x;\n      if (!part || closestDist > dist) {\n        part = p;\n        closestDist = dist;\n      }\n    }\n    if (!part) { part = order[order.length - 1]; }\n    // Clip the part to the wrapped line.\n    if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; }\n    if (part.to > end) { part = {from: part.from, to: end, level: part.level}; }\n    return part\n  }\n\n  var measureText;\n  // Compute the default text height.\n  function textHeight(display) {\n    if (display.cachedTextHeight != null) { return display.cachedTextHeight }\n    if (measureText == null) {\n      measureText = elt(\"pre\", null, \"CodeMirror-line-like\");\n      // Measure a bunch of lines, for browsers that compute\n      // fractional heights.\n      for (var i = 0; i < 49; ++i) {\n        measureText.appendChild(document.createTextNode(\"x\"));\n        measureText.appendChild(elt(\"br\"));\n      }\n      measureText.appendChild(document.createTextNode(\"x\"));\n    }\n    removeChildrenAndAdd(display.measure, measureText);\n    var height = measureText.offsetHeight / 50;\n    if (height > 3) { display.cachedTextHeight = height; }\n    removeChildren(display.measure);\n    return height || 1\n  }\n\n  // Compute the default character width.\n  function charWidth(display) {\n    if (display.cachedCharWidth != null) { return display.cachedCharWidth }\n    var anchor = elt(\"span\", \"xxxxxxxxxx\");\n    var pre = elt(\"pre\", [anchor], \"CodeMirror-line-like\");\n    removeChildrenAndAdd(display.measure, pre);\n    var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;\n    if (width > 2) { display.cachedCharWidth = width; }\n    return width || 10\n  }\n\n  // Do a bulk-read of the DOM positions and sizes needed to draw the\n  // view, so that we don't interleave reading and writing to the DOM.\n  function getDimensions(cm) {\n    var d = cm.display, left = {}, width = {};\n    var gutterLeft = d.gutters.clientLeft;\n    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {\n      var id = cm.display.gutterSpecs[i].className;\n      left[id] = n.offsetLeft + n.clientLeft + gutterLeft;\n      width[id] = n.clientWidth;\n    }\n    return {fixedPos: compensateForHScroll(d),\n            gutterTotalWidth: d.gutters.offsetWidth,\n            gutterLeft: left,\n            gutterWidth: width,\n            wrapperWidth: d.wrapper.clientWidth}\n  }\n\n  // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,\n  // but using getBoundingClientRect to get a sub-pixel-accurate\n  // result.\n  function compensateForHScroll(display) {\n    return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left\n  }\n\n  // Returns a function that estimates the height of a line, to use as\n  // first approximation until the line becomes visible (and is thus\n  // properly measurable).\n  function estimateHeight(cm) {\n    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;\n    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);\n    return function (line) {\n      if (lineIsHidden(cm.doc, line)) { return 0 }\n\n      var widgetsHeight = 0;\n      if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {\n        if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; }\n      } }\n\n      if (wrapping)\n        { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }\n      else\n        { return widgetsHeight + th }\n    }\n  }\n\n  function estimateLineHeights(cm) {\n    var doc = cm.doc, est = estimateHeight(cm);\n    doc.iter(function (line) {\n      var estHeight = est(line);\n      if (estHeight != line.height) { updateLineHeight(line, estHeight); }\n    });\n  }\n\n  // Given a mouse event, find the corresponding position. If liberal\n  // is false, it checks whether a gutter or scrollbar was clicked,\n  // and returns null if it was. forRect is used by rectangular\n  // selections, and tries to estimate a character position even for\n  // coordinates beyond the right of the text.\n  function posFromMouse(cm, e, liberal, forRect) {\n    var display = cm.display;\n    if (!liberal && e_target(e).getAttribute(\"cm-not-content\") == \"true\") { return null }\n\n    var x, y, space = display.lineSpace.getBoundingClientRect();\n    // Fails unpredictably on IE[67] when mouse is dragged around quickly.\n    try { x = e.clientX - space.left; y = e.clientY - space.top; }\n    catch (e$1) { return null }\n    var coords = coordsChar(cm, x, y), line;\n    if (forRect && coords.xRel > 0 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {\n      var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;\n      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));\n    }\n    return coords\n  }\n\n  // Find the view element corresponding to a given line. Return null\n  // when the line isn't visible.\n  function findViewIndex(cm, n) {\n    if (n >= cm.display.viewTo) { return null }\n    n -= cm.display.viewFrom;\n    if (n < 0) { return null }\n    var view = cm.display.view;\n    for (var i = 0; i < view.length; i++) {\n      n -= view[i].size;\n      if (n < 0) { return i }\n    }\n  }\n\n  // Updates the display.view data structure for a given change to the\n  // document. From and to are in pre-change coordinates. Lendiff is\n  // the amount of lines added or subtracted by the change. This is\n  // used for changes that span multiple lines, or change the way\n  // lines are divided into visual lines. regLineChange (below)\n  // registers single-line changes.\n  function regChange(cm, from, to, lendiff) {\n    if (from == null) { from = cm.doc.first; }\n    if (to == null) { to = cm.doc.first + cm.doc.size; }\n    if (!lendiff) { lendiff = 0; }\n\n    var display = cm.display;\n    if (lendiff && to < display.viewTo &&\n        (display.updateLineNumbers == null || display.updateLineNumbers > from))\n      { display.updateLineNumbers = from; }\n\n    cm.curOp.viewChanged = true;\n\n    if (from >= display.viewTo) { // Change after\n      if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)\n        { resetView(cm); }\n    } else if (to <= display.viewFrom) { // Change before\n      if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {\n        resetView(cm);\n      } else {\n        display.viewFrom += lendiff;\n        display.viewTo += lendiff;\n      }\n    } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap\n      resetView(cm);\n    } else if (from <= display.viewFrom) { // Top overlap\n      var cut = viewCuttingPoint(cm, to, to + lendiff, 1);\n      if (cut) {\n        display.view = display.view.slice(cut.index);\n        display.viewFrom = cut.lineN;\n        display.viewTo += lendiff;\n      } else {\n        resetView(cm);\n      }\n    } else if (to >= display.viewTo) { // Bottom overlap\n      var cut$1 = viewCuttingPoint(cm, from, from, -1);\n      if (cut$1) {\n        display.view = display.view.slice(0, cut$1.index);\n        display.viewTo = cut$1.lineN;\n      } else {\n        resetView(cm);\n      }\n    } else { // Gap in the middle\n      var cutTop = viewCuttingPoint(cm, from, from, -1);\n      var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);\n      if (cutTop && cutBot) {\n        display.view = display.view.slice(0, cutTop.index)\n          .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))\n          .concat(display.view.slice(cutBot.index));\n        display.viewTo += lendiff;\n      } else {\n        resetView(cm);\n      }\n    }\n\n    var ext = display.externalMeasured;\n    if (ext) {\n      if (to < ext.lineN)\n        { ext.lineN += lendiff; }\n      else if (from < ext.lineN + ext.size)\n        { display.externalMeasured = null; }\n    }\n  }\n\n  // Register a change to a single line. Type must be one of \"text\",\n  // \"gutter\", \"class\", \"widget\"\n  function regLineChange(cm, line, type) {\n    cm.curOp.viewChanged = true;\n    var display = cm.display, ext = cm.display.externalMeasured;\n    if (ext && line >= ext.lineN && line < ext.lineN + ext.size)\n      { display.externalMeasured = null; }\n\n    if (line < display.viewFrom || line >= display.viewTo) { return }\n    var lineView = display.view[findViewIndex(cm, line)];\n    if (lineView.node == null) { return }\n    var arr = lineView.changes || (lineView.changes = []);\n    if (indexOf(arr, type) == -1) { arr.push(type); }\n  }\n\n  // Clear the view.\n  function resetView(cm) {\n    cm.display.viewFrom = cm.display.viewTo = cm.doc.first;\n    cm.display.view = [];\n    cm.display.viewOffset = 0;\n  }\n\n  function viewCuttingPoint(cm, oldN, newN, dir) {\n    var index = findViewIndex(cm, oldN), diff, view = cm.display.view;\n    if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)\n      { return {index: index, lineN: newN} }\n    var n = cm.display.viewFrom;\n    for (var i = 0; i < index; i++)\n      { n += view[i].size; }\n    if (n != oldN) {\n      if (dir > 0) {\n        if (index == view.length - 1) { return null }\n        diff = (n + view[index].size) - oldN;\n        index++;\n      } else {\n        diff = n - oldN;\n      }\n      oldN += diff; newN += diff;\n    }\n    while (visualLineNo(cm.doc, newN) != newN) {\n      if (index == (dir < 0 ? 0 : view.length - 1)) { return null }\n      newN += dir * view[index - (dir < 0 ? 1 : 0)].size;\n      index += dir;\n    }\n    return {index: index, lineN: newN}\n  }\n\n  // Force the view to cover a given range, adding empty view element\n  // or clipping off existing ones as needed.\n  function adjustView(cm, from, to) {\n    var display = cm.display, view = display.view;\n    if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {\n      display.view = buildViewArray(cm, from, to);\n      display.viewFrom = from;\n    } else {\n      if (display.viewFrom > from)\n        { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); }\n      else if (display.viewFrom < from)\n        { display.view = display.view.slice(findViewIndex(cm, from)); }\n      display.viewFrom = from;\n      if (display.viewTo < to)\n        { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); }\n      else if (display.viewTo > to)\n        { display.view = display.view.slice(0, findViewIndex(cm, to)); }\n    }\n    display.viewTo = to;\n  }\n\n  // Count the number of lines in the view whose DOM representation is\n  // out of date (or nonexistent).\n  function countDirtyView(cm) {\n    var view = cm.display.view, dirty = 0;\n    for (var i = 0; i < view.length; i++) {\n      var lineView = view[i];\n      if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; }\n    }\n    return dirty\n  }\n\n  function updateSelection(cm) {\n    cm.display.input.showSelection(cm.display.input.prepareSelection());\n  }\n\n  function prepareSelection(cm, primary) {\n    if ( primary === void 0 ) primary = true;\n\n    var doc = cm.doc, result = {};\n    var curFragment = result.cursors = document.createDocumentFragment();\n    var selFragment = result.selection = document.createDocumentFragment();\n\n    for (var i = 0; i < doc.sel.ranges.length; i++) {\n      if (!primary && i == doc.sel.primIndex) { continue }\n      var range = doc.sel.ranges[i];\n      if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue }\n      var collapsed = range.empty();\n      if (collapsed || cm.options.showCursorWhenSelecting)\n        { drawSelectionCursor(cm, range.head, curFragment); }\n      if (!collapsed)\n        { drawSelectionRange(cm, range, selFragment); }\n    }\n    return result\n  }\n\n  // Draws a cursor for the given range\n  function drawSelectionCursor(cm, head, output) {\n    var pos = cursorCoords(cm, head, \"div\", null, null, !cm.options.singleCursorHeightPerLine);\n\n    var cursor = output.appendChild(elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor\"));\n    cursor.style.left = pos.left + \"px\";\n    cursor.style.top = pos.top + \"px\";\n    cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + \"px\";\n\n    if (pos.other) {\n      // Secondary cursor, shown when on a 'jump' in bi-directional text\n      var otherCursor = output.appendChild(elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor CodeMirror-secondarycursor\"));\n      otherCursor.style.display = \"\";\n      otherCursor.style.left = pos.other.left + \"px\";\n      otherCursor.style.top = pos.other.top + \"px\";\n      otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + \"px\";\n    }\n  }\n\n  function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }\n\n  // Draws the given range as a highlighted selection\n  function drawSelectionRange(cm, range, output) {\n    var display = cm.display, doc = cm.doc;\n    var fragment = document.createDocumentFragment();\n    var padding = paddingH(cm.display), leftSide = padding.left;\n    var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;\n    var docLTR = doc.direction == \"ltr\";\n\n    function add(left, top, width, bottom) {\n      if (top < 0) { top = 0; }\n      top = Math.round(top);\n      bottom = Math.round(bottom);\n      fragment.appendChild(elt(\"div\", null, \"CodeMirror-selected\", (\"position: absolute; left: \" + left + \"px;\\n                             top: \" + top + \"px; width: \" + (width == null ? rightSide - left : width) + \"px;\\n                             height: \" + (bottom - top) + \"px\")));\n    }\n\n    function drawForLine(line, fromArg, toArg) {\n      var lineObj = getLine(doc, line);\n      var lineLen = lineObj.text.length;\n      var start, end;\n      function coords(ch, bias) {\n        return charCoords(cm, Pos(line, ch), \"div\", lineObj, bias)\n      }\n\n      function wrapX(pos, dir, side) {\n        var extent = wrappedLineExtentChar(cm, lineObj, null, pos);\n        var prop = (dir == \"ltr\") == (side == \"after\") ? \"left\" : \"right\";\n        var ch = side == \"after\" ? extent.begin : extent.end - (/\\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1);\n        return coords(ch, prop)[prop]\n      }\n\n      var order = getOrder(lineObj, doc.direction);\n      iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {\n        var ltr = dir == \"ltr\";\n        var fromPos = coords(from, ltr ? \"left\" : \"right\");\n        var toPos = coords(to - 1, ltr ? \"right\" : \"left\");\n\n        var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen;\n        var first = i == 0, last = !order || i == order.length - 1;\n        if (toPos.top - fromPos.top <= 3) { // Single line\n          var openLeft = (docLTR ? openStart : openEnd) && first;\n          var openRight = (docLTR ? openEnd : openStart) && last;\n          var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left;\n          var right = openRight ? rightSide : (ltr ? toPos : fromPos).right;\n          add(left, fromPos.top, right - left, fromPos.bottom);\n        } else { // Multiple lines\n          var topLeft, topRight, botLeft, botRight;\n          if (ltr) {\n            topLeft = docLTR && openStart && first ? leftSide : fromPos.left;\n            topRight = docLTR ? rightSide : wrapX(from, dir, \"before\");\n            botLeft = docLTR ? leftSide : wrapX(to, dir, \"after\");\n            botRight = docLTR && openEnd && last ? rightSide : toPos.right;\n          } else {\n            topLeft = !docLTR ? leftSide : wrapX(from, dir, \"before\");\n            topRight = !docLTR && openStart && first ? rightSide : fromPos.right;\n            botLeft = !docLTR && openEnd && last ? leftSide : toPos.left;\n            botRight = !docLTR ? rightSide : wrapX(to, dir, \"after\");\n          }\n          add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom);\n          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); }\n          add(botLeft, toPos.top, botRight - botLeft, toPos.bottom);\n        }\n\n        if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; }\n        if (cmpCoords(toPos, start) < 0) { start = toPos; }\n        if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; }\n        if (cmpCoords(toPos, end) < 0) { end = toPos; }\n      });\n      return {start: start, end: end}\n    }\n\n    var sFrom = range.from(), sTo = range.to();\n    if (sFrom.line == sTo.line) {\n      drawForLine(sFrom.line, sFrom.ch, sTo.ch);\n    } else {\n      var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);\n      var singleVLine = visualLine(fromLine) == visualLine(toLine);\n      var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;\n      var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;\n      if (singleVLine) {\n        if (leftEnd.top < rightStart.top - 2) {\n          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);\n          add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);\n        } else {\n          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);\n        }\n      }\n      if (leftEnd.bottom < rightStart.top)\n        { add(leftSide, leftEnd.bottom, null, rightStart.top); }\n    }\n\n    output.appendChild(fragment);\n  }\n\n  // Cursor-blinking\n  function restartBlink(cm) {\n    if (!cm.state.focused) { return }\n    var display = cm.display;\n    clearInterval(display.blinker);\n    var on = true;\n    display.cursorDiv.style.visibility = \"\";\n    if (cm.options.cursorBlinkRate > 0)\n      { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? \"\" : \"hidden\"; },\n        cm.options.cursorBlinkRate); }\n    else if (cm.options.cursorBlinkRate < 0)\n      { display.cursorDiv.style.visibility = \"hidden\"; }\n  }\n\n  function ensureFocus(cm) {\n    if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }\n  }\n\n  function delayBlurEvent(cm) {\n    cm.state.delayingBlurEvent = true;\n    setTimeout(function () { if (cm.state.delayingBlurEvent) {\n      cm.state.delayingBlurEvent = false;\n      onBlur(cm);\n    } }, 100);\n  }\n\n  function onFocus(cm, e) {\n    if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; }\n\n    if (cm.options.readOnly == \"nocursor\") { return }\n    if (!cm.state.focused) {\n      signal(cm, \"focus\", cm, e);\n      cm.state.focused = true;\n      addClass(cm.display.wrapper, \"CodeMirror-focused\");\n      // This test prevents this from firing when a context\n      // menu is closed (since the input reset would kill the\n      // select-all detection hack)\n      if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {\n        cm.display.input.reset();\n        if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730\n      }\n      cm.display.input.receivedFocus();\n    }\n    restartBlink(cm);\n  }\n  function onBlur(cm, e) {\n    if (cm.state.delayingBlurEvent) { return }\n\n    if (cm.state.focused) {\n      signal(cm, \"blur\", cm, e);\n      cm.state.focused = false;\n      rmClass(cm.display.wrapper, \"CodeMirror-focused\");\n    }\n    clearInterval(cm.display.blinker);\n    setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150);\n  }\n\n  // Read the actual heights of the rendered lines, and update their\n  // stored heights to match.\n  function updateHeightsInViewport(cm) {\n    var display = cm.display;\n    var prevBottom = display.lineDiv.offsetTop;\n    for (var i = 0; i < display.view.length; i++) {\n      var cur = display.view[i], wrapping = cm.options.lineWrapping;\n      var height = (void 0), width = 0;\n      if (cur.hidden) { continue }\n      if (ie && ie_version < 8) {\n        var bot = cur.node.offsetTop + cur.node.offsetHeight;\n        height = bot - prevBottom;\n        prevBottom = bot;\n      } else {\n        var box = cur.node.getBoundingClientRect();\n        height = box.bottom - box.top;\n        // Check that lines don't extend past the right of the current\n        // editor width\n        if (!wrapping && cur.text.firstChild)\n          { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; }\n      }\n      var diff = cur.line.height - height;\n      if (diff > .005 || diff < -.005) {\n        updateLineHeight(cur.line, height);\n        updateWidgetHeight(cur.line);\n        if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)\n          { updateWidgetHeight(cur.rest[j]); } }\n      }\n      if (width > cm.display.sizerWidth) {\n        var chWidth = Math.ceil(width / charWidth(cm.display));\n        if (chWidth > cm.display.maxLineLength) {\n          cm.display.maxLineLength = chWidth;\n          cm.display.maxLine = cur.line;\n          cm.display.maxLineChanged = true;\n        }\n      }\n    }\n  }\n\n  // Read and store the height of line widgets associated with the\n  // given line.\n  function updateWidgetHeight(line) {\n    if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) {\n      var w = line.widgets[i], parent = w.node.parentNode;\n      if (parent) { w.height = parent.offsetHeight; }\n    } }\n  }\n\n  // Compute the lines that are visible in a given viewport (defaults\n  // the the current scroll position). viewport may contain top,\n  // height, and ensure (see op.scrollToPos) properties.\n  function visibleLines(display, doc, viewport) {\n    var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;\n    top = Math.floor(top - paddingTop(display));\n    var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;\n\n    var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);\n    // Ensure is a {from: {line, ch}, to: {line, ch}} object, and\n    // forces those lines into the viewport (if possible).\n    if (viewport && viewport.ensure) {\n      var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;\n      if (ensureFrom < from) {\n        from = ensureFrom;\n        to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);\n      } else if (Math.min(ensureTo, doc.lastLine()) >= to) {\n        from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);\n        to = ensureTo;\n      }\n    }\n    return {from: from, to: Math.max(to, from + 1)}\n  }\n\n  // SCROLLING THINGS INTO VIEW\n\n  // If an editor sits on the top or bottom of the window, partially\n  // scrolled out of view, this ensures that the cursor is visible.\n  function maybeScrollWindow(cm, rect) {\n    if (signalDOMEvent(cm, \"scrollCursorIntoView\")) { return }\n\n    var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;\n    if (rect.top + box.top < 0) { doScroll = true; }\n    else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; }\n    if (doScroll != null && !phantom) {\n      var scrollNode = elt(\"div\", \"\\u200b\", null, (\"position: absolute;\\n                         top: \" + (rect.top - display.viewOffset - paddingTop(cm.display)) + \"px;\\n                         height: \" + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + \"px;\\n                         left: \" + (rect.left) + \"px; width: \" + (Math.max(2, rect.right - rect.left)) + \"px;\"));\n      cm.display.lineSpace.appendChild(scrollNode);\n      scrollNode.scrollIntoView(doScroll);\n      cm.display.lineSpace.removeChild(scrollNode);\n    }\n  }\n\n  // Scroll a given position into view (immediately), verifying that\n  // it actually became visible (as line heights are accurately\n  // measured, the position of something may 'drift' during drawing).\n  function scrollPosIntoView(cm, pos, end, margin) {\n    if (margin == null) { margin = 0; }\n    var rect;\n    if (!cm.options.lineWrapping && pos == end) {\n      // Set pos and end to the cursor positions around the character pos sticks to\n      // If pos.sticky == \"before\", that is around pos.ch - 1, otherwise around pos.ch\n      // If pos == Pos(_, 0, \"before\"), pos and end are unchanged\n      pos = pos.ch ? Pos(pos.line, pos.sticky == \"before\" ? pos.ch - 1 : pos.ch, \"after\") : pos;\n      end = pos.sticky == \"before\" ? Pos(pos.line, pos.ch + 1, \"before\") : pos;\n    }\n    for (var limit = 0; limit < 5; limit++) {\n      var changed = false;\n      var coords = cursorCoords(cm, pos);\n      var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);\n      rect = {left: Math.min(coords.left, endCoords.left),\n              top: Math.min(coords.top, endCoords.top) - margin,\n              right: Math.max(coords.left, endCoords.left),\n              bottom: Math.max(coords.bottom, endCoords.bottom) + margin};\n      var scrollPos = calculateScrollPos(cm, rect);\n      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;\n      if (scrollPos.scrollTop != null) {\n        updateScrollTop(cm, scrollPos.scrollTop);\n        if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; }\n      }\n      if (scrollPos.scrollLeft != null) {\n        setScrollLeft(cm, scrollPos.scrollLeft);\n        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; }\n      }\n      if (!changed) { break }\n    }\n    return rect\n  }\n\n  // Scroll a given set of coordinates into view (immediately).\n  function scrollIntoView(cm, rect) {\n    var scrollPos = calculateScrollPos(cm, rect);\n    if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); }\n    if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); }\n  }\n\n  // Calculate a new scroll position needed to scroll the given\n  // rectangle into view. Returns an object with scrollTop and\n  // scrollLeft properties. When these are undefined, the\n  // vertical/horizontal position does not need to be adjusted.\n  function calculateScrollPos(cm, rect) {\n    var display = cm.display, snapMargin = textHeight(cm.display);\n    if (rect.top < 0) { rect.top = 0; }\n    var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;\n    var screen = displayHeight(cm), result = {};\n    if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; }\n    var docBottom = cm.doc.height + paddingVert(display);\n    var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin;\n    if (rect.top < screentop) {\n      result.scrollTop = atTop ? 0 : rect.top;\n    } else if (rect.bottom > screentop + screen) {\n      var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen);\n      if (newTop != screentop) { result.scrollTop = newTop; }\n    }\n\n    var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;\n    var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);\n    var tooWide = rect.right - rect.left > screenw;\n    if (tooWide) { rect.right = rect.left + screenw; }\n    if (rect.left < 10)\n      { result.scrollLeft = 0; }\n    else if (rect.left < screenleft)\n      { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); }\n    else if (rect.right > screenw + screenleft - 3)\n      { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; }\n    return result\n  }\n\n  // Store a relative adjustment to the scroll position in the current\n  // operation (to be applied when the operation finishes).\n  function addToScrollTop(cm, top) {\n    if (top == null) { return }\n    resolveScrollToPos(cm);\n    cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;\n  }\n\n  // Make sure that at the end of the operation the current cursor is\n  // shown.\n  function ensureCursorVisible(cm) {\n    resolveScrollToPos(cm);\n    var cur = cm.getCursor();\n    cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin};\n  }\n\n  function scrollToCoords(cm, x, y) {\n    if (x != null || y != null) { resolveScrollToPos(cm); }\n    if (x != null) { cm.curOp.scrollLeft = x; }\n    if (y != null) { cm.curOp.scrollTop = y; }\n  }\n\n  function scrollToRange(cm, range) {\n    resolveScrollToPos(cm);\n    cm.curOp.scrollToPos = range;\n  }\n\n  // When an operation has its scrollToPos property set, and another\n  // scroll action is applied before the end of the operation, this\n  // 'simulates' scrolling that position into view in a cheap way, so\n  // that the effect of intermediate scroll commands is not ignored.\n  function resolveScrollToPos(cm) {\n    var range = cm.curOp.scrollToPos;\n    if (range) {\n      cm.curOp.scrollToPos = null;\n      var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to);\n      scrollToCoordsRange(cm, from, to, range.margin);\n    }\n  }\n\n  function scrollToCoordsRange(cm, from, to, margin) {\n    var sPos = calculateScrollPos(cm, {\n      left: Math.min(from.left, to.left),\n      top: Math.min(from.top, to.top) - margin,\n      right: Math.max(from.right, to.right),\n      bottom: Math.max(from.bottom, to.bottom) + margin\n    });\n    scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop);\n  }\n\n  // Sync the scrollable area and scrollbars, ensure the viewport\n  // covers the visible area.\n  function updateScrollTop(cm, val) {\n    if (Math.abs(cm.doc.scrollTop - val) < 2) { return }\n    if (!gecko) { updateDisplaySimple(cm, {top: val}); }\n    setScrollTop(cm, val, true);\n    if (gecko) { updateDisplaySimple(cm); }\n    startWorker(cm, 100);\n  }\n\n  function setScrollTop(cm, val, forceScroll) {\n    val = Math.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val));\n    if (cm.display.scroller.scrollTop == val && !forceScroll) { return }\n    cm.doc.scrollTop = val;\n    cm.display.scrollbars.setScrollTop(val);\n    if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; }\n  }\n\n  // Sync scroller and scrollbar, ensure the gutter elements are\n  // aligned.\n  function setScrollLeft(cm, val, isScroller, forceScroll) {\n    val = Math.max(0, Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth));\n    if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return }\n    cm.doc.scrollLeft = val;\n    alignHorizontally(cm);\n    if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; }\n    cm.display.scrollbars.setScrollLeft(val);\n  }\n\n  // SCROLLBARS\n\n  // Prepare DOM reads needed to update the scrollbars. Done in one\n  // shot to minimize update/measure roundtrips.\n  function measureForScrollbars(cm) {\n    var d = cm.display, gutterW = d.gutters.offsetWidth;\n    var docH = Math.round(cm.doc.height + paddingVert(cm.display));\n    return {\n      clientHeight: d.scroller.clientHeight,\n      viewHeight: d.wrapper.clientHeight,\n      scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,\n      viewWidth: d.wrapper.clientWidth,\n      barLeft: cm.options.fixedGutter ? gutterW : 0,\n      docHeight: docH,\n      scrollHeight: docH + scrollGap(cm) + d.barHeight,\n      nativeBarWidth: d.nativeBarWidth,\n      gutterWidth: gutterW\n    }\n  }\n\n  var NativeScrollbars = function(place, scroll, cm) {\n    this.cm = cm;\n    var vert = this.vert = elt(\"div\", [elt(\"div\", null, null, \"min-width: 1px\")], \"CodeMirror-vscrollbar\");\n    var horiz = this.horiz = elt(\"div\", [elt(\"div\", null, null, \"height: 100%; min-height: 1px\")], \"CodeMirror-hscrollbar\");\n    vert.tabIndex = horiz.tabIndex = -1;\n    place(vert); place(horiz);\n\n    on(vert, \"scroll\", function () {\n      if (vert.clientHeight) { scroll(vert.scrollTop, \"vertical\"); }\n    });\n    on(horiz, \"scroll\", function () {\n      if (horiz.clientWidth) { scroll(horiz.scrollLeft, \"horizontal\"); }\n    });\n\n    this.checkedZeroWidth = false;\n    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).\n    if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = \"18px\"; }\n  };\n\n  NativeScrollbars.prototype.update = function (measure) {\n    var needsH = measure.scrollWidth > measure.clientWidth + 1;\n    var needsV = measure.scrollHeight > measure.clientHeight + 1;\n    var sWidth = measure.nativeBarWidth;\n\n    if (needsV) {\n      this.vert.style.display = \"block\";\n      this.vert.style.bottom = needsH ? sWidth + \"px\" : \"0\";\n      var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);\n      // A bug in IE8 can cause this value to be negative, so guard it.\n      this.vert.firstChild.style.height =\n        Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + \"px\";\n    } else {\n      this.vert.style.display = \"\";\n      this.vert.firstChild.style.height = \"0\";\n    }\n\n    if (needsH) {\n      this.horiz.style.display = \"block\";\n      this.horiz.style.right = needsV ? sWidth + \"px\" : \"0\";\n      this.horiz.style.left = measure.barLeft + \"px\";\n      var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);\n      this.horiz.firstChild.style.width =\n        Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + \"px\";\n    } else {\n      this.horiz.style.display = \"\";\n      this.horiz.firstChild.style.width = \"0\";\n    }\n\n    if (!this.checkedZeroWidth && measure.clientHeight > 0) {\n      if (sWidth == 0) { this.zeroWidthHack(); }\n      this.checkedZeroWidth = true;\n    }\n\n    return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}\n  };\n\n  NativeScrollbars.prototype.setScrollLeft = function (pos) {\n    if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; }\n    if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, \"horiz\"); }\n  };\n\n  NativeScrollbars.prototype.setScrollTop = function (pos) {\n    if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; }\n    if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, \"vert\"); }\n  };\n\n  NativeScrollbars.prototype.zeroWidthHack = function () {\n    var w = mac && !mac_geMountainLion ? \"12px\" : \"18px\";\n    this.horiz.style.height = this.vert.style.width = w;\n    this.horiz.style.pointerEvents = this.vert.style.pointerEvents = \"none\";\n    this.disableHoriz = new Delayed;\n    this.disableVert = new Delayed;\n  };\n\n  NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {\n    bar.style.pointerEvents = \"auto\";\n    function maybeDisable() {\n      // To find out whether the scrollbar is still visible, we\n      // check whether the element under the pixel in the bottom\n      // right corner of the scrollbar box is the scrollbar box\n      // itself (when the bar is still visible) or its filler child\n      // (when the bar is hidden). If it is still visible, we keep\n      // it enabled, if it's hidden, we disable pointer events.\n      var box = bar.getBoundingClientRect();\n      var elt = type == \"vert\" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)\n          : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1);\n      if (elt != bar) { bar.style.pointerEvents = \"none\"; }\n      else { delay.set(1000, maybeDisable); }\n    }\n    delay.set(1000, maybeDisable);\n  };\n\n  NativeScrollbars.prototype.clear = function () {\n    var parent = this.horiz.parentNode;\n    parent.removeChild(this.horiz);\n    parent.removeChild(this.vert);\n  };\n\n  var NullScrollbars = function () {};\n\n  NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };\n  NullScrollbars.prototype.setScrollLeft = function () {};\n  NullScrollbars.prototype.setScrollTop = function () {};\n  NullScrollbars.prototype.clear = function () {};\n\n  function updateScrollbars(cm, measure) {\n    if (!measure) { measure = measureForScrollbars(cm); }\n    var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;\n    updateScrollbarsInner(cm, measure);\n    for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {\n      if (startWidth != cm.display.barWidth && cm.options.lineWrapping)\n        { updateHeightsInViewport(cm); }\n      updateScrollbarsInner(cm, measureForScrollbars(cm));\n      startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;\n    }\n  }\n\n  // Re-synchronize the fake scrollbars with the actual size of the\n  // content.\n  function updateScrollbarsInner(cm, measure) {\n    var d = cm.display;\n    var sizes = d.scrollbars.update(measure);\n\n    d.sizer.style.paddingRight = (d.barWidth = sizes.right) + \"px\";\n    d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + \"px\";\n    d.heightForcer.style.borderBottom = sizes.bottom + \"px solid transparent\";\n\n    if (sizes.right && sizes.bottom) {\n      d.scrollbarFiller.style.display = \"block\";\n      d.scrollbarFiller.style.height = sizes.bottom + \"px\";\n      d.scrollbarFiller.style.width = sizes.right + \"px\";\n    } else { d.scrollbarFiller.style.display = \"\"; }\n    if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {\n      d.gutterFiller.style.display = \"block\";\n      d.gutterFiller.style.height = sizes.bottom + \"px\";\n      d.gutterFiller.style.width = measure.gutterWidth + \"px\";\n    } else { d.gutterFiller.style.display = \"\"; }\n  }\n\n  var scrollbarModel = {\"native\": NativeScrollbars, \"null\": NullScrollbars};\n\n  function initScrollbars(cm) {\n    if (cm.display.scrollbars) {\n      cm.display.scrollbars.clear();\n      if (cm.display.scrollbars.addClass)\n        { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); }\n    }\n\n    cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {\n      cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);\n      // Prevent clicks in the scrollbars from killing focus\n      on(node, \"mousedown\", function () {\n        if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); }\n      });\n      node.setAttribute(\"cm-not-content\", \"true\");\n    }, function (pos, axis) {\n      if (axis == \"horizontal\") { setScrollLeft(cm, pos); }\n      else { updateScrollTop(cm, pos); }\n    }, cm);\n    if (cm.display.scrollbars.addClass)\n      { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); }\n  }\n\n  // Operations are used to wrap a series of changes to the editor\n  // state in such a way that each change won't have to update the\n  // cursor and display (which would be awkward, slow, and\n  // error-prone). Instead, display updates are batched and then all\n  // combined and executed at once.\n\n  var nextOpId = 0;\n  // Start a new operation.\n  function startOperation(cm) {\n    cm.curOp = {\n      cm: cm,\n      viewChanged: false,      // Flag that indicates that lines might need to be redrawn\n      startHeight: cm.doc.height, // Used to detect need to update scrollbar\n      forceUpdate: false,      // Used to force a redraw\n      updateInput: 0,       // Whether to reset the input textarea\n      typing: false,           // Whether this reset should be careful to leave existing text (for compositing)\n      changeObjs: null,        // Accumulated changes, for firing change events\n      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on\n      cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already\n      selectionChanged: false, // Whether the selection needs to be redrawn\n      updateMaxLine: false,    // Set when the widest line needs to be determined anew\n      scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet\n      scrollToPos: null,       // Used to scroll to a specific position\n      focus: false,\n      id: ++nextOpId           // Unique ID\n    };\n    pushOperation(cm.curOp);\n  }\n\n  // Finish an operation, updating the display and signalling delayed events\n  function endOperation(cm) {\n    var op = cm.curOp;\n    if (op) { finishOperation(op, function (group) {\n      for (var i = 0; i < group.ops.length; i++)\n        { group.ops[i].cm.curOp = null; }\n      endOperations(group);\n    }); }\n  }\n\n  // The DOM updates done when an operation finishes are batched so\n  // that the minimum number of relayouts are required.\n  function endOperations(group) {\n    var ops = group.ops;\n    for (var i = 0; i < ops.length; i++) // Read DOM\n      { endOperation_R1(ops[i]); }\n    for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)\n      { endOperation_W1(ops[i$1]); }\n    for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM\n      { endOperation_R2(ops[i$2]); }\n    for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)\n      { endOperation_W2(ops[i$3]); }\n    for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM\n      { endOperation_finish(ops[i$4]); }\n  }\n\n  function endOperation_R1(op) {\n    var cm = op.cm, display = cm.display;\n    maybeClipScrollbars(cm);\n    if (op.updateMaxLine) { findMaxLine(cm); }\n\n    op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||\n      op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||\n                         op.scrollToPos.to.line >= display.viewTo) ||\n      display.maxLineChanged && cm.options.lineWrapping;\n    op.update = op.mustUpdate &&\n      new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);\n  }\n\n  function endOperation_W1(op) {\n    op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);\n  }\n\n  function endOperation_R2(op) {\n    var cm = op.cm, display = cm.display;\n    if (op.updatedDisplay) { updateHeightsInViewport(cm); }\n\n    op.barMeasure = measureForScrollbars(cm);\n\n    // If the max line changed since it was last measured, measure it,\n    // and ensure the document's width matches it.\n    // updateDisplay_W2 will use these properties to do the actual resizing\n    if (display.maxLineChanged && !cm.options.lineWrapping) {\n      op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;\n      cm.display.sizerWidth = op.adjustWidthTo;\n      op.barMeasure.scrollWidth =\n        Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);\n      op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));\n    }\n\n    if (op.updatedDisplay || op.selectionChanged)\n      { op.preparedSelection = display.input.prepareSelection(); }\n  }\n\n  function endOperation_W2(op) {\n    var cm = op.cm;\n\n    if (op.adjustWidthTo != null) {\n      cm.display.sizer.style.minWidth = op.adjustWidthTo + \"px\";\n      if (op.maxScrollLeft < cm.doc.scrollLeft)\n        { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); }\n      cm.display.maxLineChanged = false;\n    }\n\n    var takeFocus = op.focus && op.focus == activeElt();\n    if (op.preparedSelection)\n      { cm.display.input.showSelection(op.preparedSelection, takeFocus); }\n    if (op.updatedDisplay || op.startHeight != cm.doc.height)\n      { updateScrollbars(cm, op.barMeasure); }\n    if (op.updatedDisplay)\n      { setDocumentHeight(cm, op.barMeasure); }\n\n    if (op.selectionChanged) { restartBlink(cm); }\n\n    if (cm.state.focused && op.updateInput)\n      { cm.display.input.reset(op.typing); }\n    if (takeFocus) { ensureFocus(op.cm); }\n  }\n\n  function endOperation_finish(op) {\n    var cm = op.cm, display = cm.display, doc = cm.doc;\n\n    if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); }\n\n    // Abort mouse wheel delta measurement, when scrolling explicitly\n    if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))\n      { display.wheelStartX = display.wheelStartY = null; }\n\n    // Propagate the scroll position to the actual DOM scroller\n    if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); }\n\n    if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); }\n    // If we need to scroll a specific position into view, do so.\n    if (op.scrollToPos) {\n      var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),\n                                   clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);\n      maybeScrollWindow(cm, rect);\n    }\n\n    // Fire events for markers that are hidden/unidden by editing or\n    // undoing\n    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;\n    if (hidden) { for (var i = 0; i < hidden.length; ++i)\n      { if (!hidden[i].lines.length) { signal(hidden[i], \"hide\"); } } }\n    if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1)\n      { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], \"unhide\"); } } }\n\n    if (display.wrapper.offsetHeight)\n      { doc.scrollTop = cm.display.scroller.scrollTop; }\n\n    // Fire change events, and delayed event handlers\n    if (op.changeObjs)\n      { signal(cm, \"changes\", cm, op.changeObjs); }\n    if (op.update)\n      { op.update.finish(); }\n  }\n\n  // Run the given function in an operation\n  function runInOp(cm, f) {\n    if (cm.curOp) { return f() }\n    startOperation(cm);\n    try { return f() }\n    finally { endOperation(cm); }\n  }\n  // Wraps a function in an operation. Returns the wrapped function.\n  function operation(cm, f) {\n    return function() {\n      if (cm.curOp) { return f.apply(cm, arguments) }\n      startOperation(cm);\n      try { return f.apply(cm, arguments) }\n      finally { endOperation(cm); }\n    }\n  }\n  // Used to add methods to editor and doc instances, wrapping them in\n  // operations.\n  function methodOp(f) {\n    return function() {\n      if (this.curOp) { return f.apply(this, arguments) }\n      startOperation(this);\n      try { return f.apply(this, arguments) }\n      finally { endOperation(this); }\n    }\n  }\n  function docMethodOp(f) {\n    return function() {\n      var cm = this.cm;\n      if (!cm || cm.curOp) { return f.apply(this, arguments) }\n      startOperation(cm);\n      try { return f.apply(this, arguments) }\n      finally { endOperation(cm); }\n    }\n  }\n\n  // HIGHLIGHT WORKER\n\n  function startWorker(cm, time) {\n    if (cm.doc.highlightFrontier < cm.display.viewTo)\n      { cm.state.highlight.set(time, bind(highlightWorker, cm)); }\n  }\n\n  function highlightWorker(cm) {\n    var doc = cm.doc;\n    if (doc.highlightFrontier >= cm.display.viewTo) { return }\n    var end = +new Date + cm.options.workTime;\n    var context = getContextBefore(cm, doc.highlightFrontier);\n    var changedLines = [];\n\n    doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) {\n      if (context.line >= cm.display.viewFrom) { // Visible\n        var oldStyles = line.styles;\n        var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null;\n        var highlighted = highlightLine(cm, line, context, true);\n        if (resetState) { context.state = resetState; }\n        line.styles = highlighted.styles;\n        var oldCls = line.styleClasses, newCls = highlighted.classes;\n        if (newCls) { line.styleClasses = newCls; }\n        else if (oldCls) { line.styleClasses = null; }\n        var ischange = !oldStyles || oldStyles.length != line.styles.length ||\n          oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);\n        for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; }\n        if (ischange) { changedLines.push(context.line); }\n        line.stateAfter = context.save();\n        context.nextLine();\n      } else {\n        if (line.text.length <= cm.options.maxHighlightLength)\n          { processLine(cm, line.text, context); }\n        line.stateAfter = context.line % 5 == 0 ? context.save() : null;\n        context.nextLine();\n      }\n      if (+new Date > end) {\n        startWorker(cm, cm.options.workDelay);\n        return true\n      }\n    });\n    doc.highlightFrontier = context.line;\n    doc.modeFrontier = Math.max(doc.modeFrontier, context.line);\n    if (changedLines.length) { runInOp(cm, function () {\n      for (var i = 0; i < changedLines.length; i++)\n        { regLineChange(cm, changedLines[i], \"text\"); }\n    }); }\n  }\n\n  // DISPLAY DRAWING\n\n  var DisplayUpdate = function(cm, viewport, force) {\n    var display = cm.display;\n\n    this.viewport = viewport;\n    // Store some values that we'll need later (but don't want to force a relayout for)\n    this.visible = visibleLines(display, cm.doc, viewport);\n    this.editorIsHidden = !display.wrapper.offsetWidth;\n    this.wrapperHeight = display.wrapper.clientHeight;\n    this.wrapperWidth = display.wrapper.clientWidth;\n    this.oldDisplayWidth = displayWidth(cm);\n    this.force = force;\n    this.dims = getDimensions(cm);\n    this.events = [];\n  };\n\n  DisplayUpdate.prototype.signal = function (emitter, type) {\n    if (hasHandler(emitter, type))\n      { this.events.push(arguments); }\n  };\n  DisplayUpdate.prototype.finish = function () {\n    for (var i = 0; i < this.events.length; i++)\n      { signal.apply(null, this.events[i]); }\n  };\n\n  function maybeClipScrollbars(cm) {\n    var display = cm.display;\n    if (!display.scrollbarsClipped && display.scroller.offsetWidth) {\n      display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;\n      display.heightForcer.style.height = scrollGap(cm) + \"px\";\n      display.sizer.style.marginBottom = -display.nativeBarWidth + \"px\";\n      display.sizer.style.borderRightWidth = scrollGap(cm) + \"px\";\n      display.scrollbarsClipped = true;\n    }\n  }\n\n  function selectionSnapshot(cm) {\n    if (cm.hasFocus()) { return null }\n    var active = activeElt();\n    if (!active || !contains(cm.display.lineDiv, active)) { return null }\n    var result = {activeElt: active};\n    if (window.getSelection) {\n      var sel = window.getSelection();\n      if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {\n        result.anchorNode = sel.anchorNode;\n        result.anchorOffset = sel.anchorOffset;\n        result.focusNode = sel.focusNode;\n        result.focusOffset = sel.focusOffset;\n      }\n    }\n    return result\n  }\n\n  function restoreSelection(snapshot) {\n    if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }\n    snapshot.activeElt.focus();\n    if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&\n        snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {\n      var sel = window.getSelection(), range = document.createRange();\n      range.setEnd(snapshot.anchorNode, snapshot.anchorOffset);\n      range.collapse(false);\n      sel.removeAllRanges();\n      sel.addRange(range);\n      sel.extend(snapshot.focusNode, snapshot.focusOffset);\n    }\n  }\n\n  // Does the actual updating of the line display. Bails out\n  // (returning false) when there is nothing to be done and forced is\n  // false.\n  function updateDisplayIfNeeded(cm, update) {\n    var display = cm.display, doc = cm.doc;\n\n    if (update.editorIsHidden) {\n      resetView(cm);\n      return false\n    }\n\n    // Bail out if the visible area is already rendered and nothing changed.\n    if (!update.force &&\n        update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&\n        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&\n        display.renderedView == display.view && countDirtyView(cm) == 0)\n      { return false }\n\n    if (maybeUpdateLineNumberWidth(cm)) {\n      resetView(cm);\n      update.dims = getDimensions(cm);\n    }\n\n    // Compute a suitable new viewport (from & to)\n    var end = doc.first + doc.size;\n    var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);\n    var to = Math.min(end, update.visible.to + cm.options.viewportMargin);\n    if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); }\n    if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); }\n    if (sawCollapsedSpans) {\n      from = visualLineNo(cm.doc, from);\n      to = visualLineEndNo(cm.doc, to);\n    }\n\n    var different = from != display.viewFrom || to != display.viewTo ||\n      display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;\n    adjustView(cm, from, to);\n\n    display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));\n    // Position the mover div to align with the current scroll position\n    cm.display.mover.style.top = display.viewOffset + \"px\";\n\n    var toUpdate = countDirtyView(cm);\n    if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&\n        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))\n      { return false }\n\n    // For big changes, we hide the enclosing element during the\n    // update, since that speeds up the operations on most browsers.\n    var selSnapshot = selectionSnapshot(cm);\n    if (toUpdate > 4) { display.lineDiv.style.display = \"none\"; }\n    patchDisplay(cm, display.updateLineNumbers, update.dims);\n    if (toUpdate > 4) { display.lineDiv.style.display = \"\"; }\n    display.renderedView = display.view;\n    // There might have been a widget with a focused element that got\n    // hidden or updated, if so re-focus it.\n    restoreSelection(selSnapshot);\n\n    // Prevent selection and cursors from interfering with the scroll\n    // width and height.\n    removeChildren(display.cursorDiv);\n    removeChildren(display.selectionDiv);\n    display.gutters.style.height = display.sizer.style.minHeight = 0;\n\n    if (different) {\n      display.lastWrapHeight = update.wrapperHeight;\n      display.lastWrapWidth = update.wrapperWidth;\n      startWorker(cm, 400);\n    }\n\n    display.updateLineNumbers = null;\n\n    return true\n  }\n\n  function postUpdateDisplay(cm, update) {\n    var viewport = update.viewport;\n\n    for (var first = true;; first = false) {\n      if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {\n        // Clip forced viewport to actual scrollable area.\n        if (viewport && viewport.top != null)\n          { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; }\n        // Updated line heights might result in the drawn area not\n        // actually covering the viewport. Keep looping until it does.\n        update.visible = visibleLines(cm.display, cm.doc, viewport);\n        if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)\n          { break }\n      } else if (first) {\n        update.visible = visibleLines(cm.display, cm.doc, viewport);\n      }\n      if (!updateDisplayIfNeeded(cm, update)) { break }\n      updateHeightsInViewport(cm);\n      var barMeasure = measureForScrollbars(cm);\n      updateSelection(cm);\n      updateScrollbars(cm, barMeasure);\n      setDocumentHeight(cm, barMeasure);\n      update.force = false;\n    }\n\n    update.signal(cm, \"update\", cm);\n    if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {\n      update.signal(cm, \"viewportChange\", cm, cm.display.viewFrom, cm.display.viewTo);\n      cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;\n    }\n  }\n\n  function updateDisplaySimple(cm, viewport) {\n    var update = new DisplayUpdate(cm, viewport);\n    if (updateDisplayIfNeeded(cm, update)) {\n      updateHeightsInViewport(cm);\n      postUpdateDisplay(cm, update);\n      var barMeasure = measureForScrollbars(cm);\n      updateSelection(cm);\n      updateScrollbars(cm, barMeasure);\n      setDocumentHeight(cm, barMeasure);\n      update.finish();\n    }\n  }\n\n  // Sync the actual display DOM structure with display.view, removing\n  // nodes for lines that are no longer in view, and creating the ones\n  // that are not there yet, and updating the ones that are out of\n  // date.\n  function patchDisplay(cm, updateNumbersFrom, dims) {\n    var display = cm.display, lineNumbers = cm.options.lineNumbers;\n    var container = display.lineDiv, cur = container.firstChild;\n\n    function rm(node) {\n      var next = node.nextSibling;\n      // Works around a throw-scroll bug in OS X Webkit\n      if (webkit && mac && cm.display.currentWheelTarget == node)\n        { node.style.display = \"none\"; }\n      else\n        { node.parentNode.removeChild(node); }\n      return next\n    }\n\n    var view = display.view, lineN = display.viewFrom;\n    // Loop over the elements in the view, syncing cur (the DOM nodes\n    // in display.lineDiv) with the view as we go.\n    for (var i = 0; i < view.length; i++) {\n      var lineView = view[i];\n      if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet\n        var node = buildLineElement(cm, lineView, lineN, dims);\n        container.insertBefore(node, cur);\n      } else { // Already drawn\n        while (cur != lineView.node) { cur = rm(cur); }\n        var updateNumber = lineNumbers && updateNumbersFrom != null &&\n          updateNumbersFrom <= lineN && lineView.lineNumber;\n        if (lineView.changes) {\n          if (indexOf(lineView.changes, \"gutter\") > -1) { updateNumber = false; }\n          updateLineForChanges(cm, lineView, lineN, dims);\n        }\n        if (updateNumber) {\n          removeChildren(lineView.lineNumber);\n          lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));\n        }\n        cur = lineView.node.nextSibling;\n      }\n      lineN += lineView.size;\n    }\n    while (cur) { cur = rm(cur); }\n  }\n\n  function updateGutterSpace(display) {\n    var width = display.gutters.offsetWidth;\n    display.sizer.style.marginLeft = width + \"px\";\n  }\n\n  function setDocumentHeight(cm, measure) {\n    cm.display.sizer.style.minHeight = measure.docHeight + \"px\";\n    cm.display.heightForcer.style.top = measure.docHeight + \"px\";\n    cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + \"px\";\n  }\n\n  // Re-align line numbers and gutter marks to compensate for\n  // horizontal scrolling.\n  function alignHorizontally(cm) {\n    var display = cm.display, view = display.view;\n    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return }\n    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;\n    var gutterW = display.gutters.offsetWidth, left = comp + \"px\";\n    for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {\n      if (cm.options.fixedGutter) {\n        if (view[i].gutter)\n          { view[i].gutter.style.left = left; }\n        if (view[i].gutterBackground)\n          { view[i].gutterBackground.style.left = left; }\n      }\n      var align = view[i].alignable;\n      if (align) { for (var j = 0; j < align.length; j++)\n        { align[j].style.left = left; } }\n    } }\n    if (cm.options.fixedGutter)\n      { display.gutters.style.left = (comp + gutterW) + \"px\"; }\n  }\n\n  // Used to ensure that the line number gutter is still the right\n  // size for the current document size. Returns true when an update\n  // is needed.\n  function maybeUpdateLineNumberWidth(cm) {\n    if (!cm.options.lineNumbers) { return false }\n    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;\n    if (last.length != display.lineNumChars) {\n      var test = display.measure.appendChild(elt(\"div\", [elt(\"div\", last)],\n                                                 \"CodeMirror-linenumber CodeMirror-gutter-elt\"));\n      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;\n      display.lineGutter.style.width = \"\";\n      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;\n      display.lineNumWidth = display.lineNumInnerWidth + padding;\n      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;\n      display.lineGutter.style.width = display.lineNumWidth + \"px\";\n      updateGutterSpace(cm.display);\n      return true\n    }\n    return false\n  }\n\n  function getGutters(gutters, lineNumbers) {\n    var result = [], sawLineNumbers = false;\n    for (var i = 0; i < gutters.length; i++) {\n      var name = gutters[i], style = null;\n      if (typeof name != \"string\") { style = name.style; name = name.className; }\n      if (name == \"CodeMirror-linenumbers\") {\n        if (!lineNumbers) { continue }\n        else { sawLineNumbers = true; }\n      }\n      result.push({className: name, style: style});\n    }\n    if (lineNumbers && !sawLineNumbers) { result.push({className: \"CodeMirror-linenumbers\", style: null}); }\n    return result\n  }\n\n  // Rebuild the gutter elements, ensure the margin to the left of the\n  // code matches their width.\n  function renderGutters(display) {\n    var gutters = display.gutters, specs = display.gutterSpecs;\n    removeChildren(gutters);\n    display.lineGutter = null;\n    for (var i = 0; i < specs.length; ++i) {\n      var ref = specs[i];\n      var className = ref.className;\n      var style = ref.style;\n      var gElt = gutters.appendChild(elt(\"div\", null, \"CodeMirror-gutter \" + className));\n      if (style) { gElt.style.cssText = style; }\n      if (className == \"CodeMirror-linenumbers\") {\n        display.lineGutter = gElt;\n        gElt.style.width = (display.lineNumWidth || 1) + \"px\";\n      }\n    }\n    gutters.style.display = specs.length ? \"\" : \"none\";\n    updateGutterSpace(display);\n  }\n\n  function updateGutters(cm) {\n    renderGutters(cm.display);\n    regChange(cm);\n    alignHorizontally(cm);\n  }\n\n  // The display handles the DOM integration, both for input reading\n  // and content drawing. It holds references to DOM nodes and\n  // display-related state.\n\n  function Display(place, doc, input, options) {\n    var d = this;\n    this.input = input;\n\n    // Covers bottom-right square when both scrollbars are present.\n    d.scrollbarFiller = elt(\"div\", null, \"CodeMirror-scrollbar-filler\");\n    d.scrollbarFiller.setAttribute(\"cm-not-content\", \"true\");\n    // Covers bottom of gutter when coverGutterNextToScrollbar is on\n    // and h scrollbar is present.\n    d.gutterFiller = elt(\"div\", null, \"CodeMirror-gutter-filler\");\n    d.gutterFiller.setAttribute(\"cm-not-content\", \"true\");\n    // Will contain the actual code, positioned to cover the viewport.\n    d.lineDiv = eltP(\"div\", null, \"CodeMirror-code\");\n    // Elements are added to these to represent selection and cursors.\n    d.selectionDiv = elt(\"div\", null, null, \"position: relative; z-index: 1\");\n    d.cursorDiv = elt(\"div\", null, \"CodeMirror-cursors\");\n    // A visibility: hidden element used to find the size of things.\n    d.measure = elt(\"div\", null, \"CodeMirror-measure\");\n    // When lines outside of the viewport are measured, they are drawn in this.\n    d.lineMeasure = elt(\"div\", null, \"CodeMirror-measure\");\n    // Wraps everything that needs to exist inside the vertically-padded coordinate system\n    d.lineSpace = eltP(\"div\", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],\n                      null, \"position: relative; outline: none\");\n    var lines = eltP(\"div\", [d.lineSpace], \"CodeMirror-lines\");\n    // Moved around its parent to cover visible view.\n    d.mover = elt(\"div\", [lines], null, \"position: relative\");\n    // Set to the height of the document, allowing scrolling.\n    d.sizer = elt(\"div\", [d.mover], \"CodeMirror-sizer\");\n    d.sizerWidth = null;\n    // Behavior of elts with overflow: auto and padding is\n    // inconsistent across browsers. This is used to ensure the\n    // scrollable area is big enough.\n    d.heightForcer = elt(\"div\", null, null, \"position: absolute; height: \" + scrollerGap + \"px; width: 1px;\");\n    // Will contain the gutters, if any.\n    d.gutters = elt(\"div\", null, \"CodeMirror-gutters\");\n    d.lineGutter = null;\n    // Actual scrollable element.\n    d.scroller = elt(\"div\", [d.sizer, d.heightForcer, d.gutters], \"CodeMirror-scroll\");\n    d.scroller.setAttribute(\"tabIndex\", \"-1\");\n    // The element in which the editor lives.\n    d.wrapper = elt(\"div\", [d.scrollbarFiller, d.gutterFiller, d.scroller], \"CodeMirror\");\n\n    // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)\n    if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }\n    if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; }\n\n    if (place) {\n      if (place.appendChild) { place.appendChild(d.wrapper); }\n      else { place(d.wrapper); }\n    }\n\n    // Current rendered range (may be bigger than the view window).\n    d.viewFrom = d.viewTo = doc.first;\n    d.reportedViewFrom = d.reportedViewTo = doc.first;\n    // Information about the rendered lines.\n    d.view = [];\n    d.renderedView = null;\n    // Holds info about a single rendered line when it was rendered\n    // for measurement, while not in view.\n    d.externalMeasured = null;\n    // Empty space (in pixels) above the view\n    d.viewOffset = 0;\n    d.lastWrapHeight = d.lastWrapWidth = 0;\n    d.updateLineNumbers = null;\n\n    d.nativeBarWidth = d.barHeight = d.barWidth = 0;\n    d.scrollbarsClipped = false;\n\n    // Used to only resize the line number gutter when necessary (when\n    // the amount of lines crosses a boundary that makes its width change)\n    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;\n    // Set to true when a non-horizontal-scrolling line widget is\n    // added. As an optimization, line widget aligning is skipped when\n    // this is false.\n    d.alignWidgets = false;\n\n    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;\n\n    // Tracks the maximum line length so that the horizontal scrollbar\n    // can be kept static when scrolling.\n    d.maxLine = null;\n    d.maxLineLength = 0;\n    d.maxLineChanged = false;\n\n    // Used for measuring wheel scrolling granularity\n    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;\n\n    // True when shift is held down.\n    d.shift = false;\n\n    // Used to track whether anything happened since the context menu\n    // was opened.\n    d.selForContextMenu = null;\n\n    d.activeTouch = null;\n\n    d.gutterSpecs = getGutters(options.gutters, options.lineNumbers);\n    renderGutters(d);\n\n    input.init(d);\n  }\n\n  // Since the delta values reported on mouse wheel events are\n  // unstandardized between browsers and even browser versions, and\n  // generally horribly unpredictable, this code starts by measuring\n  // the scroll effect that the first few mouse wheel events have,\n  // and, from that, detects the way it can convert deltas to pixel\n  // offsets afterwards.\n  //\n  // The reason we want to know the amount a wheel event will scroll\n  // is that it gives us a chance to update the display before the\n  // actual scrolling happens, reducing flickering.\n\n  var wheelSamples = 0, wheelPixelsPerUnit = null;\n  // Fill in a browser-detected starting value on browsers where we\n  // know one. These don't have to be accurate -- the result of them\n  // being wrong would just be a slight flicker on the first wheel\n  // scroll (if it is large enough).\n  if (ie) { wheelPixelsPerUnit = -.53; }\n  else if (gecko) { wheelPixelsPerUnit = 15; }\n  else if (chrome) { wheelPixelsPerUnit = -.7; }\n  else if (safari) { wheelPixelsPerUnit = -1/3; }\n\n  function wheelEventDelta(e) {\n    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;\n    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; }\n    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; }\n    else if (dy == null) { dy = e.wheelDelta; }\n    return {x: dx, y: dy}\n  }\n  function wheelEventPixels(e) {\n    var delta = wheelEventDelta(e);\n    delta.x *= wheelPixelsPerUnit;\n    delta.y *= wheelPixelsPerUnit;\n    return delta\n  }\n\n  function onScrollWheel(cm, e) {\n    var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;\n\n    var display = cm.display, scroll = display.scroller;\n    // Quit if there's nothing to scroll here\n    var canScrollX = scroll.scrollWidth > scroll.clientWidth;\n    var canScrollY = scroll.scrollHeight > scroll.clientHeight;\n    if (!(dx && canScrollX || dy && canScrollY)) { return }\n\n    // Webkit browsers on OS X abort momentum scrolls when the target\n    // of the scroll event is removed from the scrollable element.\n    // This hack (see related code in patchDisplay) makes sure the\n    // element is kept around.\n    if (dy && mac && webkit) {\n      outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {\n        for (var i = 0; i < view.length; i++) {\n          if (view[i].node == cur) {\n            cm.display.currentWheelTarget = cur;\n            break outer\n          }\n        }\n      }\n    }\n\n    // On some browsers, horizontal scrolling will cause redraws to\n    // happen before the gutter has been realigned, causing it to\n    // wriggle around in a most unseemly way. When we have an\n    // estimated pixels/delta value, we just handle horizontal\n    // scrolling entirely here. It'll be slightly off from native, but\n    // better than glitching out.\n    if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {\n      if (dy && canScrollY)\n        { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); }\n      setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit));\n      // Only prevent default scrolling if vertical scrolling is\n      // actually possible. Otherwise, it causes vertical scroll\n      // jitter on OSX trackpads when deltaX is small and deltaY\n      // is large (issue #3579)\n      if (!dy || (dy && canScrollY))\n        { e_preventDefault(e); }\n      display.wheelStartX = null; // Abort measurement, if in progress\n      return\n    }\n\n    // 'Project' the visible viewport to cover the area that is being\n    // scrolled into view (if we know enough to estimate it).\n    if (dy && wheelPixelsPerUnit != null) {\n      var pixels = dy * wheelPixelsPerUnit;\n      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;\n      if (pixels < 0) { top = Math.max(0, top + pixels - 50); }\n      else { bot = Math.min(cm.doc.height, bot + pixels + 50); }\n      updateDisplaySimple(cm, {top: top, bottom: bot});\n    }\n\n    if (wheelSamples < 20) {\n      if (display.wheelStartX == null) {\n        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;\n        display.wheelDX = dx; display.wheelDY = dy;\n        setTimeout(function () {\n          if (display.wheelStartX == null) { return }\n          var movedX = scroll.scrollLeft - display.wheelStartX;\n          var movedY = scroll.scrollTop - display.wheelStartY;\n          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||\n            (movedX && display.wheelDX && movedX / display.wheelDX);\n          display.wheelStartX = display.wheelStartY = null;\n          if (!sample) { return }\n          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);\n          ++wheelSamples;\n        }, 200);\n      } else {\n        display.wheelDX += dx; display.wheelDY += dy;\n      }\n    }\n  }\n\n  // Selection objects are immutable. A new one is created every time\n  // the selection changes. A selection is one or more non-overlapping\n  // (and non-touching) ranges, sorted, and an integer that indicates\n  // which one is the primary selection (the one that's scrolled into\n  // view, that getCursor returns, etc).\n  var Selection = function(ranges, primIndex) {\n    this.ranges = ranges;\n    this.primIndex = primIndex;\n  };\n\n  Selection.prototype.primary = function () { return this.ranges[this.primIndex] };\n\n  Selection.prototype.equals = function (other) {\n    if (other == this) { return true }\n    if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false }\n    for (var i = 0; i < this.ranges.length; i++) {\n      var here = this.ranges[i], there = other.ranges[i];\n      if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }\n    }\n    return true\n  };\n\n  Selection.prototype.deepCopy = function () {\n    var out = [];\n    for (var i = 0; i < this.ranges.length; i++)\n      { out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); }\n    return new Selection(out, this.primIndex)\n  };\n\n  Selection.prototype.somethingSelected = function () {\n    for (var i = 0; i < this.ranges.length; i++)\n      { if (!this.ranges[i].empty()) { return true } }\n    return false\n  };\n\n  Selection.prototype.contains = function (pos, end) {\n    if (!end) { end = pos; }\n    for (var i = 0; i < this.ranges.length; i++) {\n      var range = this.ranges[i];\n      if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)\n        { return i }\n    }\n    return -1\n  };\n\n  var Range = function(anchor, head) {\n    this.anchor = anchor; this.head = head;\n  };\n\n  Range.prototype.from = function () { return minPos(this.anchor, this.head) };\n  Range.prototype.to = function () { return maxPos(this.anchor, this.head) };\n  Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch };\n\n  // Take an unsorted, potentially overlapping set of ranges, and\n  // build a selection out of it. 'Consumes' ranges array (modifying\n  // it).\n  function normalizeSelection(cm, ranges, primIndex) {\n    var mayTouch = cm && cm.options.selectionsMayTouch;\n    var prim = ranges[primIndex];\n    ranges.sort(function (a, b) { return cmp(a.from(), b.from()); });\n    primIndex = indexOf(ranges, prim);\n    for (var i = 1; i < ranges.length; i++) {\n      var cur = ranges[i], prev = ranges[i - 1];\n      var diff = cmp(prev.to(), cur.from());\n      if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) {\n        var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());\n        var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;\n        if (i <= primIndex) { --primIndex; }\n        ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));\n      }\n    }\n    return new Selection(ranges, primIndex)\n  }\n\n  function simpleSelection(anchor, head) {\n    return new Selection([new Range(anchor, head || anchor)], 0)\n  }\n\n  // Compute the position of the end of a change (its 'to' property\n  // refers to the pre-change end).\n  function changeEnd(change) {\n    if (!change.text) { return change.to }\n    return Pos(change.from.line + change.text.length - 1,\n               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))\n  }\n\n  // Adjust a position to refer to the post-change position of the\n  // same text, or the end of the change if the change covers it.\n  function adjustForChange(pos, change) {\n    if (cmp(pos, change.from) < 0) { return pos }\n    if (cmp(pos, change.to) <= 0) { return changeEnd(change) }\n\n    var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;\n    if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; }\n    return Pos(line, ch)\n  }\n\n  function computeSelAfterChange(doc, change) {\n    var out = [];\n    for (var i = 0; i < doc.sel.ranges.length; i++) {\n      var range = doc.sel.ranges[i];\n      out.push(new Range(adjustForChange(range.anchor, change),\n                         adjustForChange(range.head, change)));\n    }\n    return normalizeSelection(doc.cm, out, doc.sel.primIndex)\n  }\n\n  function offsetPos(pos, old, nw) {\n    if (pos.line == old.line)\n      { return Pos(nw.line, pos.ch - old.ch + nw.ch) }\n    else\n      { return Pos(nw.line + (pos.line - old.line), pos.ch) }\n  }\n\n  // Used by replaceSelections to allow moving the selection to the\n  // start or around the replaced test. Hint may be \"start\" or \"around\".\n  function computeReplacedSel(doc, changes, hint) {\n    var out = [];\n    var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;\n    for (var i = 0; i < changes.length; i++) {\n      var change = changes[i];\n      var from = offsetPos(change.from, oldPrev, newPrev);\n      var to = offsetPos(changeEnd(change), oldPrev, newPrev);\n      oldPrev = change.to;\n      newPrev = to;\n      if (hint == \"around\") {\n        var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;\n        out[i] = new Range(inv ? to : from, inv ? from : to);\n      } else {\n        out[i] = new Range(from, from);\n      }\n    }\n    return new Selection(out, doc.sel.primIndex)\n  }\n\n  // Used to get the editor into a consistent state again when options change.\n\n  function loadMode(cm) {\n    cm.doc.mode = getMode(cm.options, cm.doc.modeOption);\n    resetModeState(cm);\n  }\n\n  function resetModeState(cm) {\n    cm.doc.iter(function (line) {\n      if (line.stateAfter) { line.stateAfter = null; }\n      if (line.styles) { line.styles = null; }\n    });\n    cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first;\n    startWorker(cm, 100);\n    cm.state.modeGen++;\n    if (cm.curOp) { regChange(cm); }\n  }\n\n  // DOCUMENT DATA STRUCTURE\n\n  // By default, updates that start and end at the beginning of a line\n  // are treated specially, in order to make the association of line\n  // widgets and marker elements with the text behave more intuitive.\n  function isWholeLineUpdate(doc, change) {\n    return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == \"\" &&\n      (!doc.cm || doc.cm.options.wholeLineUpdateBefore)\n  }\n\n  // Perform a change on the document data structure.\n  function updateDoc(doc, change, markedSpans, estimateHeight) {\n    function spansFor(n) {return markedSpans ? markedSpans[n] : null}\n    function update(line, text, spans) {\n      updateLine(line, text, spans, estimateHeight);\n      signalLater(line, \"change\", line, change);\n    }\n    function linesFor(start, end) {\n      var result = [];\n      for (var i = start; i < end; ++i)\n        { result.push(new Line(text[i], spansFor(i), estimateHeight)); }\n      return result\n    }\n\n    var from = change.from, to = change.to, text = change.text;\n    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);\n    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;\n\n    // Adjust the line structure\n    if (change.full) {\n      doc.insert(0, linesFor(0, text.length));\n      doc.remove(text.length, doc.size - text.length);\n    } else if (isWholeLineUpdate(doc, change)) {\n      // This is a whole-line replace. Treated specially to make\n      // sure line objects move the way they are supposed to.\n      var added = linesFor(0, text.length - 1);\n      update(lastLine, lastLine.text, lastSpans);\n      if (nlines) { doc.remove(from.line, nlines); }\n      if (added.length) { doc.insert(from.line, added); }\n    } else if (firstLine == lastLine) {\n      if (text.length == 1) {\n        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);\n      } else {\n        var added$1 = linesFor(1, text.length - 1);\n        added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));\n        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n        doc.insert(from.line + 1, added$1);\n      }\n    } else if (text.length == 1) {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));\n      doc.remove(from.line + 1, nlines);\n    } else {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);\n      var added$2 = linesFor(1, text.length - 1);\n      if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); }\n      doc.insert(from.line + 1, added$2);\n    }\n\n    signalLater(doc, \"change\", doc, change);\n  }\n\n  // Call f for all linked documents.\n  function linkedDocs(doc, f, sharedHistOnly) {\n    function propagate(doc, skip, sharedHist) {\n      if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) {\n        var rel = doc.linked[i];\n        if (rel.doc == skip) { continue }\n        var shared = sharedHist && rel.sharedHist;\n        if (sharedHistOnly && !shared) { continue }\n        f(rel.doc, shared);\n        propagate(rel.doc, doc, shared);\n      } }\n    }\n    propagate(doc, null, true);\n  }\n\n  // Attach a document to an editor.\n  function attachDoc(cm, doc) {\n    if (doc.cm) { throw new Error(\"This document is already in use.\") }\n    cm.doc = doc;\n    doc.cm = cm;\n    estimateLineHeights(cm);\n    loadMode(cm);\n    setDirectionClass(cm);\n    if (!cm.options.lineWrapping) { findMaxLine(cm); }\n    cm.options.mode = doc.modeOption;\n    regChange(cm);\n  }\n\n  function setDirectionClass(cm) {\n  (cm.doc.direction == \"rtl\" ? addClass : rmClass)(cm.display.lineDiv, \"CodeMirror-rtl\");\n  }\n\n  function directionChanged(cm) {\n    runInOp(cm, function () {\n      setDirectionClass(cm);\n      regChange(cm);\n    });\n  }\n\n  function History(startGen) {\n    // Arrays of change events and selections. Doing something adds an\n    // event to done and clears undo. Undoing moves events from done\n    // to undone, redoing moves them in the other direction.\n    this.done = []; this.undone = [];\n    this.undoDepth = Infinity;\n    // Used to track when changes can be merged into a single undo\n    // event\n    this.lastModTime = this.lastSelTime = 0;\n    this.lastOp = this.lastSelOp = null;\n    this.lastOrigin = this.lastSelOrigin = null;\n    // Used by the isClean() method\n    this.generation = this.maxGeneration = startGen || 1;\n  }\n\n  // Create a history change event from an updateDoc-style change\n  // object.\n  function historyChangeFromChange(doc, change) {\n    var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};\n    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);\n    linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true);\n    return histChange\n  }\n\n  // Pop all selection events off the end of a history array. Stop at\n  // a change event.\n  function clearSelectionEvents(array) {\n    while (array.length) {\n      var last = lst(array);\n      if (last.ranges) { array.pop(); }\n      else { break }\n    }\n  }\n\n  // Find the top change event in the history. Pop off selection\n  // events that are in the way.\n  function lastChangeEvent(hist, force) {\n    if (force) {\n      clearSelectionEvents(hist.done);\n      return lst(hist.done)\n    } else if (hist.done.length && !lst(hist.done).ranges) {\n      return lst(hist.done)\n    } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {\n      hist.done.pop();\n      return lst(hist.done)\n    }\n  }\n\n  // Register a change in the history. Merges changes that are within\n  // a single operation, or are close together with an origin that\n  // allows merging (starting with \"+\") into a single event.\n  function addChangeToHistory(doc, change, selAfter, opId) {\n    var hist = doc.history;\n    hist.undone.length = 0;\n    var time = +new Date, cur;\n    var last;\n\n    if ((hist.lastOp == opId ||\n         hist.lastOrigin == change.origin && change.origin &&\n         ((change.origin.charAt(0) == \"+\" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||\n          change.origin.charAt(0) == \"*\")) &&\n        (cur = lastChangeEvent(hist, hist.lastOp == opId))) {\n      // Merge this change into the last event\n      last = lst(cur.changes);\n      if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {\n        // Optimized case for simple insertion -- don't want to add\n        // new changesets for every character typed\n        last.to = changeEnd(change);\n      } else {\n        // Add new sub-event\n        cur.changes.push(historyChangeFromChange(doc, change));\n      }\n    } else {\n      // Can not be merged, start a new event.\n      var before = lst(hist.done);\n      if (!before || !before.ranges)\n        { pushSelectionToHistory(doc.sel, hist.done); }\n      cur = {changes: [historyChangeFromChange(doc, change)],\n             generation: hist.generation};\n      hist.done.push(cur);\n      while (hist.done.length > hist.undoDepth) {\n        hist.done.shift();\n        if (!hist.done[0].ranges) { hist.done.shift(); }\n      }\n    }\n    hist.done.push(selAfter);\n    hist.generation = ++hist.maxGeneration;\n    hist.lastModTime = hist.lastSelTime = time;\n    hist.lastOp = hist.lastSelOp = opId;\n    hist.lastOrigin = hist.lastSelOrigin = change.origin;\n\n    if (!last) { signal(doc, \"historyAdded\"); }\n  }\n\n  function selectionEventCanBeMerged(doc, origin, prev, sel) {\n    var ch = origin.charAt(0);\n    return ch == \"*\" ||\n      ch == \"+\" &&\n      prev.ranges.length == sel.ranges.length &&\n      prev.somethingSelected() == sel.somethingSelected() &&\n      new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)\n  }\n\n  // Called whenever the selection changes, sets the new selection as\n  // the pending selection in the history, and pushes the old pending\n  // selection into the 'done' array when it was significantly\n  // different (in number of selected ranges, emptiness, or time).\n  function addSelectionToHistory(doc, sel, opId, options) {\n    var hist = doc.history, origin = options && options.origin;\n\n    // A new event is started when the previous origin does not match\n    // the current, or the origins don't allow matching. Origins\n    // starting with * are always merged, those starting with + are\n    // merged when similar and close together in time.\n    if (opId == hist.lastSelOp ||\n        (origin && hist.lastSelOrigin == origin &&\n         (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||\n          selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))\n      { hist.done[hist.done.length - 1] = sel; }\n    else\n      { pushSelectionToHistory(sel, hist.done); }\n\n    hist.lastSelTime = +new Date;\n    hist.lastSelOrigin = origin;\n    hist.lastSelOp = opId;\n    if (options && options.clearRedo !== false)\n      { clearSelectionEvents(hist.undone); }\n  }\n\n  function pushSelectionToHistory(sel, dest) {\n    var top = lst(dest);\n    if (!(top && top.ranges && top.equals(sel)))\n      { dest.push(sel); }\n  }\n\n  // Used to store marked span information in the history.\n  function attachLocalSpans(doc, change, from, to) {\n    var existing = change[\"spans_\" + doc.id], n = 0;\n    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) {\n      if (line.markedSpans)\n        { (existing || (existing = change[\"spans_\" + doc.id] = {}))[n] = line.markedSpans; }\n      ++n;\n    });\n  }\n\n  // When un/re-doing restores text containing marked spans, those\n  // that have been explicitly cleared should not be restored.\n  function removeClearedSpans(spans) {\n    if (!spans) { return null }\n    var out;\n    for (var i = 0; i < spans.length; ++i) {\n      if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } }\n      else if (out) { out.push(spans[i]); }\n    }\n    return !out ? spans : out.length ? out : null\n  }\n\n  // Retrieve and filter the old marked spans stored in a change event.\n  function getOldSpans(doc, change) {\n    var found = change[\"spans_\" + doc.id];\n    if (!found) { return null }\n    var nw = [];\n    for (var i = 0; i < change.text.length; ++i)\n      { nw.push(removeClearedSpans(found[i])); }\n    return nw\n  }\n\n  // Used for un/re-doing changes from the history. Combines the\n  // result of computing the existing spans with the set of spans that\n  // existed in the history (so that deleting around a span and then\n  // undoing brings back the span).\n  function mergeOldSpans(doc, change) {\n    var old = getOldSpans(doc, change);\n    var stretched = stretchSpansOverChange(doc, change);\n    if (!old) { return stretched }\n    if (!stretched) { return old }\n\n    for (var i = 0; i < old.length; ++i) {\n      var oldCur = old[i], stretchCur = stretched[i];\n      if (oldCur && stretchCur) {\n        spans: for (var j = 0; j < stretchCur.length; ++j) {\n          var span = stretchCur[j];\n          for (var k = 0; k < oldCur.length; ++k)\n            { if (oldCur[k].marker == span.marker) { continue spans } }\n          oldCur.push(span);\n        }\n      } else if (stretchCur) {\n        old[i] = stretchCur;\n      }\n    }\n    return old\n  }\n\n  // Used both to provide a JSON-safe object in .getHistory, and, when\n  // detaching a document, to split the history in two\n  function copyHistoryArray(events, newGroup, instantiateSel) {\n    var copy = [];\n    for (var i = 0; i < events.length; ++i) {\n      var event = events[i];\n      if (event.ranges) {\n        copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);\n        continue\n      }\n      var changes = event.changes, newChanges = [];\n      copy.push({changes: newChanges});\n      for (var j = 0; j < changes.length; ++j) {\n        var change = changes[j], m = (void 0);\n        newChanges.push({from: change.from, to: change.to, text: change.text});\n        if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\\d+)$/)) {\n          if (indexOf(newGroup, Number(m[1])) > -1) {\n            lst(newChanges)[prop] = change[prop];\n            delete change[prop];\n          }\n        } } }\n      }\n    }\n    return copy\n  }\n\n  // The 'scroll' parameter given to many of these indicated whether\n  // the new cursor position should be scrolled into view after\n  // modifying the selection.\n\n  // If shift is held or the extend flag is set, extends a range to\n  // include a given position (and optionally a second position).\n  // Otherwise, simply returns the range between the given positions.\n  // Used for cursor motion and such.\n  function extendRange(range, head, other, extend) {\n    if (extend) {\n      var anchor = range.anchor;\n      if (other) {\n        var posBefore = cmp(head, anchor) < 0;\n        if (posBefore != (cmp(other, anchor) < 0)) {\n          anchor = head;\n          head = other;\n        } else if (posBefore != (cmp(head, other) < 0)) {\n          head = other;\n        }\n      }\n      return new Range(anchor, head)\n    } else {\n      return new Range(other || head, head)\n    }\n  }\n\n  // Extend the primary selection range, discard the rest.\n  function extendSelection(doc, head, other, options, extend) {\n    if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); }\n    setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options);\n  }\n\n  // Extend all selections (pos is an array of selections with length\n  // equal the number of selections)\n  function extendSelections(doc, heads, options) {\n    var out = [];\n    var extend = doc.cm && (doc.cm.display.shift || doc.extend);\n    for (var i = 0; i < doc.sel.ranges.length; i++)\n      { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); }\n    var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex);\n    setSelection(doc, newSel, options);\n  }\n\n  // Updates a single range in the selection.\n  function replaceOneSelection(doc, i, range, options) {\n    var ranges = doc.sel.ranges.slice(0);\n    ranges[i] = range;\n    setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options);\n  }\n\n  // Reset the selection to a single range.\n  function setSimpleSelection(doc, anchor, head, options) {\n    setSelection(doc, simpleSelection(anchor, head), options);\n  }\n\n  // Give beforeSelectionChange handlers a change to influence a\n  // selection update.\n  function filterSelectionChange(doc, sel, options) {\n    var obj = {\n      ranges: sel.ranges,\n      update: function(ranges) {\n        this.ranges = [];\n        for (var i = 0; i < ranges.length; i++)\n          { this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),\n                                     clipPos(doc, ranges[i].head)); }\n      },\n      origin: options && options.origin\n    };\n    signal(doc, \"beforeSelectionChange\", doc, obj);\n    if (doc.cm) { signal(doc.cm, \"beforeSelectionChange\", doc.cm, obj); }\n    if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) }\n    else { return sel }\n  }\n\n  function setSelectionReplaceHistory(doc, sel, options) {\n    var done = doc.history.done, last = lst(done);\n    if (last && last.ranges) {\n      done[done.length - 1] = sel;\n      setSelectionNoUndo(doc, sel, options);\n    } else {\n      setSelection(doc, sel, options);\n    }\n  }\n\n  // Set a new selection.\n  function setSelection(doc, sel, options) {\n    setSelectionNoUndo(doc, sel, options);\n    addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);\n  }\n\n  function setSelectionNoUndo(doc, sel, options) {\n    if (hasHandler(doc, \"beforeSelectionChange\") || doc.cm && hasHandler(doc.cm, \"beforeSelectionChange\"))\n      { sel = filterSelectionChange(doc, sel, options); }\n\n    var bias = options && options.bias ||\n      (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);\n    setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));\n\n    if (!(options && options.scroll === false) && doc.cm)\n      { ensureCursorVisible(doc.cm); }\n  }\n\n  function setSelectionInner(doc, sel) {\n    if (sel.equals(doc.sel)) { return }\n\n    doc.sel = sel;\n\n    if (doc.cm) {\n      doc.cm.curOp.updateInput = 1;\n      doc.cm.curOp.selectionChanged = true;\n      signalCursorActivity(doc.cm);\n    }\n    signalLater(doc, \"cursorActivity\", doc);\n  }\n\n  // Verify that the selection does not partially select any atomic\n  // marked ranges.\n  function reCheckSelection(doc) {\n    setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false));\n  }\n\n  // Return a selection that does not partially select any atomic\n  // ranges.\n  function skipAtomicInSelection(doc, sel, bias, mayClear) {\n    var out;\n    for (var i = 0; i < sel.ranges.length; i++) {\n      var range = sel.ranges[i];\n      var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];\n      var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);\n      var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);\n      if (out || newAnchor != range.anchor || newHead != range.head) {\n        if (!out) { out = sel.ranges.slice(0, i); }\n        out[i] = new Range(newAnchor, newHead);\n      }\n    }\n    return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel\n  }\n\n  function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {\n    var line = getLine(doc, pos.line);\n    if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {\n      var sp = line.markedSpans[i], m = sp.marker;\n\n      // Determine if we should prevent the cursor being placed to the left/right of an atomic marker\n      // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it\n      // is with selectLeft/Right\n      var preventCursorLeft = (\"selectLeft\" in m) ? !m.selectLeft : m.inclusiveLeft;\n      var preventCursorRight = (\"selectRight\" in m) ? !m.selectRight : m.inclusiveRight;\n\n      if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&\n          (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) {\n        if (mayClear) {\n          signal(m, \"beforeCursorEnter\");\n          if (m.explicitlyCleared) {\n            if (!line.markedSpans) { break }\n            else {--i; continue}\n          }\n        }\n        if (!m.atomic) { continue }\n\n        if (oldPos) {\n          var near = m.find(dir < 0 ? 1 : -1), diff = (void 0);\n          if (dir < 0 ? preventCursorRight : preventCursorLeft)\n            { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); }\n          if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))\n            { return skipAtomicInner(doc, near, pos, dir, mayClear) }\n        }\n\n        var far = m.find(dir < 0 ? -1 : 1);\n        if (dir < 0 ? preventCursorLeft : preventCursorRight)\n          { far = movePos(doc, far, dir, far.line == pos.line ? line : null); }\n        return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null\n      }\n    } }\n    return pos\n  }\n\n  // Ensure a given position is not inside an atomic range.\n  function skipAtomic(doc, pos, oldPos, bias, mayClear) {\n    var dir = bias || 1;\n    var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||\n        (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||\n        skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||\n        (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));\n    if (!found) {\n      doc.cantEdit = true;\n      return Pos(doc.first, 0)\n    }\n    return found\n  }\n\n  function movePos(doc, pos, dir, line) {\n    if (dir < 0 && pos.ch == 0) {\n      if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }\n      else { return null }\n    } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {\n      if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }\n      else { return null }\n    } else {\n      return new Pos(pos.line, pos.ch + dir)\n    }\n  }\n\n  function selectAll(cm) {\n    cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);\n  }\n\n  // UPDATING\n\n  // Allow \"beforeChange\" event handlers to influence a change\n  function filterChange(doc, change, update) {\n    var obj = {\n      canceled: false,\n      from: change.from,\n      to: change.to,\n      text: change.text,\n      origin: change.origin,\n      cancel: function () { return obj.canceled = true; }\n    };\n    if (update) { obj.update = function (from, to, text, origin) {\n      if (from) { obj.from = clipPos(doc, from); }\n      if (to) { obj.to = clipPos(doc, to); }\n      if (text) { obj.text = text; }\n      if (origin !== undefined) { obj.origin = origin; }\n    }; }\n    signal(doc, \"beforeChange\", doc, obj);\n    if (doc.cm) { signal(doc.cm, \"beforeChange\", doc.cm, obj); }\n\n    if (obj.canceled) {\n      if (doc.cm) { doc.cm.curOp.updateInput = 2; }\n      return null\n    }\n    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}\n  }\n\n  // Apply a change to a document, and add it to the document's\n  // history, and propagating it to all linked documents.\n  function makeChange(doc, change, ignoreReadOnly) {\n    if (doc.cm) {\n      if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) }\n      if (doc.cm.state.suppressEdits) { return }\n    }\n\n    if (hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\")) {\n      change = filterChange(doc, change, true);\n      if (!change) { return }\n    }\n\n    // Possibly split or suppress the update based on the presence\n    // of read-only spans in its range.\n    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);\n    if (split) {\n      for (var i = split.length - 1; i >= 0; --i)\n        { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [\"\"] : change.text, origin: change.origin}); }\n    } else {\n      makeChangeInner(doc, change);\n    }\n  }\n\n  function makeChangeInner(doc, change) {\n    if (change.text.length == 1 && change.text[0] == \"\" && cmp(change.from, change.to) == 0) { return }\n    var selAfter = computeSelAfterChange(doc, change);\n    addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);\n\n    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));\n    var rebased = [];\n\n    linkedDocs(doc, function (doc, sharedHist) {\n      if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n        rebaseHist(doc.history, change);\n        rebased.push(doc.history);\n      }\n      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));\n    });\n  }\n\n  // Revert a change stored in a document's history.\n  function makeChangeFromHistory(doc, type, allowSelectionOnly) {\n    var suppress = doc.cm && doc.cm.state.suppressEdits;\n    if (suppress && !allowSelectionOnly) { return }\n\n    var hist = doc.history, event, selAfter = doc.sel;\n    var source = type == \"undo\" ? hist.done : hist.undone, dest = type == \"undo\" ? hist.undone : hist.done;\n\n    // Verify that there is a useable event (so that ctrl-z won't\n    // needlessly clear selection events)\n    var i = 0;\n    for (; i < source.length; i++) {\n      event = source[i];\n      if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)\n        { break }\n    }\n    if (i == source.length) { return }\n    hist.lastOrigin = hist.lastSelOrigin = null;\n\n    for (;;) {\n      event = source.pop();\n      if (event.ranges) {\n        pushSelectionToHistory(event, dest);\n        if (allowSelectionOnly && !event.equals(doc.sel)) {\n          setSelection(doc, event, {clearRedo: false});\n          return\n        }\n        selAfter = event;\n      } else if (suppress) {\n        source.push(event);\n        return\n      } else { break }\n    }\n\n    // Build up a reverse change object to add to the opposite history\n    // stack (redo when undoing, and vice versa).\n    var antiChanges = [];\n    pushSelectionToHistory(selAfter, dest);\n    dest.push({changes: antiChanges, generation: hist.generation});\n    hist.generation = event.generation || ++hist.maxGeneration;\n\n    var filter = hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\");\n\n    var loop = function ( i ) {\n      var change = event.changes[i];\n      change.origin = type;\n      if (filter && !filterChange(doc, change, false)) {\n        source.length = 0;\n        return {}\n      }\n\n      antiChanges.push(historyChangeFromChange(doc, change));\n\n      var after = i ? computeSelAfterChange(doc, change) : lst(source);\n      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));\n      if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); }\n      var rebased = [];\n\n      // Propagate to the linked documents\n      linkedDocs(doc, function (doc, sharedHist) {\n        if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n          rebaseHist(doc.history, change);\n          rebased.push(doc.history);\n        }\n        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));\n      });\n    };\n\n    for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {\n      var returned = loop( i$1 );\n\n      if ( returned ) return returned.v;\n    }\n  }\n\n  // Sub-views need their line numbers shifted when text is added\n  // above or below them in the parent document.\n  function shiftDoc(doc, distance) {\n    if (distance == 0) { return }\n    doc.first += distance;\n    doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range(\n      Pos(range.anchor.line + distance, range.anchor.ch),\n      Pos(range.head.line + distance, range.head.ch)\n    ); }), doc.sel.primIndex);\n    if (doc.cm) {\n      regChange(doc.cm, doc.first, doc.first - distance, distance);\n      for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)\n        { regLineChange(doc.cm, l, \"gutter\"); }\n    }\n  }\n\n  // More lower-level change function, handling only a single document\n  // (not linked ones).\n  function makeChangeSingleDoc(doc, change, selAfter, spans) {\n    if (doc.cm && !doc.cm.curOp)\n      { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) }\n\n    if (change.to.line < doc.first) {\n      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));\n      return\n    }\n    if (change.from.line > doc.lastLine()) { return }\n\n    // Clip the change to the size of this doc\n    if (change.from.line < doc.first) {\n      var shift = change.text.length - 1 - (doc.first - change.from.line);\n      shiftDoc(doc, shift);\n      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),\n                text: [lst(change.text)], origin: change.origin};\n    }\n    var last = doc.lastLine();\n    if (change.to.line > last) {\n      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),\n                text: [change.text[0]], origin: change.origin};\n    }\n\n    change.removed = getBetween(doc, change.from, change.to);\n\n    if (!selAfter) { selAfter = computeSelAfterChange(doc, change); }\n    if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); }\n    else { updateDoc(doc, change, spans); }\n    setSelectionNoUndo(doc, selAfter, sel_dontScroll);\n\n    if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0)))\n      { doc.cantEdit = false; }\n  }\n\n  // Handle the interaction of a change to a document with the editor\n  // that this document is part of.\n  function makeChangeSingleDocInEditor(cm, change, spans) {\n    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;\n\n    var recomputeMaxLength = false, checkWidthStart = from.line;\n    if (!cm.options.lineWrapping) {\n      checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));\n      doc.iter(checkWidthStart, to.line + 1, function (line) {\n        if (line == display.maxLine) {\n          recomputeMaxLength = true;\n          return true\n        }\n      });\n    }\n\n    if (doc.sel.contains(change.from, change.to) > -1)\n      { signalCursorActivity(cm); }\n\n    updateDoc(doc, change, spans, estimateHeight(cm));\n\n    if (!cm.options.lineWrapping) {\n      doc.iter(checkWidthStart, from.line + change.text.length, function (line) {\n        var len = lineLength(line);\n        if (len > display.maxLineLength) {\n          display.maxLine = line;\n          display.maxLineLength = len;\n          display.maxLineChanged = true;\n          recomputeMaxLength = false;\n        }\n      });\n      if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; }\n    }\n\n    retreatFrontier(doc, from.line);\n    startWorker(cm, 400);\n\n    var lendiff = change.text.length - (to.line - from.line) - 1;\n    // Remember that these lines changed, for updating the display\n    if (change.full)\n      { regChange(cm); }\n    else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))\n      { regLineChange(cm, from.line, \"text\"); }\n    else\n      { regChange(cm, from.line, to.line + 1, lendiff); }\n\n    var changesHandler = hasHandler(cm, \"changes\"), changeHandler = hasHandler(cm, \"change\");\n    if (changeHandler || changesHandler) {\n      var obj = {\n        from: from, to: to,\n        text: change.text,\n        removed: change.removed,\n        origin: change.origin\n      };\n      if (changeHandler) { signalLater(cm, \"change\", cm, obj); }\n      if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); }\n    }\n    cm.display.selForContextMenu = null;\n  }\n\n  function replaceRange(doc, code, from, to, origin) {\n    var assign;\n\n    if (!to) { to = from; }\n    if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); }\n    if (typeof code == \"string\") { code = doc.splitLines(code); }\n    makeChange(doc, {from: from, to: to, text: code, origin: origin});\n  }\n\n  // Rebasing/resetting history to deal with externally-sourced changes\n\n  function rebaseHistSelSingle(pos, from, to, diff) {\n    if (to < pos.line) {\n      pos.line += diff;\n    } else if (from < pos.line) {\n      pos.line = from;\n      pos.ch = 0;\n    }\n  }\n\n  // Tries to rebase an array of history events given a change in the\n  // document. If the change touches the same lines as the event, the\n  // event, and everything 'behind' it, is discarded. If the change is\n  // before the event, the event's positions are updated. Uses a\n  // copy-on-write scheme for the positions, to avoid having to\n  // reallocate them all on every rebase, but also avoid problems with\n  // shared position objects being unsafely updated.\n  function rebaseHistArray(array, from, to, diff) {\n    for (var i = 0; i < array.length; ++i) {\n      var sub = array[i], ok = true;\n      if (sub.ranges) {\n        if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }\n        for (var j = 0; j < sub.ranges.length; j++) {\n          rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);\n          rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);\n        }\n        continue\n      }\n      for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {\n        var cur = sub.changes[j$1];\n        if (to < cur.from.line) {\n          cur.from = Pos(cur.from.line + diff, cur.from.ch);\n          cur.to = Pos(cur.to.line + diff, cur.to.ch);\n        } else if (from <= cur.to.line) {\n          ok = false;\n          break\n        }\n      }\n      if (!ok) {\n        array.splice(0, i + 1);\n        i = 0;\n      }\n    }\n  }\n\n  function rebaseHist(hist, change) {\n    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;\n    rebaseHistArray(hist.done, from, to, diff);\n    rebaseHistArray(hist.undone, from, to, diff);\n  }\n\n  // Utility for applying a change to a line by handle or number,\n  // returning the number and optionally registering the line as\n  // changed.\n  function changeLine(doc, handle, changeType, op) {\n    var no = handle, line = handle;\n    if (typeof handle == \"number\") { line = getLine(doc, clipLine(doc, handle)); }\n    else { no = lineNo(handle); }\n    if (no == null) { return null }\n    if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); }\n    return line\n  }\n\n  // The document is represented as a BTree consisting of leaves, with\n  // chunk of lines in them, and branches, with up to ten leaves or\n  // other branch nodes below them. The top node is always a branch\n  // node, and is the document object itself (meaning it has\n  // additional methods and properties).\n  //\n  // All nodes have parent links. The tree is used both to go from\n  // line numbers to line objects, and to go from objects to numbers.\n  // It also indexes by height, and is used to convert between height\n  // and line object, and to find the total height of the document.\n  //\n  // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html\n\n  function LeafChunk(lines) {\n    this.lines = lines;\n    this.parent = null;\n    var height = 0;\n    for (var i = 0; i < lines.length; ++i) {\n      lines[i].parent = this;\n      height += lines[i].height;\n    }\n    this.height = height;\n  }\n\n  LeafChunk.prototype = {\n    chunkSize: function() { return this.lines.length },\n\n    // Remove the n lines at offset 'at'.\n    removeInner: function(at, n) {\n      for (var i = at, e = at + n; i < e; ++i) {\n        var line = this.lines[i];\n        this.height -= line.height;\n        cleanUpLine(line);\n        signalLater(line, \"delete\");\n      }\n      this.lines.splice(at, n);\n    },\n\n    // Helper used to collapse a small branch into a single leaf.\n    collapse: function(lines) {\n      lines.push.apply(lines, this.lines);\n    },\n\n    // Insert the given array of lines at offset 'at', count them as\n    // having the given height.\n    insertInner: function(at, lines, height) {\n      this.height += height;\n      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));\n      for (var i = 0; i < lines.length; ++i) { lines[i].parent = this; }\n    },\n\n    // Used to iterate over a part of the tree.\n    iterN: function(at, n, op) {\n      for (var e = at + n; at < e; ++at)\n        { if (op(this.lines[at])) { return true } }\n    }\n  };\n\n  function BranchChunk(children) {\n    this.children = children;\n    var size = 0, height = 0;\n    for (var i = 0; i < children.length; ++i) {\n      var ch = children[i];\n      size += ch.chunkSize(); height += ch.height;\n      ch.parent = this;\n    }\n    this.size = size;\n    this.height = height;\n    this.parent = null;\n  }\n\n  BranchChunk.prototype = {\n    chunkSize: function() { return this.size },\n\n    removeInner: function(at, n) {\n      this.size -= n;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var rm = Math.min(n, sz - at), oldHeight = child.height;\n          child.removeInner(at, rm);\n          this.height -= oldHeight - child.height;\n          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }\n          if ((n -= rm) == 0) { break }\n          at = 0;\n        } else { at -= sz; }\n      }\n      // If the result is smaller than 25 lines, ensure that it is a\n      // single leaf node.\n      if (this.size - n < 25 &&\n          (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {\n        var lines = [];\n        this.collapse(lines);\n        this.children = [new LeafChunk(lines)];\n        this.children[0].parent = this;\n      }\n    },\n\n    collapse: function(lines) {\n      for (var i = 0; i < this.children.length; ++i) { this.children[i].collapse(lines); }\n    },\n\n    insertInner: function(at, lines, height) {\n      this.size += lines.length;\n      this.height += height;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at <= sz) {\n          child.insertInner(at, lines, height);\n          if (child.lines && child.lines.length > 50) {\n            // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.\n            // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.\n            var remaining = child.lines.length % 25 + 25;\n            for (var pos = remaining; pos < child.lines.length;) {\n              var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));\n              child.height -= leaf.height;\n              this.children.splice(++i, 0, leaf);\n              leaf.parent = this;\n            }\n            child.lines = child.lines.slice(0, remaining);\n            this.maybeSpill();\n          }\n          break\n        }\n        at -= sz;\n      }\n    },\n\n    // When a node has grown, check whether it should be split.\n    maybeSpill: function() {\n      if (this.children.length <= 10) { return }\n      var me = this;\n      do {\n        var spilled = me.children.splice(me.children.length - 5, 5);\n        var sibling = new BranchChunk(spilled);\n        if (!me.parent) { // Become the parent node\n          var copy = new BranchChunk(me.children);\n          copy.parent = me;\n          me.children = [copy, sibling];\n          me = copy;\n       } else {\n          me.size -= sibling.size;\n          me.height -= sibling.height;\n          var myIndex = indexOf(me.parent.children, me);\n          me.parent.children.splice(myIndex + 1, 0, sibling);\n        }\n        sibling.parent = me.parent;\n      } while (me.children.length > 10)\n      me.parent.maybeSpill();\n    },\n\n    iterN: function(at, n, op) {\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var used = Math.min(n, sz - at);\n          if (child.iterN(at, used, op)) { return true }\n          if ((n -= used) == 0) { break }\n          at = 0;\n        } else { at -= sz; }\n      }\n    }\n  };\n\n  // Line widgets are block elements displayed above or below a line.\n\n  var LineWidget = function(doc, node, options) {\n    if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))\n      { this[opt] = options[opt]; } } }\n    this.doc = doc;\n    this.node = node;\n  };\n\n  LineWidget.prototype.clear = function () {\n    var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);\n    if (no == null || !ws) { return }\n    for (var i = 0; i < ws.length; ++i) { if (ws[i] == this) { ws.splice(i--, 1); } }\n    if (!ws.length) { line.widgets = null; }\n    var height = widgetHeight(this);\n    updateLineHeight(line, Math.max(0, line.height - height));\n    if (cm) {\n      runInOp(cm, function () {\n        adjustScrollWhenAboveVisible(cm, line, -height);\n        regLineChange(cm, no, \"widget\");\n      });\n      signalLater(cm, \"lineWidgetCleared\", cm, this, no);\n    }\n  };\n\n  LineWidget.prototype.changed = function () {\n      var this$1 = this;\n\n    var oldH = this.height, cm = this.doc.cm, line = this.line;\n    this.height = null;\n    var diff = widgetHeight(this) - oldH;\n    if (!diff) { return }\n    if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); }\n    if (cm) {\n      runInOp(cm, function () {\n        cm.curOp.forceUpdate = true;\n        adjustScrollWhenAboveVisible(cm, line, diff);\n        signalLater(cm, \"lineWidgetChanged\", cm, this$1, lineNo(line));\n      });\n    }\n  };\n  eventMixin(LineWidget);\n\n  function adjustScrollWhenAboveVisible(cm, line, diff) {\n    if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))\n      { addToScrollTop(cm, diff); }\n  }\n\n  function addLineWidget(doc, handle, node, options) {\n    var widget = new LineWidget(doc, node, options);\n    var cm = doc.cm;\n    if (cm && widget.noHScroll) { cm.display.alignWidgets = true; }\n    changeLine(doc, handle, \"widget\", function (line) {\n      var widgets = line.widgets || (line.widgets = []);\n      if (widget.insertAt == null) { widgets.push(widget); }\n      else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); }\n      widget.line = line;\n      if (cm && !lineIsHidden(doc, line)) {\n        var aboveVisible = heightAtLine(line) < doc.scrollTop;\n        updateLineHeight(line, line.height + widgetHeight(widget));\n        if (aboveVisible) { addToScrollTop(cm, widget.height); }\n        cm.curOp.forceUpdate = true;\n      }\n      return true\n    });\n    if (cm) { signalLater(cm, \"lineWidgetAdded\", cm, widget, typeof handle == \"number\" ? handle : lineNo(handle)); }\n    return widget\n  }\n\n  // TEXTMARKERS\n\n  // Created with markText and setBookmark methods. A TextMarker is a\n  // handle that can be used to clear or find a marked position in the\n  // document. Line objects hold arrays (markedSpans) containing\n  // {from, to, marker} object pointing to such marker objects, and\n  // indicating that such a marker is present on that line. Multiple\n  // lines may point to the same marker when it spans across lines.\n  // The spans will have null for their from/to properties when the\n  // marker continues beyond the start/end of the line. Markers have\n  // links back to the lines they currently touch.\n\n  // Collapsed markers have unique ids, in order to be able to order\n  // them, which is needed for uniquely determining an outer marker\n  // when they overlap (they may nest, but not partially overlap).\n  var nextMarkerId = 0;\n\n  var TextMarker = function(doc, type) {\n    this.lines = [];\n    this.type = type;\n    this.doc = doc;\n    this.id = ++nextMarkerId;\n  };\n\n  // Clear the marker.\n  TextMarker.prototype.clear = function () {\n    if (this.explicitlyCleared) { return }\n    var cm = this.doc.cm, withOp = cm && !cm.curOp;\n    if (withOp) { startOperation(cm); }\n    if (hasHandler(this, \"clear\")) {\n      var found = this.find();\n      if (found) { signalLater(this, \"clear\", found.from, found.to); }\n    }\n    var min = null, max = null;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this);\n      if (cm && !this.collapsed) { regLineChange(cm, lineNo(line), \"text\"); }\n      else if (cm) {\n        if (span.to != null) { max = lineNo(line); }\n        if (span.from != null) { min = lineNo(line); }\n      }\n      line.markedSpans = removeMarkedSpan(line.markedSpans, span);\n      if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)\n        { updateLineHeight(line, textHeight(cm.display)); }\n    }\n    if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {\n      var visual = visualLine(this.lines[i$1]), len = lineLength(visual);\n      if (len > cm.display.maxLineLength) {\n        cm.display.maxLine = visual;\n        cm.display.maxLineLength = len;\n        cm.display.maxLineChanged = true;\n      }\n    } }\n\n    if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); }\n    this.lines.length = 0;\n    this.explicitlyCleared = true;\n    if (this.atomic && this.doc.cantEdit) {\n      this.doc.cantEdit = false;\n      if (cm) { reCheckSelection(cm.doc); }\n    }\n    if (cm) { signalLater(cm, \"markerCleared\", cm, this, min, max); }\n    if (withOp) { endOperation(cm); }\n    if (this.parent) { this.parent.clear(); }\n  };\n\n  // Find the position of the marker in the document. Returns a {from,\n  // to} object by default. Side can be passed to get a specific side\n  // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the\n  // Pos objects returned contain a line object, rather than a line\n  // number (used to prevent looking up the same line twice).\n  TextMarker.prototype.find = function (side, lineObj) {\n    if (side == null && this.type == \"bookmark\") { side = 1; }\n    var from, to;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this);\n      if (span.from != null) {\n        from = Pos(lineObj ? line : lineNo(line), span.from);\n        if (side == -1) { return from }\n      }\n      if (span.to != null) {\n        to = Pos(lineObj ? line : lineNo(line), span.to);\n        if (side == 1) { return to }\n      }\n    }\n    return from && {from: from, to: to}\n  };\n\n  // Signals that the marker's widget changed, and surrounding layout\n  // should be recomputed.\n  TextMarker.prototype.changed = function () {\n      var this$1 = this;\n\n    var pos = this.find(-1, true), widget = this, cm = this.doc.cm;\n    if (!pos || !cm) { return }\n    runInOp(cm, function () {\n      var line = pos.line, lineN = lineNo(pos.line);\n      var view = findViewForLine(cm, lineN);\n      if (view) {\n        clearLineMeasurementCacheFor(view);\n        cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;\n      }\n      cm.curOp.updateMaxLine = true;\n      if (!lineIsHidden(widget.doc, line) && widget.height != null) {\n        var oldHeight = widget.height;\n        widget.height = null;\n        var dHeight = widgetHeight(widget) - oldHeight;\n        if (dHeight)\n          { updateLineHeight(line, line.height + dHeight); }\n      }\n      signalLater(cm, \"markerChanged\", cm, this$1);\n    });\n  };\n\n  TextMarker.prototype.attachLine = function (line) {\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp;\n      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)\n        { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); }\n    }\n    this.lines.push(line);\n  };\n\n  TextMarker.prototype.detachLine = function (line) {\n    this.lines.splice(indexOf(this.lines, line), 1);\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp\n      ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);\n    }\n  };\n  eventMixin(TextMarker);\n\n  // Create a marker, wire it up to the right lines, and\n  function markText(doc, from, to, options, type) {\n    // Shared markers (across linked documents) are handled separately\n    // (markTextShared will call out to this again, once per\n    // document).\n    if (options && options.shared) { return markTextShared(doc, from, to, options, type) }\n    // Ensure we are in an operation.\n    if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) }\n\n    var marker = new TextMarker(doc, type), diff = cmp(from, to);\n    if (options) { copyObj(options, marker, false); }\n    // Don't connect empty markers unless clearWhenEmpty is false\n    if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)\n      { return marker }\n    if (marker.replacedWith) {\n      // Showing up as a widget implies collapsed (widget replaces text)\n      marker.collapsed = true;\n      marker.widgetNode = eltP(\"span\", [marker.replacedWith], \"CodeMirror-widget\");\n      if (!options.handleMouseEvents) { marker.widgetNode.setAttribute(\"cm-ignore-events\", \"true\"); }\n      if (options.insertLeft) { marker.widgetNode.insertLeft = true; }\n    }\n    if (marker.collapsed) {\n      if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||\n          from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))\n        { throw new Error(\"Inserting collapsed marker partially overlapping an existing one\") }\n      seeCollapsedSpans();\n    }\n\n    if (marker.addToHistory)\n      { addChangeToHistory(doc, {from: from, to: to, origin: \"markText\"}, doc.sel, NaN); }\n\n    var curLine = from.line, cm = doc.cm, updateMaxLine;\n    doc.iter(curLine, to.line + 1, function (line) {\n      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)\n        { updateMaxLine = true; }\n      if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); }\n      addMarkedSpan(line, new MarkedSpan(marker,\n                                         curLine == from.line ? from.ch : null,\n                                         curLine == to.line ? to.ch : null));\n      ++curLine;\n    });\n    // lineIsHidden depends on the presence of the spans, so needs a second pass\n    if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) {\n      if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); }\n    }); }\n\n    if (marker.clearOnEnter) { on(marker, \"beforeCursorEnter\", function () { return marker.clear(); }); }\n\n    if (marker.readOnly) {\n      seeReadOnlySpans();\n      if (doc.history.done.length || doc.history.undone.length)\n        { doc.clearHistory(); }\n    }\n    if (marker.collapsed) {\n      marker.id = ++nextMarkerId;\n      marker.atomic = true;\n    }\n    if (cm) {\n      // Sync editor state\n      if (updateMaxLine) { cm.curOp.updateMaxLine = true; }\n      if (marker.collapsed)\n        { regChange(cm, from.line, to.line + 1); }\n      else if (marker.className || marker.startStyle || marker.endStyle || marker.css ||\n               marker.attributes || marker.title)\n        { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, \"text\"); } }\n      if (marker.atomic) { reCheckSelection(cm.doc); }\n      signalLater(cm, \"markerAdded\", cm, marker);\n    }\n    return marker\n  }\n\n  // SHARED TEXTMARKERS\n\n  // A shared marker spans multiple linked documents. It is\n  // implemented as a meta-marker-object controlling multiple normal\n  // markers.\n  var SharedTextMarker = function(markers, primary) {\n    this.markers = markers;\n    this.primary = primary;\n    for (var i = 0; i < markers.length; ++i)\n      { markers[i].parent = this; }\n  };\n\n  SharedTextMarker.prototype.clear = function () {\n    if (this.explicitlyCleared) { return }\n    this.explicitlyCleared = true;\n    for (var i = 0; i < this.markers.length; ++i)\n      { this.markers[i].clear(); }\n    signalLater(this, \"clear\");\n  };\n\n  SharedTextMarker.prototype.find = function (side, lineObj) {\n    return this.primary.find(side, lineObj)\n  };\n  eventMixin(SharedTextMarker);\n\n  function markTextShared(doc, from, to, options, type) {\n    options = copyObj(options);\n    options.shared = false;\n    var markers = [markText(doc, from, to, options, type)], primary = markers[0];\n    var widget = options.widgetNode;\n    linkedDocs(doc, function (doc) {\n      if (widget) { options.widgetNode = widget.cloneNode(true); }\n      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));\n      for (var i = 0; i < doc.linked.length; ++i)\n        { if (doc.linked[i].isParent) { return } }\n      primary = lst(markers);\n    });\n    return new SharedTextMarker(markers, primary)\n  }\n\n  function findSharedMarkers(doc) {\n    return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; })\n  }\n\n  function copySharedMarkers(doc, markers) {\n    for (var i = 0; i < markers.length; i++) {\n      var marker = markers[i], pos = marker.find();\n      var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);\n      if (cmp(mFrom, mTo)) {\n        var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);\n        marker.markers.push(subMark);\n        subMark.parent = marker;\n      }\n    }\n  }\n\n  function detachSharedMarkers(markers) {\n    var loop = function ( i ) {\n      var marker = markers[i], linked = [marker.primary.doc];\n      linkedDocs(marker.primary.doc, function (d) { return linked.push(d); });\n      for (var j = 0; j < marker.markers.length; j++) {\n        var subMarker = marker.markers[j];\n        if (indexOf(linked, subMarker.doc) == -1) {\n          subMarker.parent = null;\n          marker.markers.splice(j--, 1);\n        }\n      }\n    };\n\n    for (var i = 0; i < markers.length; i++) loop( i );\n  }\n\n  var nextDocId = 0;\n  var Doc = function(text, mode, firstLine, lineSep, direction) {\n    if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) }\n    if (firstLine == null) { firstLine = 0; }\n\n    BranchChunk.call(this, [new LeafChunk([new Line(\"\", null)])]);\n    this.first = firstLine;\n    this.scrollTop = this.scrollLeft = 0;\n    this.cantEdit = false;\n    this.cleanGeneration = 1;\n    this.modeFrontier = this.highlightFrontier = firstLine;\n    var start = Pos(firstLine, 0);\n    this.sel = simpleSelection(start);\n    this.history = new History(null);\n    this.id = ++nextDocId;\n    this.modeOption = mode;\n    this.lineSep = lineSep;\n    this.direction = (direction == \"rtl\") ? \"rtl\" : \"ltr\";\n    this.extend = false;\n\n    if (typeof text == \"string\") { text = this.splitLines(text); }\n    updateDoc(this, {from: start, to: start, text: text});\n    setSelection(this, simpleSelection(start), sel_dontScroll);\n  };\n\n  Doc.prototype = createObj(BranchChunk.prototype, {\n    constructor: Doc,\n    // Iterate over the document. Supports two forms -- with only one\n    // argument, it calls that for each line in the document. With\n    // three, it iterates over the range given by the first two (with\n    // the second being non-inclusive).\n    iter: function(from, to, op) {\n      if (op) { this.iterN(from - this.first, to - from, op); }\n      else { this.iterN(this.first, this.first + this.size, from); }\n    },\n\n    // Non-public interface for adding and removing lines.\n    insert: function(at, lines) {\n      var height = 0;\n      for (var i = 0; i < lines.length; ++i) { height += lines[i].height; }\n      this.insertInner(at - this.first, lines, height);\n    },\n    remove: function(at, n) { this.removeInner(at - this.first, n); },\n\n    // From here, the methods are part of the public interface. Most\n    // are also available from CodeMirror (editor) instances.\n\n    getValue: function(lineSep) {\n      var lines = getLines(this, this.first, this.first + this.size);\n      if (lineSep === false) { return lines }\n      return lines.join(lineSep || this.lineSeparator())\n    },\n    setValue: docMethodOp(function(code) {\n      var top = Pos(this.first, 0), last = this.first + this.size - 1;\n      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),\n                        text: this.splitLines(code), origin: \"setValue\", full: true}, true);\n      if (this.cm) { scrollToCoords(this.cm, 0, 0); }\n      setSelection(this, simpleSelection(top), sel_dontScroll);\n    }),\n    replaceRange: function(code, from, to, origin) {\n      from = clipPos(this, from);\n      to = to ? clipPos(this, to) : from;\n      replaceRange(this, code, from, to, origin);\n    },\n    getRange: function(from, to, lineSep) {\n      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));\n      if (lineSep === false) { return lines }\n      return lines.join(lineSep || this.lineSeparator())\n    },\n\n    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text},\n\n    getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},\n    getLineNumber: function(line) {return lineNo(line)},\n\n    getLineHandleVisualStart: function(line) {\n      if (typeof line == \"number\") { line = getLine(this, line); }\n      return visualLine(line)\n    },\n\n    lineCount: function() {return this.size},\n    firstLine: function() {return this.first},\n    lastLine: function() {return this.first + this.size - 1},\n\n    clipPos: function(pos) {return clipPos(this, pos)},\n\n    getCursor: function(start) {\n      var range = this.sel.primary(), pos;\n      if (start == null || start == \"head\") { pos = range.head; }\n      else if (start == \"anchor\") { pos = range.anchor; }\n      else if (start == \"end\" || start == \"to\" || start === false) { pos = range.to(); }\n      else { pos = range.from(); }\n      return pos\n    },\n    listSelections: function() { return this.sel.ranges },\n    somethingSelected: function() {return this.sel.somethingSelected()},\n\n    setCursor: docMethodOp(function(line, ch, options) {\n      setSimpleSelection(this, clipPos(this, typeof line == \"number\" ? Pos(line, ch || 0) : line), null, options);\n    }),\n    setSelection: docMethodOp(function(anchor, head, options) {\n      setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);\n    }),\n    extendSelection: docMethodOp(function(head, other, options) {\n      extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);\n    }),\n    extendSelections: docMethodOp(function(heads, options) {\n      extendSelections(this, clipPosArray(this, heads), options);\n    }),\n    extendSelectionsBy: docMethodOp(function(f, options) {\n      var heads = map(this.sel.ranges, f);\n      extendSelections(this, clipPosArray(this, heads), options);\n    }),\n    setSelections: docMethodOp(function(ranges, primary, options) {\n      if (!ranges.length) { return }\n      var out = [];\n      for (var i = 0; i < ranges.length; i++)\n        { out[i] = new Range(clipPos(this, ranges[i].anchor),\n                           clipPos(this, ranges[i].head)); }\n      if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }\n      setSelection(this, normalizeSelection(this.cm, out, primary), options);\n    }),\n    addSelection: docMethodOp(function(anchor, head, options) {\n      var ranges = this.sel.ranges.slice(0);\n      ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));\n      setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options);\n    }),\n\n    getSelection: function(lineSep) {\n      var ranges = this.sel.ranges, lines;\n      for (var i = 0; i < ranges.length; i++) {\n        var sel = getBetween(this, ranges[i].from(), ranges[i].to());\n        lines = lines ? lines.concat(sel) : sel;\n      }\n      if (lineSep === false) { return lines }\n      else { return lines.join(lineSep || this.lineSeparator()) }\n    },\n    getSelections: function(lineSep) {\n      var parts = [], ranges = this.sel.ranges;\n      for (var i = 0; i < ranges.length; i++) {\n        var sel = getBetween(this, ranges[i].from(), ranges[i].to());\n        if (lineSep !== false) { sel = sel.join(lineSep || this.lineSeparator()); }\n        parts[i] = sel;\n      }\n      return parts\n    },\n    replaceSelection: function(code, collapse, origin) {\n      var dup = [];\n      for (var i = 0; i < this.sel.ranges.length; i++)\n        { dup[i] = code; }\n      this.replaceSelections(dup, collapse, origin || \"+input\");\n    },\n    replaceSelections: docMethodOp(function(code, collapse, origin) {\n      var changes = [], sel = this.sel;\n      for (var i = 0; i < sel.ranges.length; i++) {\n        var range = sel.ranges[i];\n        changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin};\n      }\n      var newSel = collapse && collapse != \"end\" && computeReplacedSel(this, changes, collapse);\n      for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)\n        { makeChange(this, changes[i$1]); }\n      if (newSel) { setSelectionReplaceHistory(this, newSel); }\n      else if (this.cm) { ensureCursorVisible(this.cm); }\n    }),\n    undo: docMethodOp(function() {makeChangeFromHistory(this, \"undo\");}),\n    redo: docMethodOp(function() {makeChangeFromHistory(this, \"redo\");}),\n    undoSelection: docMethodOp(function() {makeChangeFromHistory(this, \"undo\", true);}),\n    redoSelection: docMethodOp(function() {makeChangeFromHistory(this, \"redo\", true);}),\n\n    setExtending: function(val) {this.extend = val;},\n    getExtending: function() {return this.extend},\n\n    historySize: function() {\n      var hist = this.history, done = 0, undone = 0;\n      for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } }\n      for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } }\n      return {undo: done, redo: undone}\n    },\n    clearHistory: function() {\n      var this$1 = this;\n\n      this.history = new History(this.history.maxGeneration);\n      linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true);\n    },\n\n    markClean: function() {\n      this.cleanGeneration = this.changeGeneration(true);\n    },\n    changeGeneration: function(forceSplit) {\n      if (forceSplit)\n        { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; }\n      return this.history.generation\n    },\n    isClean: function (gen) {\n      return this.history.generation == (gen || this.cleanGeneration)\n    },\n\n    getHistory: function() {\n      return {done: copyHistoryArray(this.history.done),\n              undone: copyHistoryArray(this.history.undone)}\n    },\n    setHistory: function(histData) {\n      var hist = this.history = new History(this.history.maxGeneration);\n      hist.done = copyHistoryArray(histData.done.slice(0), null, true);\n      hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);\n    },\n\n    setGutterMarker: docMethodOp(function(line, gutterID, value) {\n      return changeLine(this, line, \"gutter\", function (line) {\n        var markers = line.gutterMarkers || (line.gutterMarkers = {});\n        markers[gutterID] = value;\n        if (!value && isEmpty(markers)) { line.gutterMarkers = null; }\n        return true\n      })\n    }),\n\n    clearGutter: docMethodOp(function(gutterID) {\n      var this$1 = this;\n\n      this.iter(function (line) {\n        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {\n          changeLine(this$1, line, \"gutter\", function () {\n            line.gutterMarkers[gutterID] = null;\n            if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; }\n            return true\n          });\n        }\n      });\n    }),\n\n    lineInfo: function(line) {\n      var n;\n      if (typeof line == \"number\") {\n        if (!isLine(this, line)) { return null }\n        n = line;\n        line = getLine(this, line);\n        if (!line) { return null }\n      } else {\n        n = lineNo(line);\n        if (n == null) { return null }\n      }\n      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,\n              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,\n              widgets: line.widgets}\n    },\n\n    addLineClass: docMethodOp(function(handle, where, cls) {\n      return changeLine(this, handle, where == \"gutter\" ? \"gutter\" : \"class\", function (line) {\n        var prop = where == \"text\" ? \"textClass\"\n                 : where == \"background\" ? \"bgClass\"\n                 : where == \"gutter\" ? \"gutterClass\" : \"wrapClass\";\n        if (!line[prop]) { line[prop] = cls; }\n        else if (classTest(cls).test(line[prop])) { return false }\n        else { line[prop] += \" \" + cls; }\n        return true\n      })\n    }),\n    removeLineClass: docMethodOp(function(handle, where, cls) {\n      return changeLine(this, handle, where == \"gutter\" ? \"gutter\" : \"class\", function (line) {\n        var prop = where == \"text\" ? \"textClass\"\n                 : where == \"background\" ? \"bgClass\"\n                 : where == \"gutter\" ? \"gutterClass\" : \"wrapClass\";\n        var cur = line[prop];\n        if (!cur) { return false }\n        else if (cls == null) { line[prop] = null; }\n        else {\n          var found = cur.match(classTest(cls));\n          if (!found) { return false }\n          var end = found.index + found[0].length;\n          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? \"\" : \" \") + cur.slice(end) || null;\n        }\n        return true\n      })\n    }),\n\n    addLineWidget: docMethodOp(function(handle, node, options) {\n      return addLineWidget(this, handle, node, options)\n    }),\n    removeLineWidget: function(widget) { widget.clear(); },\n\n    markText: function(from, to, options) {\n      return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || \"range\")\n    },\n    setBookmark: function(pos, options) {\n      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),\n                      insertLeft: options && options.insertLeft,\n                      clearWhenEmpty: false, shared: options && options.shared,\n                      handleMouseEvents: options && options.handleMouseEvents};\n      pos = clipPos(this, pos);\n      return markText(this, pos, pos, realOpts, \"bookmark\")\n    },\n    findMarksAt: function(pos) {\n      pos = clipPos(this, pos);\n      var markers = [], spans = getLine(this, pos.line).markedSpans;\n      if (spans) { for (var i = 0; i < spans.length; ++i) {\n        var span = spans[i];\n        if ((span.from == null || span.from <= pos.ch) &&\n            (span.to == null || span.to >= pos.ch))\n          { markers.push(span.marker.parent || span.marker); }\n      } }\n      return markers\n    },\n    findMarks: function(from, to, filter) {\n      from = clipPos(this, from); to = clipPos(this, to);\n      var found = [], lineNo = from.line;\n      this.iter(from.line, to.line + 1, function (line) {\n        var spans = line.markedSpans;\n        if (spans) { for (var i = 0; i < spans.length; i++) {\n          var span = spans[i];\n          if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||\n                span.from == null && lineNo != from.line ||\n                span.from != null && lineNo == to.line && span.from >= to.ch) &&\n              (!filter || filter(span.marker)))\n            { found.push(span.marker.parent || span.marker); }\n        } }\n        ++lineNo;\n      });\n      return found\n    },\n    getAllMarks: function() {\n      var markers = [];\n      this.iter(function (line) {\n        var sps = line.markedSpans;\n        if (sps) { for (var i = 0; i < sps.length; ++i)\n          { if (sps[i].from != null) { markers.push(sps[i].marker); } } }\n      });\n      return markers\n    },\n\n    posFromIndex: function(off) {\n      var ch, lineNo = this.first, sepSize = this.lineSeparator().length;\n      this.iter(function (line) {\n        var sz = line.text.length + sepSize;\n        if (sz > off) { ch = off; return true }\n        off -= sz;\n        ++lineNo;\n      });\n      return clipPos(this, Pos(lineNo, ch))\n    },\n    indexFromPos: function (coords) {\n      coords = clipPos(this, coords);\n      var index = coords.ch;\n      if (coords.line < this.first || coords.ch < 0) { return 0 }\n      var sepSize = this.lineSeparator().length;\n      this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value\n        index += line.text.length + sepSize;\n      });\n      return index\n    },\n\n    copy: function(copyHistory) {\n      var doc = new Doc(getLines(this, this.first, this.first + this.size),\n                        this.modeOption, this.first, this.lineSep, this.direction);\n      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;\n      doc.sel = this.sel;\n      doc.extend = false;\n      if (copyHistory) {\n        doc.history.undoDepth = this.history.undoDepth;\n        doc.setHistory(this.getHistory());\n      }\n      return doc\n    },\n\n    linkedDoc: function(options) {\n      if (!options) { options = {}; }\n      var from = this.first, to = this.first + this.size;\n      if (options.from != null && options.from > from) { from = options.from; }\n      if (options.to != null && options.to < to) { to = options.to; }\n      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction);\n      if (options.sharedHist) { copy.history = this.history\n      ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});\n      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];\n      copySharedMarkers(copy, findSharedMarkers(this));\n      return copy\n    },\n    unlinkDoc: function(other) {\n      if (other instanceof CodeMirror) { other = other.doc; }\n      if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {\n        var link = this.linked[i];\n        if (link.doc != other) { continue }\n        this.linked.splice(i, 1);\n        other.unlinkDoc(this);\n        detachSharedMarkers(findSharedMarkers(this));\n        break\n      } }\n      // If the histories were shared, split them again\n      if (other.history == this.history) {\n        var splitIds = [other.id];\n        linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true);\n        other.history = new History(null);\n        other.history.done = copyHistoryArray(this.history.done, splitIds);\n        other.history.undone = copyHistoryArray(this.history.undone, splitIds);\n      }\n    },\n    iterLinkedDocs: function(f) {linkedDocs(this, f);},\n\n    getMode: function() {return this.mode},\n    getEditor: function() {return this.cm},\n\n    splitLines: function(str) {\n      if (this.lineSep) { return str.split(this.lineSep) }\n      return splitLinesAuto(str)\n    },\n    lineSeparator: function() { return this.lineSep || \"\\n\" },\n\n    setDirection: docMethodOp(function (dir) {\n      if (dir != \"rtl\") { dir = \"ltr\"; }\n      if (dir == this.direction) { return }\n      this.direction = dir;\n      this.iter(function (line) { return line.order = null; });\n      if (this.cm) { directionChanged(this.cm); }\n    })\n  });\n\n  // Public alias.\n  Doc.prototype.eachLine = Doc.prototype.iter;\n\n  // Kludge to work around strange IE behavior where it'll sometimes\n  // re-fire a series of drag-related events right after the drop (#1551)\n  var lastDrop = 0;\n\n  function onDrop(e) {\n    var cm = this;\n    clearDragCursor(cm);\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))\n      { return }\n    e_preventDefault(e);\n    if (ie) { lastDrop = +new Date; }\n    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;\n    if (!pos || cm.isReadOnly()) { return }\n    // Might be a file drop, in which case we simply extract the text\n    // and insert it.\n    if (files && files.length && window.FileReader && window.File) {\n      var n = files.length, text = Array(n), read = 0;\n      var markAsReadAndPasteIfAllFilesAreRead = function () {\n        if (++read == n) {\n          operation(cm, function () {\n            pos = clipPos(cm.doc, pos);\n            var change = {from: pos, to: pos,\n                          text: cm.doc.splitLines(\n                              text.filter(function (t) { return t != null; }).join(cm.doc.lineSeparator())),\n                          origin: \"paste\"};\n            makeChange(cm.doc, change);\n            setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change))));\n          })();\n        }\n      };\n      var readTextFromFile = function (file, i) {\n        if (cm.options.allowDropFileTypes &&\n            indexOf(cm.options.allowDropFileTypes, file.type) == -1) {\n          markAsReadAndPasteIfAllFilesAreRead();\n          return\n        }\n        var reader = new FileReader;\n        reader.onerror = function () { return markAsReadAndPasteIfAllFilesAreRead(); };\n        reader.onload = function () {\n          var content = reader.result;\n          if (/[\\x00-\\x08\\x0e-\\x1f]{2}/.test(content)) {\n            markAsReadAndPasteIfAllFilesAreRead();\n            return\n          }\n          text[i] = content;\n          markAsReadAndPasteIfAllFilesAreRead();\n        };\n        reader.readAsText(file);\n      };\n      for (var i = 0; i < files.length; i++) { readTextFromFile(files[i], i); }\n    } else { // Normal drop\n      // Don't do a replace if the drop happened inside of the selected text.\n      if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {\n        cm.state.draggingText(e);\n        // Ensure the editor is re-focused\n        setTimeout(function () { return cm.display.input.focus(); }, 20);\n        return\n      }\n      try {\n        var text$1 = e.dataTransfer.getData(\"Text\");\n        if (text$1) {\n          var selected;\n          if (cm.state.draggingText && !cm.state.draggingText.copy)\n            { selected = cm.listSelections(); }\n          setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));\n          if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)\n            { replaceRange(cm.doc, \"\", selected[i$1].anchor, selected[i$1].head, \"drag\"); } }\n          cm.replaceSelection(text$1, \"around\", \"paste\");\n          cm.display.input.focus();\n        }\n      }\n      catch(e$1){}\n    }\n  }\n\n  function onDragStart(cm, e) {\n    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return }\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return }\n\n    e.dataTransfer.setData(\"Text\", cm.getSelection());\n    e.dataTransfer.effectAllowed = \"copyMove\";\n\n    // Use dummy image instead of default browsers image.\n    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.\n    if (e.dataTransfer.setDragImage && !safari) {\n      var img = elt(\"img\", null, null, \"position: fixed; left: 0; top: 0;\");\n      img.src = \"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\";\n      if (presto) {\n        img.width = img.height = 1;\n        cm.display.wrapper.appendChild(img);\n        // Force a relayout, or Opera won't use our image for some obscure reason\n        img._top = img.offsetTop;\n      }\n      e.dataTransfer.setDragImage(img, 0, 0);\n      if (presto) { img.parentNode.removeChild(img); }\n    }\n  }\n\n  function onDragOver(cm, e) {\n    var pos = posFromMouse(cm, e);\n    if (!pos) { return }\n    var frag = document.createDocumentFragment();\n    drawSelectionCursor(cm, pos, frag);\n    if (!cm.display.dragCursor) {\n      cm.display.dragCursor = elt(\"div\", null, \"CodeMirror-cursors CodeMirror-dragcursors\");\n      cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);\n    }\n    removeChildrenAndAdd(cm.display.dragCursor, frag);\n  }\n\n  function clearDragCursor(cm) {\n    if (cm.display.dragCursor) {\n      cm.display.lineSpace.removeChild(cm.display.dragCursor);\n      cm.display.dragCursor = null;\n    }\n  }\n\n  // These must be handled carefully, because naively registering a\n  // handler for each editor will cause the editors to never be\n  // garbage collected.\n\n  function forEachCodeMirror(f) {\n    if (!document.getElementsByClassName) { return }\n    var byClass = document.getElementsByClassName(\"CodeMirror\"), editors = [];\n    for (var i = 0; i < byClass.length; i++) {\n      var cm = byClass[i].CodeMirror;\n      if (cm) { editors.push(cm); }\n    }\n    if (editors.length) { editors[0].operation(function () {\n      for (var i = 0; i < editors.length; i++) { f(editors[i]); }\n    }); }\n  }\n\n  var globalsRegistered = false;\n  function ensureGlobalHandlers() {\n    if (globalsRegistered) { return }\n    registerGlobalHandlers();\n    globalsRegistered = true;\n  }\n  function registerGlobalHandlers() {\n    // When the window resizes, we need to refresh active editors.\n    var resizeTimer;\n    on(window, \"resize\", function () {\n      if (resizeTimer == null) { resizeTimer = setTimeout(function () {\n        resizeTimer = null;\n        forEachCodeMirror(onResize);\n      }, 100); }\n    });\n    // When the window loses focus, we want to show the editor as blurred\n    on(window, \"blur\", function () { return forEachCodeMirror(onBlur); });\n  }\n  // Called when the window resizes\n  function onResize(cm) {\n    var d = cm.display;\n    // Might be a text scaling operation, clear size caches.\n    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;\n    d.scrollbarsClipped = false;\n    cm.setSize();\n  }\n\n  var keyNames = {\n    3: \"Pause\", 8: \"Backspace\", 9: \"Tab\", 13: \"Enter\", 16: \"Shift\", 17: \"Ctrl\", 18: \"Alt\",\n    19: \"Pause\", 20: \"CapsLock\", 27: \"Esc\", 32: \"Space\", 33: \"PageUp\", 34: \"PageDown\", 35: \"End\",\n    36: \"Home\", 37: \"Left\", 38: \"Up\", 39: \"Right\", 40: \"Down\", 44: \"PrintScrn\", 45: \"Insert\",\n    46: \"Delete\", 59: \";\", 61: \"=\", 91: \"Mod\", 92: \"Mod\", 93: \"Mod\",\n    106: \"*\", 107: \"=\", 109: \"-\", 110: \".\", 111: \"/\", 145: \"ScrollLock\",\n    173: \"-\", 186: \";\", 187: \"=\", 188: \",\", 189: \"-\", 190: \".\", 191: \"/\", 192: \"`\", 219: \"[\", 220: \"\\\\\",\n    221: \"]\", 222: \"'\", 63232: \"Up\", 63233: \"Down\", 63234: \"Left\", 63235: \"Right\", 63272: \"Delete\",\n    63273: \"Home\", 63275: \"End\", 63276: \"PageUp\", 63277: \"PageDown\", 63302: \"Insert\"\n  };\n\n  // Number keys\n  for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); }\n  // Alphabetic keys\n  for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); }\n  // Function keys\n  for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = \"F\" + i$2; }\n\n  var keyMap = {};\n\n  keyMap.basic = {\n    \"Left\": \"goCharLeft\", \"Right\": \"goCharRight\", \"Up\": \"goLineUp\", \"Down\": \"goLineDown\",\n    \"End\": \"goLineEnd\", \"Home\": \"goLineStartSmart\", \"PageUp\": \"goPageUp\", \"PageDown\": \"goPageDown\",\n    \"Delete\": \"delCharAfter\", \"Backspace\": \"delCharBefore\", \"Shift-Backspace\": \"delCharBefore\",\n    \"Tab\": \"defaultTab\", \"Shift-Tab\": \"indentAuto\",\n    \"Enter\": \"newlineAndIndent\", \"Insert\": \"toggleOverwrite\",\n    \"Esc\": \"singleSelection\"\n  };\n  // Note that the save and find-related commands aren't defined by\n  // default. User code or addons can define them. Unknown commands\n  // are simply ignored.\n  keyMap.pcDefault = {\n    \"Ctrl-A\": \"selectAll\", \"Ctrl-D\": \"deleteLine\", \"Ctrl-Z\": \"undo\", \"Shift-Ctrl-Z\": \"redo\", \"Ctrl-Y\": \"redo\",\n    \"Ctrl-Home\": \"goDocStart\", \"Ctrl-End\": \"goDocEnd\", \"Ctrl-Up\": \"goLineUp\", \"Ctrl-Down\": \"goLineDown\",\n    \"Ctrl-Left\": \"goGroupLeft\", \"Ctrl-Right\": \"goGroupRight\", \"Alt-Left\": \"goLineStart\", \"Alt-Right\": \"goLineEnd\",\n    \"Ctrl-Backspace\": \"delGroupBefore\", \"Ctrl-Delete\": \"delGroupAfter\", \"Ctrl-S\": \"save\", \"Ctrl-F\": \"find\",\n    \"Ctrl-G\": \"findNext\", \"Shift-Ctrl-G\": \"findPrev\", \"Shift-Ctrl-F\": \"replace\", \"Shift-Ctrl-R\": \"replaceAll\",\n    \"Ctrl-[\": \"indentLess\", \"Ctrl-]\": \"indentMore\",\n    \"Ctrl-U\": \"undoSelection\", \"Shift-Ctrl-U\": \"redoSelection\", \"Alt-U\": \"redoSelection\",\n    \"fallthrough\": \"basic\"\n  };\n  // Very basic readline/emacs-style bindings, which are standard on Mac.\n  keyMap.emacsy = {\n    \"Ctrl-F\": \"goCharRight\", \"Ctrl-B\": \"goCharLeft\", \"Ctrl-P\": \"goLineUp\", \"Ctrl-N\": \"goLineDown\",\n    \"Alt-F\": \"goWordRight\", \"Alt-B\": \"goWordLeft\", \"Ctrl-A\": \"goLineStart\", \"Ctrl-E\": \"goLineEnd\",\n    \"Ctrl-V\": \"goPageDown\", \"Shift-Ctrl-V\": \"goPageUp\", \"Ctrl-D\": \"delCharAfter\", \"Ctrl-H\": \"delCharBefore\",\n    \"Alt-D\": \"delWordAfter\", \"Alt-Backspace\": \"delWordBefore\", \"Ctrl-K\": \"killLine\", \"Ctrl-T\": \"transposeChars\",\n    \"Ctrl-O\": \"openLine\"\n  };\n  keyMap.macDefault = {\n    \"Cmd-A\": \"selectAll\", \"Cmd-D\": \"deleteLine\", \"Cmd-Z\": \"undo\", \"Shift-Cmd-Z\": \"redo\", \"Cmd-Y\": \"redo\",\n    \"Cmd-Home\": \"goDocStart\", \"Cmd-Up\": \"goDocStart\", \"Cmd-End\": \"goDocEnd\", \"Cmd-Down\": \"goDocEnd\", \"Alt-Left\": \"goGroupLeft\",\n    \"Alt-Right\": \"goGroupRight\", \"Cmd-Left\": \"goLineLeft\", \"Cmd-Right\": \"goLineRight\", \"Alt-Backspace\": \"delGroupBefore\",\n    \"Ctrl-Alt-Backspace\": \"delGroupAfter\", \"Alt-Delete\": \"delGroupAfter\", \"Cmd-S\": \"save\", \"Cmd-F\": \"find\",\n    \"Cmd-G\": \"findNext\", \"Shift-Cmd-G\": \"findPrev\", \"Cmd-Alt-F\": \"replace\", \"Shift-Cmd-Alt-F\": \"replaceAll\",\n    \"Cmd-[\": \"indentLess\", \"Cmd-]\": \"indentMore\", \"Cmd-Backspace\": \"delWrappedLineLeft\", \"Cmd-Delete\": \"delWrappedLineRight\",\n    \"Cmd-U\": \"undoSelection\", \"Shift-Cmd-U\": \"redoSelection\", \"Ctrl-Up\": \"goDocStart\", \"Ctrl-Down\": \"goDocEnd\",\n    \"fallthrough\": [\"basic\", \"emacsy\"]\n  };\n  keyMap[\"default\"] = mac ? keyMap.macDefault : keyMap.pcDefault;\n\n  // KEYMAP DISPATCH\n\n  function normalizeKeyName(name) {\n    var parts = name.split(/-(?!$)/);\n    name = parts[parts.length - 1];\n    var alt, ctrl, shift, cmd;\n    for (var i = 0; i < parts.length - 1; i++) {\n      var mod = parts[i];\n      if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; }\n      else if (/^a(lt)?$/i.test(mod)) { alt = true; }\n      else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; }\n      else if (/^s(hift)?$/i.test(mod)) { shift = true; }\n      else { throw new Error(\"Unrecognized modifier name: \" + mod) }\n    }\n    if (alt) { name = \"Alt-\" + name; }\n    if (ctrl) { name = \"Ctrl-\" + name; }\n    if (cmd) { name = \"Cmd-\" + name; }\n    if (shift) { name = \"Shift-\" + name; }\n    return name\n  }\n\n  // This is a kludge to keep keymaps mostly working as raw objects\n  // (backwards compatibility) while at the same time support features\n  // like normalization and multi-stroke key bindings. It compiles a\n  // new normalized keymap, and then updates the old object to reflect\n  // this.\n  function normalizeKeyMap(keymap) {\n    var copy = {};\n    for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {\n      var value = keymap[keyname];\n      if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }\n      if (value == \"...\") { delete keymap[keyname]; continue }\n\n      var keys = map(keyname.split(\" \"), normalizeKeyName);\n      for (var i = 0; i < keys.length; i++) {\n        var val = (void 0), name = (void 0);\n        if (i == keys.length - 1) {\n          name = keys.join(\" \");\n          val = value;\n        } else {\n          name = keys.slice(0, i + 1).join(\" \");\n          val = \"...\";\n        }\n        var prev = copy[name];\n        if (!prev) { copy[name] = val; }\n        else if (prev != val) { throw new Error(\"Inconsistent bindings for \" + name) }\n      }\n      delete keymap[keyname];\n    } }\n    for (var prop in copy) { keymap[prop] = copy[prop]; }\n    return keymap\n  }\n\n  function lookupKey(key, map, handle, context) {\n    map = getKeyMap(map);\n    var found = map.call ? map.call(key, context) : map[key];\n    if (found === false) { return \"nothing\" }\n    if (found === \"...\") { return \"multi\" }\n    if (found != null && handle(found)) { return \"handled\" }\n\n    if (map.fallthrough) {\n      if (Object.prototype.toString.call(map.fallthrough) != \"[object Array]\")\n        { return lookupKey(key, map.fallthrough, handle, context) }\n      for (var i = 0; i < map.fallthrough.length; i++) {\n        var result = lookupKey(key, map.fallthrough[i], handle, context);\n        if (result) { return result }\n      }\n    }\n  }\n\n  // Modifier key presses don't count as 'real' key presses for the\n  // purpose of keymap fallthrough.\n  function isModifierKey(value) {\n    var name = typeof value == \"string\" ? value : keyNames[value.keyCode];\n    return name == \"Ctrl\" || name == \"Alt\" || name == \"Shift\" || name == \"Mod\"\n  }\n\n  function addModifierNames(name, event, noShift) {\n    var base = name;\n    if (event.altKey && base != \"Alt\") { name = \"Alt-\" + name; }\n    if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != \"Ctrl\") { name = \"Ctrl-\" + name; }\n    if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != \"Cmd\") { name = \"Cmd-\" + name; }\n    if (!noShift && event.shiftKey && base != \"Shift\") { name = \"Shift-\" + name; }\n    return name\n  }\n\n  // Look up the name of a key as indicated by an event object.\n  function keyName(event, noShift) {\n    if (presto && event.keyCode == 34 && event[\"char\"]) { return false }\n    var name = keyNames[event.keyCode];\n    if (name == null || event.altGraphKey) { return false }\n    // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,\n    // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)\n    if (event.keyCode == 3 && event.code) { name = event.code; }\n    return addModifierNames(name, event, noShift)\n  }\n\n  function getKeyMap(val) {\n    return typeof val == \"string\" ? keyMap[val] : val\n  }\n\n  // Helper for deleting text near the selection(s), used to implement\n  // backspace, delete, and similar functionality.\n  function deleteNearSelection(cm, compute) {\n    var ranges = cm.doc.sel.ranges, kill = [];\n    // Build up a set of ranges to kill first, merging overlapping\n    // ranges.\n    for (var i = 0; i < ranges.length; i++) {\n      var toKill = compute(ranges[i]);\n      while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {\n        var replaced = kill.pop();\n        if (cmp(replaced.from, toKill.from) < 0) {\n          toKill.from = replaced.from;\n          break\n        }\n      }\n      kill.push(toKill);\n    }\n    // Next, remove those actual ranges.\n    runInOp(cm, function () {\n      for (var i = kill.length - 1; i >= 0; i--)\n        { replaceRange(cm.doc, \"\", kill[i].from, kill[i].to, \"+delete\"); }\n      ensureCursorVisible(cm);\n    });\n  }\n\n  function moveCharLogically(line, ch, dir) {\n    var target = skipExtendingChars(line.text, ch + dir, dir);\n    return target < 0 || target > line.text.length ? null : target\n  }\n\n  function moveLogically(line, start, dir) {\n    var ch = moveCharLogically(line, start.ch, dir);\n    return ch == null ? null : new Pos(start.line, ch, dir < 0 ? \"after\" : \"before\")\n  }\n\n  function endOfLine(visually, cm, lineObj, lineNo, dir) {\n    if (visually) {\n      if (cm.doc.direction == \"rtl\") { dir = -dir; }\n      var order = getOrder(lineObj, cm.doc.direction);\n      if (order) {\n        var part = dir < 0 ? lst(order) : order[0];\n        var moveInStorageOrder = (dir < 0) == (part.level == 1);\n        var sticky = moveInStorageOrder ? \"after\" : \"before\";\n        var ch;\n        // With a wrapped rtl chunk (possibly spanning multiple bidi parts),\n        // it could be that the last bidi part is not on the last visual line,\n        // since visual lines contain content order-consecutive chunks.\n        // Thus, in rtl, we are looking for the first (content-order) character\n        // in the rtl chunk that is on the last line (that is, the same line\n        // as the last (content-order) character).\n        if (part.level > 0 || cm.doc.direction == \"rtl\") {\n          var prep = prepareMeasureForLine(cm, lineObj);\n          ch = dir < 0 ? lineObj.text.length - 1 : 0;\n          var targetTop = measureCharPrepared(cm, prep, ch).top;\n          ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch);\n          if (sticky == \"before\") { ch = moveCharLogically(lineObj, ch, 1); }\n        } else { ch = dir < 0 ? part.to : part.from; }\n        return new Pos(lineNo, ch, sticky)\n      }\n    }\n    return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? \"before\" : \"after\")\n  }\n\n  function moveVisually(cm, line, start, dir) {\n    var bidi = getOrder(line, cm.doc.direction);\n    if (!bidi) { return moveLogically(line, start, dir) }\n    if (start.ch >= line.text.length) {\n      start.ch = line.text.length;\n      start.sticky = \"before\";\n    } else if (start.ch <= 0) {\n      start.ch = 0;\n      start.sticky = \"after\";\n    }\n    var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos];\n    if (cm.doc.direction == \"ltr\" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {\n      // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,\n      // nothing interesting happens.\n      return moveLogically(line, start, dir)\n    }\n\n    var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); };\n    var prep;\n    var getWrappedLineExtent = function (ch) {\n      if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} }\n      prep = prep || prepareMeasureForLine(cm, line);\n      return wrappedLineExtentChar(cm, line, prep, ch)\n    };\n    var wrappedLineExtent = getWrappedLineExtent(start.sticky == \"before\" ? mv(start, -1) : start.ch);\n\n    if (cm.doc.direction == \"rtl\" || part.level == 1) {\n      var moveInStorageOrder = (part.level == 1) == (dir < 0);\n      var ch = mv(start, moveInStorageOrder ? 1 : -1);\n      if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {\n        // Case 2: We move within an rtl part or in an rtl editor on the same visual line\n        var sticky = moveInStorageOrder ? \"before\" : \"after\";\n        return new Pos(start.line, ch, sticky)\n      }\n    }\n\n    // Case 3: Could not move within this bidi part in this visual line, so leave\n    // the current bidi part\n\n    var searchInVisualLine = function (partPos, dir, wrappedLineExtent) {\n      var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder\n        ? new Pos(start.line, mv(ch, 1), \"before\")\n        : new Pos(start.line, ch, \"after\"); };\n\n      for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {\n        var part = bidi[partPos];\n        var moveInStorageOrder = (dir > 0) == (part.level != 1);\n        var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1);\n        if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) }\n        ch = moveInStorageOrder ? part.from : mv(part.to, -1);\n        if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) }\n      }\n    };\n\n    // Case 3a: Look for other bidi parts on the same visual line\n    var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent);\n    if (res) { return res }\n\n    // Case 3b: Look for other bidi parts on the next visual line\n    var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1);\n    if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {\n      res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh));\n      if (res) { return res }\n    }\n\n    // Case 4: Nowhere to move\n    return null\n  }\n\n  // Commands are parameter-less actions that can be performed on an\n  // editor, mostly used for keybindings.\n  var commands = {\n    selectAll: selectAll,\n    singleSelection: function (cm) { return cm.setSelection(cm.getCursor(\"anchor\"), cm.getCursor(\"head\"), sel_dontScroll); },\n    killLine: function (cm) { return deleteNearSelection(cm, function (range) {\n      if (range.empty()) {\n        var len = getLine(cm.doc, range.head.line).text.length;\n        if (range.head.ch == len && range.head.line < cm.lastLine())\n          { return {from: range.head, to: Pos(range.head.line + 1, 0)} }\n        else\n          { return {from: range.head, to: Pos(range.head.line, len)} }\n      } else {\n        return {from: range.from(), to: range.to()}\n      }\n    }); },\n    deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({\n      from: Pos(range.from().line, 0),\n      to: clipPos(cm.doc, Pos(range.to().line + 1, 0))\n    }); }); },\n    delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({\n      from: Pos(range.from().line, 0), to: range.from()\n    }); }); },\n    delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {\n      var top = cm.charCoords(range.head, \"div\").top + 5;\n      var leftPos = cm.coordsChar({left: 0, top: top}, \"div\");\n      return {from: leftPos, to: range.from()}\n    }); },\n    delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) {\n      var top = cm.charCoords(range.head, \"div\").top + 5;\n      var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\");\n      return {from: range.from(), to: rightPos }\n    }); },\n    undo: function (cm) { return cm.undo(); },\n    redo: function (cm) { return cm.redo(); },\n    undoSelection: function (cm) { return cm.undoSelection(); },\n    redoSelection: function (cm) { return cm.redoSelection(); },\n    goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },\n    goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },\n    goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); },\n      {origin: \"+move\", bias: 1}\n    ); },\n    goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },\n      {origin: \"+move\", bias: 1}\n    ); },\n    goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },\n      {origin: \"+move\", bias: -1}\n    ); },\n    goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\")\n    }, sel_move); },\n    goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      return cm.coordsChar({left: 0, top: top}, \"div\")\n    }, sel_move); },\n    goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      var pos = cm.coordsChar({left: 0, top: top}, \"div\");\n      if (pos.ch < cm.getLine(pos.line).search(/\\S/)) { return lineStartSmart(cm, range.head) }\n      return pos\n    }, sel_move); },\n    goLineUp: function (cm) { return cm.moveV(-1, \"line\"); },\n    goLineDown: function (cm) { return cm.moveV(1, \"line\"); },\n    goPageUp: function (cm) { return cm.moveV(-1, \"page\"); },\n    goPageDown: function (cm) { return cm.moveV(1, \"page\"); },\n    goCharLeft: function (cm) { return cm.moveH(-1, \"char\"); },\n    goCharRight: function (cm) { return cm.moveH(1, \"char\"); },\n    goColumnLeft: function (cm) { return cm.moveH(-1, \"column\"); },\n    goColumnRight: function (cm) { return cm.moveH(1, \"column\"); },\n    goWordLeft: function (cm) { return cm.moveH(-1, \"word\"); },\n    goGroupRight: function (cm) { return cm.moveH(1, \"group\"); },\n    goGroupLeft: function (cm) { return cm.moveH(-1, \"group\"); },\n    goWordRight: function (cm) { return cm.moveH(1, \"word\"); },\n    delCharBefore: function (cm) { return cm.deleteH(-1, \"char\"); },\n    delCharAfter: function (cm) { return cm.deleteH(1, \"char\"); },\n    delWordBefore: function (cm) { return cm.deleteH(-1, \"word\"); },\n    delWordAfter: function (cm) { return cm.deleteH(1, \"word\"); },\n    delGroupBefore: function (cm) { return cm.deleteH(-1, \"group\"); },\n    delGroupAfter: function (cm) { return cm.deleteH(1, \"group\"); },\n    indentAuto: function (cm) { return cm.indentSelection(\"smart\"); },\n    indentMore: function (cm) { return cm.indentSelection(\"add\"); },\n    indentLess: function (cm) { return cm.indentSelection(\"subtract\"); },\n    insertTab: function (cm) { return cm.replaceSelection(\"\\t\"); },\n    insertSoftTab: function (cm) {\n      var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;\n      for (var i = 0; i < ranges.length; i++) {\n        var pos = ranges[i].from();\n        var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);\n        spaces.push(spaceStr(tabSize - col % tabSize));\n      }\n      cm.replaceSelections(spaces);\n    },\n    defaultTab: function (cm) {\n      if (cm.somethingSelected()) { cm.indentSelection(\"add\"); }\n      else { cm.execCommand(\"insertTab\"); }\n    },\n    // Swap the two chars left and right of each selection's head.\n    // Move cursor behind the two swapped characters afterwards.\n    //\n    // Doesn't consider line feeds a character.\n    // Doesn't scan more than one line above to find a character.\n    // Doesn't do anything on an empty line.\n    // Doesn't do anything with non-empty selections.\n    transposeChars: function (cm) { return runInOp(cm, function () {\n      var ranges = cm.listSelections(), newSel = [];\n      for (var i = 0; i < ranges.length; i++) {\n        if (!ranges[i].empty()) { continue }\n        var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;\n        if (line) {\n          if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); }\n          if (cur.ch > 0) {\n            cur = new Pos(cur.line, cur.ch + 1);\n            cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),\n                            Pos(cur.line, cur.ch - 2), cur, \"+transpose\");\n          } else if (cur.line > cm.doc.first) {\n            var prev = getLine(cm.doc, cur.line - 1).text;\n            if (prev) {\n              cur = new Pos(cur.line, 1);\n              cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +\n                              prev.charAt(prev.length - 1),\n                              Pos(cur.line - 1, prev.length - 1), cur, \"+transpose\");\n            }\n          }\n        }\n        newSel.push(new Range(cur, cur));\n      }\n      cm.setSelections(newSel);\n    }); },\n    newlineAndIndent: function (cm) { return runInOp(cm, function () {\n      var sels = cm.listSelections();\n      for (var i = sels.length - 1; i >= 0; i--)\n        { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, \"+input\"); }\n      sels = cm.listSelections();\n      for (var i$1 = 0; i$1 < sels.length; i$1++)\n        { cm.indentLine(sels[i$1].from().line, null, true); }\n      ensureCursorVisible(cm);\n    }); },\n    openLine: function (cm) { return cm.replaceSelection(\"\\n\", \"start\"); },\n    toggleOverwrite: function (cm) { return cm.toggleOverwrite(); }\n  };\n\n\n  function lineStart(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLine(line);\n    if (visual != line) { lineN = lineNo(visual); }\n    return endOfLine(true, cm, visual, lineN, 1)\n  }\n  function lineEnd(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLineEnd(line);\n    if (visual != line) { lineN = lineNo(visual); }\n    return endOfLine(true, cm, line, lineN, -1)\n  }\n  function lineStartSmart(cm, pos) {\n    var start = lineStart(cm, pos.line);\n    var line = getLine(cm.doc, start.line);\n    var order = getOrder(line, cm.doc.direction);\n    if (!order || order[0].level == 0) {\n      var firstNonWS = Math.max(start.ch, line.text.search(/\\S/));\n      var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;\n      return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)\n    }\n    return start\n  }\n\n  // Run a handler that was bound to a key.\n  function doHandleBinding(cm, bound, dropShift) {\n    if (typeof bound == \"string\") {\n      bound = commands[bound];\n      if (!bound) { return false }\n    }\n    // Ensure previous input has been read, so that the handler sees a\n    // consistent view of the document\n    cm.display.input.ensurePolled();\n    var prevShift = cm.display.shift, done = false;\n    try {\n      if (cm.isReadOnly()) { cm.state.suppressEdits = true; }\n      if (dropShift) { cm.display.shift = false; }\n      done = bound(cm) != Pass;\n    } finally {\n      cm.display.shift = prevShift;\n      cm.state.suppressEdits = false;\n    }\n    return done\n  }\n\n  function lookupKeyForEditor(cm, name, handle) {\n    for (var i = 0; i < cm.state.keyMaps.length; i++) {\n      var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);\n      if (result) { return result }\n    }\n    return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))\n      || lookupKey(name, cm.options.keyMap, handle, cm)\n  }\n\n  // Note that, despite the name, this function is also used to check\n  // for bound mouse clicks.\n\n  var stopSeq = new Delayed;\n\n  function dispatchKey(cm, name, e, handle) {\n    var seq = cm.state.keySeq;\n    if (seq) {\n      if (isModifierKey(name)) { return \"handled\" }\n      if (/\\'$/.test(name))\n        { cm.state.keySeq = null; }\n      else\n        { stopSeq.set(50, function () {\n          if (cm.state.keySeq == seq) {\n            cm.state.keySeq = null;\n            cm.display.input.reset();\n          }\n        }); }\n      if (dispatchKeyInner(cm, seq + \" \" + name, e, handle)) { return true }\n    }\n    return dispatchKeyInner(cm, name, e, handle)\n  }\n\n  function dispatchKeyInner(cm, name, e, handle) {\n    var result = lookupKeyForEditor(cm, name, handle);\n\n    if (result == \"multi\")\n      { cm.state.keySeq = name; }\n    if (result == \"handled\")\n      { signalLater(cm, \"keyHandled\", cm, name, e); }\n\n    if (result == \"handled\" || result == \"multi\") {\n      e_preventDefault(e);\n      restartBlink(cm);\n    }\n\n    return !!result\n  }\n\n  // Handle a key from the keydown event.\n  function handleKeyBinding(cm, e) {\n    var name = keyName(e, true);\n    if (!name) { return false }\n\n    if (e.shiftKey && !cm.state.keySeq) {\n      // First try to resolve full name (including 'Shift-'). Failing\n      // that, see if there is a cursor-motion command (starting with\n      // 'go') bound to the keyname without 'Shift-'.\n      return dispatchKey(cm, \"Shift-\" + name, e, function (b) { return doHandleBinding(cm, b, true); })\n          || dispatchKey(cm, name, e, function (b) {\n               if (typeof b == \"string\" ? /^go[A-Z]/.test(b) : b.motion)\n                 { return doHandleBinding(cm, b) }\n             })\n    } else {\n      return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); })\n    }\n  }\n\n  // Handle a key from the keypress event\n  function handleCharBinding(cm, e, ch) {\n    return dispatchKey(cm, \"'\" + ch + \"'\", e, function (b) { return doHandleBinding(cm, b, true); })\n  }\n\n  var lastStoppedKey = null;\n  function onKeyDown(e) {\n    var cm = this;\n    if (e.target && e.target != cm.display.input.getField()) { return }\n    cm.curOp.focus = activeElt();\n    if (signalDOMEvent(cm, e)) { return }\n    // IE does strange things with escape.\n    if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; }\n    var code = e.keyCode;\n    cm.display.shift = code == 16 || e.shiftKey;\n    var handled = handleKeyBinding(cm, e);\n    if (presto) {\n      lastStoppedKey = handled ? code : null;\n      // Opera has no cut event... we try to at least catch the key combo\n      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))\n        { cm.replaceSelection(\"\", null, \"cut\"); }\n    }\n    if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand)\n      { document.execCommand(\"cut\"); }\n\n    // Turn mouse into crosshair when Alt is held on Mac.\n    if (code == 18 && !/\\bCodeMirror-crosshair\\b/.test(cm.display.lineDiv.className))\n      { showCrossHair(cm); }\n  }\n\n  function showCrossHair(cm) {\n    var lineDiv = cm.display.lineDiv;\n    addClass(lineDiv, \"CodeMirror-crosshair\");\n\n    function up(e) {\n      if (e.keyCode == 18 || !e.altKey) {\n        rmClass(lineDiv, \"CodeMirror-crosshair\");\n        off(document, \"keyup\", up);\n        off(document, \"mouseover\", up);\n      }\n    }\n    on(document, \"keyup\", up);\n    on(document, \"mouseover\", up);\n  }\n\n  function onKeyUp(e) {\n    if (e.keyCode == 16) { this.doc.sel.shift = false; }\n    signalDOMEvent(this, e);\n  }\n\n  function onKeyPress(e) {\n    var cm = this;\n    if (e.target && e.target != cm.display.input.getField()) { return }\n    if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return }\n    var keyCode = e.keyCode, charCode = e.charCode;\n    if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}\n    if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return }\n    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);\n    // Some browsers fire keypress events for backspace\n    if (ch == \"\\x08\") { return }\n    if (handleCharBinding(cm, e, ch)) { return }\n    cm.display.input.onKeyPress(e);\n  }\n\n  var DOUBLECLICK_DELAY = 400;\n\n  var PastClick = function(time, pos, button) {\n    this.time = time;\n    this.pos = pos;\n    this.button = button;\n  };\n\n  PastClick.prototype.compare = function (time, pos, button) {\n    return this.time + DOUBLECLICK_DELAY > time &&\n      cmp(pos, this.pos) == 0 && button == this.button\n  };\n\n  var lastClick, lastDoubleClick;\n  function clickRepeat(pos, button) {\n    var now = +new Date;\n    if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) {\n      lastClick = lastDoubleClick = null;\n      return \"triple\"\n    } else if (lastClick && lastClick.compare(now, pos, button)) {\n      lastDoubleClick = new PastClick(now, pos, button);\n      lastClick = null;\n      return \"double\"\n    } else {\n      lastClick = new PastClick(now, pos, button);\n      lastDoubleClick = null;\n      return \"single\"\n    }\n  }\n\n  // A mouse down can be a single click, double click, triple click,\n  // start of selection drag, start of text drag, new cursor\n  // (ctrl-click), rectangle drag (alt-drag), or xwin\n  // middle-click-paste. Or it might be a click on something we should\n  // not interfere with, such as a scrollbar or widget.\n  function onMouseDown(e) {\n    var cm = this, display = cm.display;\n    if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return }\n    display.input.ensurePolled();\n    display.shift = e.shiftKey;\n\n    if (eventInWidget(display, e)) {\n      if (!webkit) {\n        // Briefly turn off draggability, to allow widgets to do\n        // normal dragging things.\n        display.scroller.draggable = false;\n        setTimeout(function () { return display.scroller.draggable = true; }, 100);\n      }\n      return\n    }\n    if (clickInGutter(cm, e)) { return }\n    var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : \"single\";\n    window.focus();\n\n    // #3261: make sure, that we're not starting a second selection\n    if (button == 1 && cm.state.selectingText)\n      { cm.state.selectingText(e); }\n\n    if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }\n\n    if (button == 1) {\n      if (pos) { leftButtonDown(cm, pos, repeat, e); }\n      else if (e_target(e) == display.scroller) { e_preventDefault(e); }\n    } else if (button == 2) {\n      if (pos) { extendSelection(cm.doc, pos); }\n      setTimeout(function () { return display.input.focus(); }, 20);\n    } else if (button == 3) {\n      if (captureRightClick) { cm.display.input.onContextMenu(e); }\n      else { delayBlurEvent(cm); }\n    }\n  }\n\n  function handleMappedButton(cm, button, pos, repeat, event) {\n    var name = \"Click\";\n    if (repeat == \"double\") { name = \"Double\" + name; }\n    else if (repeat == \"triple\") { name = \"Triple\" + name; }\n    name = (button == 1 ? \"Left\" : button == 2 ? \"Middle\" : \"Right\") + name;\n\n    return dispatchKey(cm,  addModifierNames(name, event), event, function (bound) {\n      if (typeof bound == \"string\") { bound = commands[bound]; }\n      if (!bound) { return false }\n      var done = false;\n      try {\n        if (cm.isReadOnly()) { cm.state.suppressEdits = true; }\n        done = bound(cm, pos) != Pass;\n      } finally {\n        cm.state.suppressEdits = false;\n      }\n      return done\n    })\n  }\n\n  function configureMouse(cm, repeat, event) {\n    var option = cm.getOption(\"configureMouse\");\n    var value = option ? option(cm, repeat, event) : {};\n    if (value.unit == null) {\n      var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey;\n      value.unit = rect ? \"rectangle\" : repeat == \"single\" ? \"char\" : repeat == \"double\" ? \"word\" : \"line\";\n    }\n    if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; }\n    if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; }\n    if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); }\n    return value\n  }\n\n  function leftButtonDown(cm, pos, repeat, event) {\n    if (ie) { setTimeout(bind(ensureFocus, cm), 0); }\n    else { cm.curOp.focus = activeElt(); }\n\n    var behavior = configureMouse(cm, repeat, event);\n\n    var sel = cm.doc.sel, contained;\n    if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&\n        repeat == \"single\" && (contained = sel.contains(pos)) > -1 &&\n        (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) &&\n        (cmp(contained.to(), pos) > 0 || pos.xRel < 0))\n      { leftButtonStartDrag(cm, event, pos, behavior); }\n    else\n      { leftButtonSelect(cm, event, pos, behavior); }\n  }\n\n  // Start a text drag. When it ends, see if any dragging actually\n  // happen, and treat as a click if it didn't.\n  function leftButtonStartDrag(cm, event, pos, behavior) {\n    var display = cm.display, moved = false;\n    var dragEnd = operation(cm, function (e) {\n      if (webkit) { display.scroller.draggable = false; }\n      cm.state.draggingText = false;\n      off(display.wrapper.ownerDocument, \"mouseup\", dragEnd);\n      off(display.wrapper.ownerDocument, \"mousemove\", mouseMove);\n      off(display.scroller, \"dragstart\", dragStart);\n      off(display.scroller, \"drop\", dragEnd);\n      if (!moved) {\n        e_preventDefault(e);\n        if (!behavior.addNew)\n          { extendSelection(cm.doc, pos, null, null, behavior.extend); }\n        // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)\n        if ((webkit && !safari) || ie && ie_version == 9)\n          { setTimeout(function () {display.wrapper.ownerDocument.body.focus({preventScroll: true}); display.input.focus();}, 20); }\n        else\n          { display.input.focus(); }\n      }\n    });\n    var mouseMove = function(e2) {\n      moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10;\n    };\n    var dragStart = function () { return moved = true; };\n    // Let the drag handler handle this.\n    if (webkit) { display.scroller.draggable = true; }\n    cm.state.draggingText = dragEnd;\n    dragEnd.copy = !behavior.moveOnDrag;\n    // IE's approach to draggable\n    if (display.scroller.dragDrop) { display.scroller.dragDrop(); }\n    on(display.wrapper.ownerDocument, \"mouseup\", dragEnd);\n    on(display.wrapper.ownerDocument, \"mousemove\", mouseMove);\n    on(display.scroller, \"dragstart\", dragStart);\n    on(display.scroller, \"drop\", dragEnd);\n\n    delayBlurEvent(cm);\n    setTimeout(function () { return display.input.focus(); }, 20);\n  }\n\n  function rangeForUnit(cm, pos, unit) {\n    if (unit == \"char\") { return new Range(pos, pos) }\n    if (unit == \"word\") { return cm.findWordAt(pos) }\n    if (unit == \"line\") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }\n    var result = unit(cm, pos);\n    return new Range(result.from, result.to)\n  }\n\n  // Normal selection, as opposed to text dragging.\n  function leftButtonSelect(cm, event, start, behavior) {\n    var display = cm.display, doc = cm.doc;\n    e_preventDefault(event);\n\n    var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;\n    if (behavior.addNew && !behavior.extend) {\n      ourIndex = doc.sel.contains(start);\n      if (ourIndex > -1)\n        { ourRange = ranges[ourIndex]; }\n      else\n        { ourRange = new Range(start, start); }\n    } else {\n      ourRange = doc.sel.primary();\n      ourIndex = doc.sel.primIndex;\n    }\n\n    if (behavior.unit == \"rectangle\") {\n      if (!behavior.addNew) { ourRange = new Range(start, start); }\n      start = posFromMouse(cm, event, true, true);\n      ourIndex = -1;\n    } else {\n      var range = rangeForUnit(cm, start, behavior.unit);\n      if (behavior.extend)\n        { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend); }\n      else\n        { ourRange = range; }\n    }\n\n    if (!behavior.addNew) {\n      ourIndex = 0;\n      setSelection(doc, new Selection([ourRange], 0), sel_mouse);\n      startSel = doc.sel;\n    } else if (ourIndex == -1) {\n      ourIndex = ranges.length;\n      setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex),\n                   {scroll: false, origin: \"*mouse\"});\n    } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == \"char\" && !behavior.extend) {\n      setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),\n                   {scroll: false, origin: \"*mouse\"});\n      startSel = doc.sel;\n    } else {\n      replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);\n    }\n\n    var lastPos = start;\n    function extendTo(pos) {\n      if (cmp(lastPos, pos) == 0) { return }\n      lastPos = pos;\n\n      if (behavior.unit == \"rectangle\") {\n        var ranges = [], tabSize = cm.options.tabSize;\n        var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);\n        var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);\n        var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);\n        for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));\n             line <= end; line++) {\n          var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);\n          if (left == right)\n            { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); }\n          else if (text.length > leftPos)\n            { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); }\n        }\n        if (!ranges.length) { ranges.push(new Range(start, start)); }\n        setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),\n                     {origin: \"*mouse\", scroll: false});\n        cm.scrollIntoView(pos);\n      } else {\n        var oldRange = ourRange;\n        var range = rangeForUnit(cm, pos, behavior.unit);\n        var anchor = oldRange.anchor, head;\n        if (cmp(range.anchor, anchor) > 0) {\n          head = range.head;\n          anchor = minPos(oldRange.from(), range.anchor);\n        } else {\n          head = range.anchor;\n          anchor = maxPos(oldRange.to(), range.head);\n        }\n        var ranges$1 = startSel.ranges.slice(0);\n        ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head));\n        setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse);\n      }\n    }\n\n    var editorSize = display.wrapper.getBoundingClientRect();\n    // Used to ensure timeout re-tries don't fire when another extend\n    // happened in the meantime (clearTimeout isn't reliable -- at\n    // least on Chrome, the timeouts still happen even when cleared,\n    // if the clear happens after their scheduled firing time).\n    var counter = 0;\n\n    function extend(e) {\n      var curCount = ++counter;\n      var cur = posFromMouse(cm, e, true, behavior.unit == \"rectangle\");\n      if (!cur) { return }\n      if (cmp(cur, lastPos) != 0) {\n        cm.curOp.focus = activeElt();\n        extendTo(cur);\n        var visible = visibleLines(display, doc);\n        if (cur.line >= visible.to || cur.line < visible.from)\n          { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); }\n      } else {\n        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;\n        if (outside) { setTimeout(operation(cm, function () {\n          if (counter != curCount) { return }\n          display.scroller.scrollTop += outside;\n          extend(e);\n        }), 50); }\n      }\n    }\n\n    function done(e) {\n      cm.state.selectingText = false;\n      counter = Infinity;\n      // If e is null or undefined we interpret this as someone trying\n      // to explicitly cancel the selection rather than the user\n      // letting go of the mouse button.\n      if (e) {\n        e_preventDefault(e);\n        display.input.focus();\n      }\n      off(display.wrapper.ownerDocument, \"mousemove\", move);\n      off(display.wrapper.ownerDocument, \"mouseup\", up);\n      doc.history.lastSelOrigin = null;\n    }\n\n    var move = operation(cm, function (e) {\n      if (e.buttons === 0 || !e_button(e)) { done(e); }\n      else { extend(e); }\n    });\n    var up = operation(cm, done);\n    cm.state.selectingText = up;\n    on(display.wrapper.ownerDocument, \"mousemove\", move);\n    on(display.wrapper.ownerDocument, \"mouseup\", up);\n  }\n\n  // Used when mouse-selecting to adjust the anchor to the proper side\n  // of a bidi jump depending on the visual position of the head.\n  function bidiSimplify(cm, range) {\n    var anchor = range.anchor;\n    var head = range.head;\n    var anchorLine = getLine(cm.doc, anchor.line);\n    if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range }\n    var order = getOrder(anchorLine);\n    if (!order) { return range }\n    var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index];\n    if (part.from != anchor.ch && part.to != anchor.ch) { return range }\n    var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1);\n    if (boundary == 0 || boundary == order.length) { return range }\n\n    // Compute the relative visual position of the head compared to the\n    // anchor (<0 is to the left, >0 to the right)\n    var leftSide;\n    if (head.line != anchor.line) {\n      leftSide = (head.line - anchor.line) * (cm.doc.direction == \"ltr\" ? 1 : -1) > 0;\n    } else {\n      var headIndex = getBidiPartAt(order, head.ch, head.sticky);\n      var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1);\n      if (headIndex == boundary - 1 || headIndex == boundary)\n        { leftSide = dir < 0; }\n      else\n        { leftSide = dir > 0; }\n    }\n\n    var usePart = order[boundary + (leftSide ? -1 : 0)];\n    var from = leftSide == (usePart.level == 1);\n    var ch = from ? usePart.from : usePart.to, sticky = from ? \"after\" : \"before\";\n    return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head)\n  }\n\n\n  // Determines whether an event happened in the gutter, and fires the\n  // handlers for the corresponding event.\n  function gutterEvent(cm, e, type, prevent) {\n    var mX, mY;\n    if (e.touches) {\n      mX = e.touches[0].clientX;\n      mY = e.touches[0].clientY;\n    } else {\n      try { mX = e.clientX; mY = e.clientY; }\n      catch(e$1) { return false }\n    }\n    if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }\n    if (prevent) { e_preventDefault(e); }\n\n    var display = cm.display;\n    var lineBox = display.lineDiv.getBoundingClientRect();\n\n    if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) }\n    mY -= lineBox.top - display.viewOffset;\n\n    for (var i = 0; i < cm.display.gutterSpecs.length; ++i) {\n      var g = display.gutters.childNodes[i];\n      if (g && g.getBoundingClientRect().right >= mX) {\n        var line = lineAtHeight(cm.doc, mY);\n        var gutter = cm.display.gutterSpecs[i];\n        signal(cm, type, cm, line, gutter.className, e);\n        return e_defaultPrevented(e)\n      }\n    }\n  }\n\n  function clickInGutter(cm, e) {\n    return gutterEvent(cm, e, \"gutterClick\", true)\n  }\n\n  // CONTEXT MENU HANDLING\n\n  // To make the context menu work, we need to briefly unhide the\n  // textarea (making it as unobtrusive as possible) to let the\n  // right-click take effect on it.\n  function onContextMenu(cm, e) {\n    if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return }\n    if (signalDOMEvent(cm, e, \"contextmenu\")) { return }\n    if (!captureRightClick) { cm.display.input.onContextMenu(e); }\n  }\n\n  function contextMenuInGutter(cm, e) {\n    if (!hasHandler(cm, \"gutterContextMenu\")) { return false }\n    return gutterEvent(cm, e, \"gutterContextMenu\", false)\n  }\n\n  function themeChanged(cm) {\n    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\\s*cm-s-\\S+/g, \"\") +\n      cm.options.theme.replace(/(^|\\s)\\s*/g, \" cm-s-\");\n    clearCaches(cm);\n  }\n\n  var Init = {toString: function(){return \"CodeMirror.Init\"}};\n\n  var defaults = {};\n  var optionHandlers = {};\n\n  function defineOptions(CodeMirror) {\n    var optionHandlers = CodeMirror.optionHandlers;\n\n    function option(name, deflt, handle, notOnInit) {\n      CodeMirror.defaults[name] = deflt;\n      if (handle) { optionHandlers[name] =\n        notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; }\n    }\n\n    CodeMirror.defineOption = option;\n\n    // Passed to option handlers when there is no old value.\n    CodeMirror.Init = Init;\n\n    // These two are, on init, called from the constructor because they\n    // have to be initialized before the editor can start at all.\n    option(\"value\", \"\", function (cm, val) { return cm.setValue(val); }, true);\n    option(\"mode\", null, function (cm, val) {\n      cm.doc.modeOption = val;\n      loadMode(cm);\n    }, true);\n\n    option(\"indentUnit\", 2, loadMode, true);\n    option(\"indentWithTabs\", false);\n    option(\"smartIndent\", true);\n    option(\"tabSize\", 4, function (cm) {\n      resetModeState(cm);\n      clearCaches(cm);\n      regChange(cm);\n    }, true);\n\n    option(\"lineSeparator\", null, function (cm, val) {\n      cm.doc.lineSep = val;\n      if (!val) { return }\n      var newBreaks = [], lineNo = cm.doc.first;\n      cm.doc.iter(function (line) {\n        for (var pos = 0;;) {\n          var found = line.text.indexOf(val, pos);\n          if (found == -1) { break }\n          pos = found + val.length;\n          newBreaks.push(Pos(lineNo, found));\n        }\n        lineNo++;\n      });\n      for (var i = newBreaks.length - 1; i >= 0; i--)\n        { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }\n    });\n    option(\"specialChars\", /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u200b-\\u200c\\u200e\\u200f\\u2028\\u2029\\ufeff\\ufff9-\\ufffc]/g, function (cm, val, old) {\n      cm.state.specialChars = new RegExp(val.source + (val.test(\"\\t\") ? \"\" : \"|\\t\"), \"g\");\n      if (old != Init) { cm.refresh(); }\n    });\n    option(\"specialCharPlaceholder\", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true);\n    option(\"electricChars\", true);\n    option(\"inputStyle\", mobile ? \"contenteditable\" : \"textarea\", function () {\n      throw new Error(\"inputStyle can not (yet) be changed in a running editor\") // FIXME\n    }, true);\n    option(\"spellcheck\", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true);\n    option(\"autocorrect\", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true);\n    option(\"autocapitalize\", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true);\n    option(\"rtlMoveVisually\", !windows);\n    option(\"wholeLineUpdateBefore\", true);\n\n    option(\"theme\", \"default\", function (cm) {\n      themeChanged(cm);\n      updateGutters(cm);\n    }, true);\n    option(\"keyMap\", \"default\", function (cm, val, old) {\n      var next = getKeyMap(val);\n      var prev = old != Init && getKeyMap(old);\n      if (prev && prev.detach) { prev.detach(cm, next); }\n      if (next.attach) { next.attach(cm, prev || null); }\n    });\n    option(\"extraKeys\", null);\n    option(\"configureMouse\", null);\n\n    option(\"lineWrapping\", false, wrappingChanged, true);\n    option(\"gutters\", [], function (cm, val) {\n      cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers);\n      updateGutters(cm);\n    }, true);\n    option(\"fixedGutter\", true, function (cm, val) {\n      cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + \"px\" : \"0\";\n      cm.refresh();\n    }, true);\n    option(\"coverGutterNextToScrollbar\", false, function (cm) { return updateScrollbars(cm); }, true);\n    option(\"scrollbarStyle\", \"native\", function (cm) {\n      initScrollbars(cm);\n      updateScrollbars(cm);\n      cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);\n      cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);\n    }, true);\n    option(\"lineNumbers\", false, function (cm, val) {\n      cm.display.gutterSpecs = getGutters(cm.options.gutters, val);\n      updateGutters(cm);\n    }, true);\n    option(\"firstLineNumber\", 1, updateGutters, true);\n    option(\"lineNumberFormatter\", function (integer) { return integer; }, updateGutters, true);\n    option(\"showCursorWhenSelecting\", false, updateSelection, true);\n\n    option(\"resetSelectionOnContextMenu\", true);\n    option(\"lineWiseCopyCut\", true);\n    option(\"pasteLinesPerSelection\", true);\n    option(\"selectionsMayTouch\", false);\n\n    option(\"readOnly\", false, function (cm, val) {\n      if (val == \"nocursor\") {\n        onBlur(cm);\n        cm.display.input.blur();\n      }\n      cm.display.input.readOnlyChanged(val);\n    });\n\n    option(\"screenReaderLabel\", null, function (cm, val) {\n      val = (val === '') ? null : val;\n      cm.display.input.screenReaderLabelChanged(val);\n    });\n\n    option(\"disableInput\", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true);\n    option(\"dragDrop\", true, dragDropChanged);\n    option(\"allowDropFileTypes\", null);\n\n    option(\"cursorBlinkRate\", 530);\n    option(\"cursorScrollMargin\", 0);\n    option(\"cursorHeight\", 1, updateSelection, true);\n    option(\"singleCursorHeightPerLine\", true, updateSelection, true);\n    option(\"workTime\", 100);\n    option(\"workDelay\", 100);\n    option(\"flattenSpans\", true, resetModeState, true);\n    option(\"addModeClass\", false, resetModeState, true);\n    option(\"pollInterval\", 100);\n    option(\"undoDepth\", 200, function (cm, val) { return cm.doc.history.undoDepth = val; });\n    option(\"historyEventDelay\", 1250);\n    option(\"viewportMargin\", 10, function (cm) { return cm.refresh(); }, true);\n    option(\"maxHighlightLength\", 10000, resetModeState, true);\n    option(\"moveInputWithCursor\", true, function (cm, val) {\n      if (!val) { cm.display.input.resetPosition(); }\n    });\n\n    option(\"tabindex\", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || \"\"; });\n    option(\"autofocus\", null);\n    option(\"direction\", \"ltr\", function (cm, val) { return cm.doc.setDirection(val); }, true);\n    option(\"phrases\", null);\n  }\n\n  function dragDropChanged(cm, value, old) {\n    var wasOn = old && old != Init;\n    if (!value != !wasOn) {\n      var funcs = cm.display.dragFunctions;\n      var toggle = value ? on : off;\n      toggle(cm.display.scroller, \"dragstart\", funcs.start);\n      toggle(cm.display.scroller, \"dragenter\", funcs.enter);\n      toggle(cm.display.scroller, \"dragover\", funcs.over);\n      toggle(cm.display.scroller, \"dragleave\", funcs.leave);\n      toggle(cm.display.scroller, \"drop\", funcs.drop);\n    }\n  }\n\n  function wrappingChanged(cm) {\n    if (cm.options.lineWrapping) {\n      addClass(cm.display.wrapper, \"CodeMirror-wrap\");\n      cm.display.sizer.style.minWidth = \"\";\n      cm.display.sizerWidth = null;\n    } else {\n      rmClass(cm.display.wrapper, \"CodeMirror-wrap\");\n      findMaxLine(cm);\n    }\n    estimateLineHeights(cm);\n    regChange(cm);\n    clearCaches(cm);\n    setTimeout(function () { return updateScrollbars(cm); }, 100);\n  }\n\n  // A CodeMirror instance represents an editor. This is the object\n  // that user code is usually dealing with.\n\n  function CodeMirror(place, options) {\n    var this$1 = this;\n\n    if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }\n\n    this.options = options = options ? copyObj(options) : {};\n    // Determine effective options based on given values and defaults.\n    copyObj(defaults, options, false);\n\n    var doc = options.value;\n    if (typeof doc == \"string\") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); }\n    else if (options.mode) { doc.modeOption = options.mode; }\n    this.doc = doc;\n\n    var input = new CodeMirror.inputStyles[options.inputStyle](this);\n    var display = this.display = new Display(place, doc, input, options);\n    display.wrapper.CodeMirror = this;\n    themeChanged(this);\n    if (options.lineWrapping)\n      { this.display.wrapper.className += \" CodeMirror-wrap\"; }\n    initScrollbars(this);\n\n    this.state = {\n      keyMaps: [],  // stores maps added by addKeyMap\n      overlays: [], // highlighting overlays, as added by addOverlay\n      modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info\n      overwrite: false,\n      delayingBlurEvent: false,\n      focused: false,\n      suppressEdits: false, // used to disable editing during key handlers when in readOnly mode\n      pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll\n      selectingText: false,\n      draggingText: false,\n      highlight: new Delayed(), // stores highlight worker timeout\n      keySeq: null,  // Unfinished key sequence\n      specialChars: null\n    };\n\n    if (options.autofocus && !mobile) { display.input.focus(); }\n\n    // Override magic textarea content restore that IE sometimes does\n    // on our hidden textarea on reload\n    if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); }\n\n    registerEventHandlers(this);\n    ensureGlobalHandlers();\n\n    startOperation(this);\n    this.curOp.forceUpdate = true;\n    attachDoc(this, doc);\n\n    if ((options.autofocus && !mobile) || this.hasFocus())\n      { setTimeout(bind(onFocus, this), 20); }\n    else\n      { onBlur(this); }\n\n    for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))\n      { optionHandlers[opt](this, options[opt], Init); } }\n    maybeUpdateLineNumberWidth(this);\n    if (options.finishInit) { options.finishInit(this); }\n    for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this); }\n    endOperation(this);\n    // Suppress optimizelegibility in Webkit, since it breaks text\n    // measuring on line wrapping boundaries.\n    if (webkit && options.lineWrapping &&\n        getComputedStyle(display.lineDiv).textRendering == \"optimizelegibility\")\n      { display.lineDiv.style.textRendering = \"auto\"; }\n  }\n\n  // The default configuration options.\n  CodeMirror.defaults = defaults;\n  // Functions to run when options are changed.\n  CodeMirror.optionHandlers = optionHandlers;\n\n  // Attach the necessary event handlers when initializing the editor\n  function registerEventHandlers(cm) {\n    var d = cm.display;\n    on(d.scroller, \"mousedown\", operation(cm, onMouseDown));\n    // Older IE's will not fire a second mousedown for a double click\n    if (ie && ie_version < 11)\n      { on(d.scroller, \"dblclick\", operation(cm, function (e) {\n        if (signalDOMEvent(cm, e)) { return }\n        var pos = posFromMouse(cm, e);\n        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return }\n        e_preventDefault(e);\n        var word = cm.findWordAt(pos);\n        extendSelection(cm.doc, word.anchor, word.head);\n      })); }\n    else\n      { on(d.scroller, \"dblclick\", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); }\n    // Some browsers fire contextmenu *after* opening the menu, at\n    // which point we can't mess with it anymore. Context menu is\n    // handled in onMouseDown for these browsers.\n    on(d.scroller, \"contextmenu\", function (e) { return onContextMenu(cm, e); });\n    on(d.input.getField(), \"contextmenu\", function (e) {\n      if (!d.scroller.contains(e.target)) { onContextMenu(cm, e); }\n    });\n\n    // Used to suppress mouse event handling when a touch happens\n    var touchFinished, prevTouch = {end: 0};\n    function finishTouch() {\n      if (d.activeTouch) {\n        touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000);\n        prevTouch = d.activeTouch;\n        prevTouch.end = +new Date;\n      }\n    }\n    function isMouseLikeTouchEvent(e) {\n      if (e.touches.length != 1) { return false }\n      var touch = e.touches[0];\n      return touch.radiusX <= 1 && touch.radiusY <= 1\n    }\n    function farAway(touch, other) {\n      if (other.left == null) { return true }\n      var dx = other.left - touch.left, dy = other.top - touch.top;\n      return dx * dx + dy * dy > 20 * 20\n    }\n    on(d.scroller, \"touchstart\", function (e) {\n      if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {\n        d.input.ensurePolled();\n        clearTimeout(touchFinished);\n        var now = +new Date;\n        d.activeTouch = {start: now, moved: false,\n                         prev: now - prevTouch.end <= 300 ? prevTouch : null};\n        if (e.touches.length == 1) {\n          d.activeTouch.left = e.touches[0].pageX;\n          d.activeTouch.top = e.touches[0].pageY;\n        }\n      }\n    });\n    on(d.scroller, \"touchmove\", function () {\n      if (d.activeTouch) { d.activeTouch.moved = true; }\n    });\n    on(d.scroller, \"touchend\", function (e) {\n      var touch = d.activeTouch;\n      if (touch && !eventInWidget(d, e) && touch.left != null &&\n          !touch.moved && new Date - touch.start < 300) {\n        var pos = cm.coordsChar(d.activeTouch, \"page\"), range;\n        if (!touch.prev || farAway(touch, touch.prev)) // Single tap\n          { range = new Range(pos, pos); }\n        else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap\n          { range = cm.findWordAt(pos); }\n        else // Triple tap\n          { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); }\n        cm.setSelection(range.anchor, range.head);\n        cm.focus();\n        e_preventDefault(e);\n      }\n      finishTouch();\n    });\n    on(d.scroller, \"touchcancel\", finishTouch);\n\n    // Sync scrolling between fake scrollbars and real scrollable\n    // area, ensure viewport is updated when scrolling.\n    on(d.scroller, \"scroll\", function () {\n      if (d.scroller.clientHeight) {\n        updateScrollTop(cm, d.scroller.scrollTop);\n        setScrollLeft(cm, d.scroller.scrollLeft, true);\n        signal(cm, \"scroll\", cm);\n      }\n    });\n\n    // Listen to wheel events in order to try and update the viewport on time.\n    on(d.scroller, \"mousewheel\", function (e) { return onScrollWheel(cm, e); });\n    on(d.scroller, \"DOMMouseScroll\", function (e) { return onScrollWheel(cm, e); });\n\n    // Prevent wrapper from ever scrolling\n    on(d.wrapper, \"scroll\", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });\n\n    d.dragFunctions = {\n      enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }},\n      over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},\n      start: function (e) { return onDragStart(cm, e); },\n      drop: operation(cm, onDrop),\n      leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}\n    };\n\n    var inp = d.input.getField();\n    on(inp, \"keyup\", function (e) { return onKeyUp.call(cm, e); });\n    on(inp, \"keydown\", operation(cm, onKeyDown));\n    on(inp, \"keypress\", operation(cm, onKeyPress));\n    on(inp, \"focus\", function (e) { return onFocus(cm, e); });\n    on(inp, \"blur\", function (e) { return onBlur(cm, e); });\n  }\n\n  var initHooks = [];\n  CodeMirror.defineInitHook = function (f) { return initHooks.push(f); };\n\n  // Indent the given line. The how parameter can be \"smart\",\n  // \"add\"/null, \"subtract\", or \"prev\". When aggressive is false\n  // (typically set to true for forced single-line indents), empty\n  // lines are not indented, and places where the mode returns Pass\n  // are left alone.\n  function indentLine(cm, n, how, aggressive) {\n    var doc = cm.doc, state;\n    if (how == null) { how = \"add\"; }\n    if (how == \"smart\") {\n      // Fall back to \"prev\" when the mode doesn't have an indentation\n      // method.\n      if (!doc.mode.indent) { how = \"prev\"; }\n      else { state = getContextBefore(cm, n).state; }\n    }\n\n    var tabSize = cm.options.tabSize;\n    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);\n    if (line.stateAfter) { line.stateAfter = null; }\n    var curSpaceString = line.text.match(/^\\s*/)[0], indentation;\n    if (!aggressive && !/\\S/.test(line.text)) {\n      indentation = 0;\n      how = \"not\";\n    } else if (how == \"smart\") {\n      indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);\n      if (indentation == Pass || indentation > 150) {\n        if (!aggressive) { return }\n        how = \"prev\";\n      }\n    }\n    if (how == \"prev\") {\n      if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); }\n      else { indentation = 0; }\n    } else if (how == \"add\") {\n      indentation = curSpace + cm.options.indentUnit;\n    } else if (how == \"subtract\") {\n      indentation = curSpace - cm.options.indentUnit;\n    } else if (typeof how == \"number\") {\n      indentation = curSpace + how;\n    }\n    indentation = Math.max(0, indentation);\n\n    var indentString = \"\", pos = 0;\n    if (cm.options.indentWithTabs)\n      { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += \"\\t\";} }\n    if (pos < indentation) { indentString += spaceStr(indentation - pos); }\n\n    if (indentString != curSpaceString) {\n      replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), \"+input\");\n      line.stateAfter = null;\n      return true\n    } else {\n      // Ensure that, if the cursor was in the whitespace at the start\n      // of the line, it is moved to the end of that space.\n      for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {\n        var range = doc.sel.ranges[i$1];\n        if (range.head.line == n && range.head.ch < curSpaceString.length) {\n          var pos$1 = Pos(n, curSpaceString.length);\n          replaceOneSelection(doc, i$1, new Range(pos$1, pos$1));\n          break\n        }\n      }\n    }\n  }\n\n  // This will be set to a {lineWise: bool, text: [string]} object, so\n  // that, when pasting, we know what kind of selections the copied\n  // text was made out of.\n  var lastCopied = null;\n\n  function setLastCopied(newLastCopied) {\n    lastCopied = newLastCopied;\n  }\n\n  function applyTextInput(cm, inserted, deleted, sel, origin) {\n    var doc = cm.doc;\n    cm.display.shift = false;\n    if (!sel) { sel = doc.sel; }\n\n    var recent = +new Date - 200;\n    var paste = origin == \"paste\" || cm.state.pasteIncoming > recent;\n    var textLines = splitLinesAuto(inserted), multiPaste = null;\n    // When pasting N lines into N selections, insert one line per selection\n    if (paste && sel.ranges.length > 1) {\n      if (lastCopied && lastCopied.text.join(\"\\n\") == inserted) {\n        if (sel.ranges.length % lastCopied.text.length == 0) {\n          multiPaste = [];\n          for (var i = 0; i < lastCopied.text.length; i++)\n            { multiPaste.push(doc.splitLines(lastCopied.text[i])); }\n        }\n      } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) {\n        multiPaste = map(textLines, function (l) { return [l]; });\n      }\n    }\n\n    var updateInput = cm.curOp.updateInput;\n    // Normal behavior is to insert the new text into every selection\n    for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {\n      var range = sel.ranges[i$1];\n      var from = range.from(), to = range.to();\n      if (range.empty()) {\n        if (deleted && deleted > 0) // Handle deletion\n          { from = Pos(from.line, from.ch - deleted); }\n        else if (cm.state.overwrite && !paste) // Handle overwrite\n          { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); }\n        else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join(\"\\n\") == textLines.join(\"\\n\"))\n          { from = to = Pos(from.line, 0); }\n      }\n      var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,\n                         origin: origin || (paste ? \"paste\" : cm.state.cutIncoming > recent ? \"cut\" : \"+input\")};\n      makeChange(cm.doc, changeEvent);\n      signalLater(cm, \"inputRead\", cm, changeEvent);\n    }\n    if (inserted && !paste)\n      { triggerElectric(cm, inserted); }\n\n    ensureCursorVisible(cm);\n    if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; }\n    cm.curOp.typing = true;\n    cm.state.pasteIncoming = cm.state.cutIncoming = -1;\n  }\n\n  function handlePaste(e, cm) {\n    var pasted = e.clipboardData && e.clipboardData.getData(\"Text\");\n    if (pasted) {\n      e.preventDefault();\n      if (!cm.isReadOnly() && !cm.options.disableInput)\n        { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, \"paste\"); }); }\n      return true\n    }\n  }\n\n  function triggerElectric(cm, inserted) {\n    // When an 'electric' character is inserted, immediately trigger a reindent\n    if (!cm.options.electricChars || !cm.options.smartIndent) { return }\n    var sel = cm.doc.sel;\n\n    for (var i = sel.ranges.length - 1; i >= 0; i--) {\n      var range = sel.ranges[i];\n      if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue }\n      var mode = cm.getModeAt(range.head);\n      var indented = false;\n      if (mode.electricChars) {\n        for (var j = 0; j < mode.electricChars.length; j++)\n          { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {\n            indented = indentLine(cm, range.head.line, \"smart\");\n            break\n          } }\n      } else if (mode.electricInput) {\n        if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))\n          { indented = indentLine(cm, range.head.line, \"smart\"); }\n      }\n      if (indented) { signalLater(cm, \"electricInput\", cm, range.head.line); }\n    }\n  }\n\n  function copyableRanges(cm) {\n    var text = [], ranges = [];\n    for (var i = 0; i < cm.doc.sel.ranges.length; i++) {\n      var line = cm.doc.sel.ranges[i].head.line;\n      var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};\n      ranges.push(lineRange);\n      text.push(cm.getRange(lineRange.anchor, lineRange.head));\n    }\n    return {text: text, ranges: ranges}\n  }\n\n  function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {\n    field.setAttribute(\"autocorrect\", autocorrect ? \"\" : \"off\");\n    field.setAttribute(\"autocapitalize\", autocapitalize ? \"\" : \"off\");\n    field.setAttribute(\"spellcheck\", !!spellcheck);\n  }\n\n  function hiddenTextarea() {\n    var te = elt(\"textarea\", null, null, \"position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none\");\n    var div = elt(\"div\", [te], null, \"overflow: hidden; position: relative; width: 3px; height: 0px;\");\n    // The textarea is kept positioned near the cursor to prevent the\n    // fact that it'll be scrolled into view on input from scrolling\n    // our fake cursor out of view. On webkit, when wrap=off, paste is\n    // very slow. So make the area wide instead.\n    if (webkit) { te.style.width = \"1000px\"; }\n    else { te.setAttribute(\"wrap\", \"off\"); }\n    // If border: 0; -- iOS fails to open keyboard (issue #1287)\n    if (ios) { te.style.border = \"1px solid black\"; }\n    disableBrowserMagic(te);\n    return div\n  }\n\n  // The publicly visible API. Note that methodOp(f) means\n  // 'wrap f in an operation, performed on its `this` parameter'.\n\n  // This is not the complete set of editor methods. Most of the\n  // methods defined on the Doc type are also injected into\n  // CodeMirror.prototype, for backwards compatibility and\n  // convenience.\n\n  function addEditorMethods(CodeMirror) {\n    var optionHandlers = CodeMirror.optionHandlers;\n\n    var helpers = CodeMirror.helpers = {};\n\n    CodeMirror.prototype = {\n      constructor: CodeMirror,\n      focus: function(){window.focus(); this.display.input.focus();},\n\n      setOption: function(option, value) {\n        var options = this.options, old = options[option];\n        if (options[option] == value && option != \"mode\") { return }\n        options[option] = value;\n        if (optionHandlers.hasOwnProperty(option))\n          { operation(this, optionHandlers[option])(this, value, old); }\n        signal(this, \"optionChange\", this, option);\n      },\n\n      getOption: function(option) {return this.options[option]},\n      getDoc: function() {return this.doc},\n\n      addKeyMap: function(map, bottom) {\n        this.state.keyMaps[bottom ? \"push\" : \"unshift\"](getKeyMap(map));\n      },\n      removeKeyMap: function(map) {\n        var maps = this.state.keyMaps;\n        for (var i = 0; i < maps.length; ++i)\n          { if (maps[i] == map || maps[i].name == map) {\n            maps.splice(i, 1);\n            return true\n          } }\n      },\n\n      addOverlay: methodOp(function(spec, options) {\n        var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);\n        if (mode.startState) { throw new Error(\"Overlays may not be stateful.\") }\n        insertSorted(this.state.overlays,\n                     {mode: mode, modeSpec: spec, opaque: options && options.opaque,\n                      priority: (options && options.priority) || 0},\n                     function (overlay) { return overlay.priority; });\n        this.state.modeGen++;\n        regChange(this);\n      }),\n      removeOverlay: methodOp(function(spec) {\n        var overlays = this.state.overlays;\n        for (var i = 0; i < overlays.length; ++i) {\n          var cur = overlays[i].modeSpec;\n          if (cur == spec || typeof spec == \"string\" && cur.name == spec) {\n            overlays.splice(i, 1);\n            this.state.modeGen++;\n            regChange(this);\n            return\n          }\n        }\n      }),\n\n      indentLine: methodOp(function(n, dir, aggressive) {\n        if (typeof dir != \"string\" && typeof dir != \"number\") {\n          if (dir == null) { dir = this.options.smartIndent ? \"smart\" : \"prev\"; }\n          else { dir = dir ? \"add\" : \"subtract\"; }\n        }\n        if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); }\n      }),\n      indentSelection: methodOp(function(how) {\n        var ranges = this.doc.sel.ranges, end = -1;\n        for (var i = 0; i < ranges.length; i++) {\n          var range = ranges[i];\n          if (!range.empty()) {\n            var from = range.from(), to = range.to();\n            var start = Math.max(end, from.line);\n            end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;\n            for (var j = start; j < end; ++j)\n              { indentLine(this, j, how); }\n            var newRanges = this.doc.sel.ranges;\n            if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)\n              { replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); }\n          } else if (range.head.line > end) {\n            indentLine(this, range.head.line, how, true);\n            end = range.head.line;\n            if (i == this.doc.sel.primIndex) { ensureCursorVisible(this); }\n          }\n        }\n      }),\n\n      // Fetch the parser token for a given character. Useful for hacks\n      // that want to inspect the mode state (say, for completion).\n      getTokenAt: function(pos, precise) {\n        return takeToken(this, pos, precise)\n      },\n\n      getLineTokens: function(line, precise) {\n        return takeToken(this, Pos(line), precise, true)\n      },\n\n      getTokenTypeAt: function(pos) {\n        pos = clipPos(this.doc, pos);\n        var styles = getLineStyles(this, getLine(this.doc, pos.line));\n        var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;\n        var type;\n        if (ch == 0) { type = styles[2]; }\n        else { for (;;) {\n          var mid = (before + after) >> 1;\n          if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; }\n          else if (styles[mid * 2 + 1] < ch) { before = mid + 1; }\n          else { type = styles[mid * 2 + 2]; break }\n        } }\n        var cut = type ? type.indexOf(\"overlay \") : -1;\n        return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)\n      },\n\n      getModeAt: function(pos) {\n        var mode = this.doc.mode;\n        if (!mode.innerMode) { return mode }\n        return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode\n      },\n\n      getHelper: function(pos, type) {\n        return this.getHelpers(pos, type)[0]\n      },\n\n      getHelpers: function(pos, type) {\n        var found = [];\n        if (!helpers.hasOwnProperty(type)) { return found }\n        var help = helpers[type], mode = this.getModeAt(pos);\n        if (typeof mode[type] == \"string\") {\n          if (help[mode[type]]) { found.push(help[mode[type]]); }\n        } else if (mode[type]) {\n          for (var i = 0; i < mode[type].length; i++) {\n            var val = help[mode[type][i]];\n            if (val) { found.push(val); }\n          }\n        } else if (mode.helperType && help[mode.helperType]) {\n          found.push(help[mode.helperType]);\n        } else if (help[mode.name]) {\n          found.push(help[mode.name]);\n        }\n        for (var i$1 = 0; i$1 < help._global.length; i$1++) {\n          var cur = help._global[i$1];\n          if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)\n            { found.push(cur.val); }\n        }\n        return found\n      },\n\n      getStateAfter: function(line, precise) {\n        var doc = this.doc;\n        line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);\n        return getContextBefore(this, line + 1, precise).state\n      },\n\n      cursorCoords: function(start, mode) {\n        var pos, range = this.doc.sel.primary();\n        if (start == null) { pos = range.head; }\n        else if (typeof start == \"object\") { pos = clipPos(this.doc, start); }\n        else { pos = start ? range.from() : range.to(); }\n        return cursorCoords(this, pos, mode || \"page\")\n      },\n\n      charCoords: function(pos, mode) {\n        return charCoords(this, clipPos(this.doc, pos), mode || \"page\")\n      },\n\n      coordsChar: function(coords, mode) {\n        coords = fromCoordSystem(this, coords, mode || \"page\");\n        return coordsChar(this, coords.left, coords.top)\n      },\n\n      lineAtHeight: function(height, mode) {\n        height = fromCoordSystem(this, {top: height, left: 0}, mode || \"page\").top;\n        return lineAtHeight(this.doc, height + this.display.viewOffset)\n      },\n      heightAtLine: function(line, mode, includeWidgets) {\n        var end = false, lineObj;\n        if (typeof line == \"number\") {\n          var last = this.doc.first + this.doc.size - 1;\n          if (line < this.doc.first) { line = this.doc.first; }\n          else if (line > last) { line = last; end = true; }\n          lineObj = getLine(this.doc, line);\n        } else {\n          lineObj = line;\n        }\n        return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || \"page\", includeWidgets || end).top +\n          (end ? this.doc.height - heightAtLine(lineObj) : 0)\n      },\n\n      defaultTextHeight: function() { return textHeight(this.display) },\n      defaultCharWidth: function() { return charWidth(this.display) },\n\n      getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},\n\n      addWidget: function(pos, node, scroll, vert, horiz) {\n        var display = this.display;\n        pos = cursorCoords(this, clipPos(this.doc, pos));\n        var top = pos.bottom, left = pos.left;\n        node.style.position = \"absolute\";\n        node.setAttribute(\"cm-ignore-events\", \"true\");\n        this.display.input.setUneditable(node);\n        display.sizer.appendChild(node);\n        if (vert == \"over\") {\n          top = pos.top;\n        } else if (vert == \"above\" || vert == \"near\") {\n          var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),\n          hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);\n          // Default to positioning above (if specified and possible); otherwise default to positioning below\n          if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)\n            { top = pos.top - node.offsetHeight; }\n          else if (pos.bottom + node.offsetHeight <= vspace)\n            { top = pos.bottom; }\n          if (left + node.offsetWidth > hspace)\n            { left = hspace - node.offsetWidth; }\n        }\n        node.style.top = top + \"px\";\n        node.style.left = node.style.right = \"\";\n        if (horiz == \"right\") {\n          left = display.sizer.clientWidth - node.offsetWidth;\n          node.style.right = \"0px\";\n        } else {\n          if (horiz == \"left\") { left = 0; }\n          else if (horiz == \"middle\") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; }\n          node.style.left = left + \"px\";\n        }\n        if (scroll)\n          { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); }\n      },\n\n      triggerOnKeyDown: methodOp(onKeyDown),\n      triggerOnKeyPress: methodOp(onKeyPress),\n      triggerOnKeyUp: onKeyUp,\n      triggerOnMouseDown: methodOp(onMouseDown),\n\n      execCommand: function(cmd) {\n        if (commands.hasOwnProperty(cmd))\n          { return commands[cmd].call(null, this) }\n      },\n\n      triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),\n\n      findPosH: function(from, amount, unit, visually) {\n        var dir = 1;\n        if (amount < 0) { dir = -1; amount = -amount; }\n        var cur = clipPos(this.doc, from);\n        for (var i = 0; i < amount; ++i) {\n          cur = findPosH(this.doc, cur, dir, unit, visually);\n          if (cur.hitSide) { break }\n        }\n        return cur\n      },\n\n      moveH: methodOp(function(dir, unit) {\n        var this$1 = this;\n\n        this.extendSelectionsBy(function (range) {\n          if (this$1.display.shift || this$1.doc.extend || range.empty())\n            { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) }\n          else\n            { return dir < 0 ? range.from() : range.to() }\n        }, sel_move);\n      }),\n\n      deleteH: methodOp(function(dir, unit) {\n        var sel = this.doc.sel, doc = this.doc;\n        if (sel.somethingSelected())\n          { doc.replaceSelection(\"\", null, \"+delete\"); }\n        else\n          { deleteNearSelection(this, function (range) {\n            var other = findPosH(doc, range.head, dir, unit, false);\n            return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}\n          }); }\n      }),\n\n      findPosV: function(from, amount, unit, goalColumn) {\n        var dir = 1, x = goalColumn;\n        if (amount < 0) { dir = -1; amount = -amount; }\n        var cur = clipPos(this.doc, from);\n        for (var i = 0; i < amount; ++i) {\n          var coords = cursorCoords(this, cur, \"div\");\n          if (x == null) { x = coords.left; }\n          else { coords.left = x; }\n          cur = findPosV(this, coords, dir, unit);\n          if (cur.hitSide) { break }\n        }\n        return cur\n      },\n\n      moveV: methodOp(function(dir, unit) {\n        var this$1 = this;\n\n        var doc = this.doc, goals = [];\n        var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected();\n        doc.extendSelectionsBy(function (range) {\n          if (collapse)\n            { return dir < 0 ? range.from() : range.to() }\n          var headPos = cursorCoords(this$1, range.head, \"div\");\n          if (range.goalColumn != null) { headPos.left = range.goalColumn; }\n          goals.push(headPos.left);\n          var pos = findPosV(this$1, headPos, dir, unit);\n          if (unit == \"page\" && range == doc.sel.primary())\n            { addToScrollTop(this$1, charCoords(this$1, pos, \"div\").top - headPos.top); }\n          return pos\n        }, sel_move);\n        if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++)\n          { doc.sel.ranges[i].goalColumn = goals[i]; } }\n      }),\n\n      // Find the word at the given position (as returned by coordsChar).\n      findWordAt: function(pos) {\n        var doc = this.doc, line = getLine(doc, pos.line).text;\n        var start = pos.ch, end = pos.ch;\n        if (line) {\n          var helper = this.getHelper(pos, \"wordChars\");\n          if ((pos.sticky == \"before\" || end == line.length) && start) { --start; } else { ++end; }\n          var startChar = line.charAt(start);\n          var check = isWordChar(startChar, helper)\n            ? function (ch) { return isWordChar(ch, helper); }\n            : /\\s/.test(startChar) ? function (ch) { return /\\s/.test(ch); }\n            : function (ch) { return (!/\\s/.test(ch) && !isWordChar(ch)); };\n          while (start > 0 && check(line.charAt(start - 1))) { --start; }\n          while (end < line.length && check(line.charAt(end))) { ++end; }\n        }\n        return new Range(Pos(pos.line, start), Pos(pos.line, end))\n      },\n\n      toggleOverwrite: function(value) {\n        if (value != null && value == this.state.overwrite) { return }\n        if (this.state.overwrite = !this.state.overwrite)\n          { addClass(this.display.cursorDiv, \"CodeMirror-overwrite\"); }\n        else\n          { rmClass(this.display.cursorDiv, \"CodeMirror-overwrite\"); }\n\n        signal(this, \"overwriteToggle\", this, this.state.overwrite);\n      },\n      hasFocus: function() { return this.display.input.getField() == activeElt() },\n      isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },\n\n      scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }),\n      getScrollInfo: function() {\n        var scroller = this.display.scroller;\n        return {left: scroller.scrollLeft, top: scroller.scrollTop,\n                height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,\n                width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,\n                clientHeight: displayHeight(this), clientWidth: displayWidth(this)}\n      },\n\n      scrollIntoView: methodOp(function(range, margin) {\n        if (range == null) {\n          range = {from: this.doc.sel.primary().head, to: null};\n          if (margin == null) { margin = this.options.cursorScrollMargin; }\n        } else if (typeof range == \"number\") {\n          range = {from: Pos(range, 0), to: null};\n        } else if (range.from == null) {\n          range = {from: range, to: null};\n        }\n        if (!range.to) { range.to = range.from; }\n        range.margin = margin || 0;\n\n        if (range.from.line != null) {\n          scrollToRange(this, range);\n        } else {\n          scrollToCoordsRange(this, range.from, range.to, range.margin);\n        }\n      }),\n\n      setSize: methodOp(function(width, height) {\n        var this$1 = this;\n\n        var interpret = function (val) { return typeof val == \"number\" || /^\\d+$/.test(String(val)) ? val + \"px\" : val; };\n        if (width != null) { this.display.wrapper.style.width = interpret(width); }\n        if (height != null) { this.display.wrapper.style.height = interpret(height); }\n        if (this.options.lineWrapping) { clearLineMeasurementCache(this); }\n        var lineNo = this.display.viewFrom;\n        this.doc.iter(lineNo, this.display.viewTo, function (line) {\n          if (line.widgets) { for (var i = 0; i < line.widgets.length; i++)\n            { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, \"widget\"); break } } }\n          ++lineNo;\n        });\n        this.curOp.forceUpdate = true;\n        signal(this, \"refresh\", this);\n      }),\n\n      operation: function(f){return runInOp(this, f)},\n      startOperation: function(){return startOperation(this)},\n      endOperation: function(){return endOperation(this)},\n\n      refresh: methodOp(function() {\n        var oldHeight = this.display.cachedTextHeight;\n        regChange(this);\n        this.curOp.forceUpdate = true;\n        clearCaches(this);\n        scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop);\n        updateGutterSpace(this.display);\n        if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping)\n          { estimateLineHeights(this); }\n        signal(this, \"refresh\", this);\n      }),\n\n      swapDoc: methodOp(function(doc) {\n        var old = this.doc;\n        old.cm = null;\n        // Cancel the current text selection if any (#5821)\n        if (this.state.selectingText) { this.state.selectingText(); }\n        attachDoc(this, doc);\n        clearCaches(this);\n        this.display.input.reset();\n        scrollToCoords(this, doc.scrollLeft, doc.scrollTop);\n        this.curOp.forceScroll = true;\n        signalLater(this, \"swapDoc\", this, old);\n        return old\n      }),\n\n      phrase: function(phraseText) {\n        var phrases = this.options.phrases;\n        return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText\n      },\n\n      getInputField: function(){return this.display.input.getField()},\n      getWrapperElement: function(){return this.display.wrapper},\n      getScrollerElement: function(){return this.display.scroller},\n      getGutterElement: function(){return this.display.gutters}\n    };\n    eventMixin(CodeMirror);\n\n    CodeMirror.registerHelper = function(type, name, value) {\n      if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; }\n      helpers[type][name] = value;\n    };\n    CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {\n      CodeMirror.registerHelper(type, name, value);\n      helpers[type]._global.push({pred: predicate, val: value});\n    };\n  }\n\n  // Used for horizontal relative motion. Dir is -1 or 1 (left or\n  // right), unit can be \"char\", \"column\" (like char, but doesn't\n  // cross line boundaries), \"word\" (across next word), or \"group\" (to\n  // the start of next group of word or non-word-non-whitespace\n  // chars). The visually param controls whether, in right-to-left\n  // text, direction 1 means to move towards the next index in the\n  // string, or towards the character to the right of the current\n  // position. The resulting position will have a hitSide=true\n  // property if it reached the end of the document.\n  function findPosH(doc, pos, dir, unit, visually) {\n    var oldPos = pos;\n    var origDir = dir;\n    var lineObj = getLine(doc, pos.line);\n    var lineDir = visually && doc.direction == \"rtl\" ? -dir : dir;\n    function findNextLine() {\n      var l = pos.line + lineDir;\n      if (l < doc.first || l >= doc.first + doc.size) { return false }\n      pos = new Pos(l, pos.ch, pos.sticky);\n      return lineObj = getLine(doc, l)\n    }\n    function moveOnce(boundToLine) {\n      var next;\n      if (visually) {\n        next = moveVisually(doc.cm, lineObj, pos, dir);\n      } else {\n        next = moveLogically(lineObj, pos, dir);\n      }\n      if (next == null) {\n        if (!boundToLine && findNextLine())\n          { pos = endOfLine(visually, doc.cm, lineObj, pos.line, lineDir); }\n        else\n          { return false }\n      } else {\n        pos = next;\n      }\n      return true\n    }\n\n    if (unit == \"char\") {\n      moveOnce();\n    } else if (unit == \"column\") {\n      moveOnce(true);\n    } else if (unit == \"word\" || unit == \"group\") {\n      var sawType = null, group = unit == \"group\";\n      var helper = doc.cm && doc.cm.getHelper(pos, \"wordChars\");\n      for (var first = true;; first = false) {\n        if (dir < 0 && !moveOnce(!first)) { break }\n        var cur = lineObj.text.charAt(pos.ch) || \"\\n\";\n        var type = isWordChar(cur, helper) ? \"w\"\n          : group && cur == \"\\n\" ? \"n\"\n          : !group || /\\s/.test(cur) ? null\n          : \"p\";\n        if (group && !first && !type) { type = \"s\"; }\n        if (sawType && sawType != type) {\n          if (dir < 0) {dir = 1; moveOnce(); pos.sticky = \"after\";}\n          break\n        }\n\n        if (type) { sawType = type; }\n        if (dir > 0 && !moveOnce(!first)) { break }\n      }\n    }\n    var result = skipAtomic(doc, pos, oldPos, origDir, true);\n    if (equalCursorPos(oldPos, result)) { result.hitSide = true; }\n    return result\n  }\n\n  // For relative vertical movement. Dir may be -1 or 1. Unit can be\n  // \"page\" or \"line\". The resulting position will have a hitSide=true\n  // property if it reached the end of the document.\n  function findPosV(cm, pos, dir, unit) {\n    var doc = cm.doc, x = pos.left, y;\n    if (unit == \"page\") {\n      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);\n      var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);\n      y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;\n\n    } else if (unit == \"line\") {\n      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;\n    }\n    var target;\n    for (;;) {\n      target = coordsChar(cm, x, y);\n      if (!target.outside) { break }\n      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break }\n      y += dir * 5;\n    }\n    return target\n  }\n\n  // CONTENTEDITABLE INPUT STYLE\n\n  var ContentEditableInput = function(cm) {\n    this.cm = cm;\n    this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;\n    this.polling = new Delayed();\n    this.composing = null;\n    this.gracePeriod = false;\n    this.readDOMTimeout = null;\n  };\n\n  ContentEditableInput.prototype.init = function (display) {\n      var this$1 = this;\n\n    var input = this, cm = input.cm;\n    var div = input.div = display.lineDiv;\n    disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);\n\n    function belongsToInput(e) {\n      for (var t = e.target; t; t = t.parentNode) {\n        if (t == div) { return true }\n        if (/\\bCodeMirror-(?:line)?widget\\b/.test(t.className)) { break }\n      }\n      return false\n    }\n\n    on(div, \"paste\", function (e) {\n      if (!belongsToInput(e) || signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }\n      // IE doesn't fire input events, so we schedule a read for the pasted content in this way\n      if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); }\n    });\n\n    on(div, \"compositionstart\", function (e) {\n      this$1.composing = {data: e.data, done: false};\n    });\n    on(div, \"compositionupdate\", function (e) {\n      if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; }\n    });\n    on(div, \"compositionend\", function (e) {\n      if (this$1.composing) {\n        if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); }\n        this$1.composing.done = true;\n      }\n    });\n\n    on(div, \"touchstart\", function () { return input.forceCompositionEnd(); });\n\n    on(div, \"input\", function () {\n      if (!this$1.composing) { this$1.readFromDOMSoon(); }\n    });\n\n    function onCopyCut(e) {\n      if (!belongsToInput(e) || signalDOMEvent(cm, e)) { return }\n      if (cm.somethingSelected()) {\n        setLastCopied({lineWise: false, text: cm.getSelections()});\n        if (e.type == \"cut\") { cm.replaceSelection(\"\", null, \"cut\"); }\n      } else if (!cm.options.lineWiseCopyCut) {\n        return\n      } else {\n        var ranges = copyableRanges(cm);\n        setLastCopied({lineWise: true, text: ranges.text});\n        if (e.type == \"cut\") {\n          cm.operation(function () {\n            cm.setSelections(ranges.ranges, 0, sel_dontScroll);\n            cm.replaceSelection(\"\", null, \"cut\");\n          });\n        }\n      }\n      if (e.clipboardData) {\n        e.clipboardData.clearData();\n        var content = lastCopied.text.join(\"\\n\");\n        // iOS exposes the clipboard API, but seems to discard content inserted into it\n        e.clipboardData.setData(\"Text\", content);\n        if (e.clipboardData.getData(\"Text\") == content) {\n          e.preventDefault();\n          return\n        }\n      }\n      // Old-fashioned briefly-focus-a-textarea hack\n      var kludge = hiddenTextarea(), te = kludge.firstChild;\n      cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);\n      te.value = lastCopied.text.join(\"\\n\");\n      var hadFocus = document.activeElement;\n      selectInput(te);\n      setTimeout(function () {\n        cm.display.lineSpace.removeChild(kludge);\n        hadFocus.focus();\n        if (hadFocus == div) { input.showPrimarySelection(); }\n      }, 50);\n    }\n    on(div, \"copy\", onCopyCut);\n    on(div, \"cut\", onCopyCut);\n  };\n\n  ContentEditableInput.prototype.screenReaderLabelChanged = function (label) {\n    // Label for screenreaders, accessibility\n    if(label) {\n      this.div.setAttribute('aria-label', label);\n    } else {\n      this.div.removeAttribute('aria-label');\n    }\n  };\n\n  ContentEditableInput.prototype.prepareSelection = function () {\n    var result = prepareSelection(this.cm, false);\n    result.focus = document.activeElement == this.div;\n    return result\n  };\n\n  ContentEditableInput.prototype.showSelection = function (info, takeFocus) {\n    if (!info || !this.cm.display.view.length) { return }\n    if (info.focus || takeFocus) { this.showPrimarySelection(); }\n    this.showMultipleSelections(info);\n  };\n\n  ContentEditableInput.prototype.getSelection = function () {\n    return this.cm.display.wrapper.ownerDocument.getSelection()\n  };\n\n  ContentEditableInput.prototype.showPrimarySelection = function () {\n    var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary();\n    var from = prim.from(), to = prim.to();\n\n    if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {\n      sel.removeAllRanges();\n      return\n    }\n\n    var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);\n    var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset);\n    if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&\n        cmp(minPos(curAnchor, curFocus), from) == 0 &&\n        cmp(maxPos(curAnchor, curFocus), to) == 0)\n      { return }\n\n    var view = cm.display.view;\n    var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||\n        {node: view[0].measure.map[2], offset: 0};\n    var end = to.line < cm.display.viewTo && posToDOM(cm, to);\n    if (!end) {\n      var measure = view[view.length - 1].measure;\n      var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;\n      end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]};\n    }\n\n    if (!start || !end) {\n      sel.removeAllRanges();\n      return\n    }\n\n    var old = sel.rangeCount && sel.getRangeAt(0), rng;\n    try { rng = range(start.node, start.offset, end.offset, end.node); }\n    catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible\n    if (rng) {\n      if (!gecko && cm.state.focused) {\n        sel.collapse(start.node, start.offset);\n        if (!rng.collapsed) {\n          sel.removeAllRanges();\n          sel.addRange(rng);\n        }\n      } else {\n        sel.removeAllRanges();\n        sel.addRange(rng);\n      }\n      if (old && sel.anchorNode == null) { sel.addRange(old); }\n      else if (gecko) { this.startGracePeriod(); }\n    }\n    this.rememberSelection();\n  };\n\n  ContentEditableInput.prototype.startGracePeriod = function () {\n      var this$1 = this;\n\n    clearTimeout(this.gracePeriod);\n    this.gracePeriod = setTimeout(function () {\n      this$1.gracePeriod = false;\n      if (this$1.selectionChanged())\n        { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); }\n    }, 20);\n  };\n\n  ContentEditableInput.prototype.showMultipleSelections = function (info) {\n    removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);\n    removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);\n  };\n\n  ContentEditableInput.prototype.rememberSelection = function () {\n    var sel = this.getSelection();\n    this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;\n    this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;\n  };\n\n  ContentEditableInput.prototype.selectionInEditor = function () {\n    var sel = this.getSelection();\n    if (!sel.rangeCount) { return false }\n    var node = sel.getRangeAt(0).commonAncestorContainer;\n    return contains(this.div, node)\n  };\n\n  ContentEditableInput.prototype.focus = function () {\n    if (this.cm.options.readOnly != \"nocursor\") {\n      if (!this.selectionInEditor() || document.activeElement != this.div)\n        { this.showSelection(this.prepareSelection(), true); }\n      this.div.focus();\n    }\n  };\n  ContentEditableInput.prototype.blur = function () { this.div.blur(); };\n  ContentEditableInput.prototype.getField = function () { return this.div };\n\n  ContentEditableInput.prototype.supportsTouch = function () { return true };\n\n  ContentEditableInput.prototype.receivedFocus = function () {\n    var input = this;\n    if (this.selectionInEditor())\n      { this.pollSelection(); }\n    else\n      { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }\n\n    function poll() {\n      if (input.cm.state.focused) {\n        input.pollSelection();\n        input.polling.set(input.cm.options.pollInterval, poll);\n      }\n    }\n    this.polling.set(this.cm.options.pollInterval, poll);\n  };\n\n  ContentEditableInput.prototype.selectionChanged = function () {\n    var sel = this.getSelection();\n    return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||\n      sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset\n  };\n\n  ContentEditableInput.prototype.pollSelection = function () {\n    if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }\n    var sel = this.getSelection(), cm = this.cm;\n    // On Android Chrome (version 56, at least), backspacing into an\n    // uneditable block element will put the cursor in that element,\n    // and then, because it's not editable, hide the virtual keyboard.\n    // Because Android doesn't allow us to actually detect backspace\n    // presses in a sane way, this code checks for when that happens\n    // and simulates a backspace press in this case.\n    if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) {\n      this.cm.triggerOnKeyDown({type: \"keydown\", keyCode: 8, preventDefault: Math.abs});\n      this.blur();\n      this.focus();\n      return\n    }\n    if (this.composing) { return }\n    this.rememberSelection();\n    var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);\n    var head = domToPos(cm, sel.focusNode, sel.focusOffset);\n    if (anchor && head) { runInOp(cm, function () {\n      setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);\n      if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; }\n    }); }\n  };\n\n  ContentEditableInput.prototype.pollContent = function () {\n    if (this.readDOMTimeout != null) {\n      clearTimeout(this.readDOMTimeout);\n      this.readDOMTimeout = null;\n    }\n\n    var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();\n    var from = sel.from(), to = sel.to();\n    if (from.ch == 0 && from.line > cm.firstLine())\n      { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); }\n    if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())\n      { to = Pos(to.line + 1, 0); }\n    if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }\n\n    var fromIndex, fromLine, fromNode;\n    if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {\n      fromLine = lineNo(display.view[0].line);\n      fromNode = display.view[0].node;\n    } else {\n      fromLine = lineNo(display.view[fromIndex].line);\n      fromNode = display.view[fromIndex - 1].node.nextSibling;\n    }\n    var toIndex = findViewIndex(cm, to.line);\n    var toLine, toNode;\n    if (toIndex == display.view.length - 1) {\n      toLine = display.viewTo - 1;\n      toNode = display.lineDiv.lastChild;\n    } else {\n      toLine = lineNo(display.view[toIndex + 1].line) - 1;\n      toNode = display.view[toIndex + 1].node.previousSibling;\n    }\n\n    if (!fromNode) { return false }\n    var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));\n    var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));\n    while (newText.length > 1 && oldText.length > 1) {\n      if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }\n      else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }\n      else { break }\n    }\n\n    var cutFront = 0, cutEnd = 0;\n    var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);\n    while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))\n      { ++cutFront; }\n    var newBot = lst(newText), oldBot = lst(oldText);\n    var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),\n                             oldBot.length - (oldText.length == 1 ? cutFront : 0));\n    while (cutEnd < maxCutEnd &&\n           newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))\n      { ++cutEnd; }\n    // Try to move start of change to start of selection if ambiguous\n    if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {\n      while (cutFront && cutFront > from.ch &&\n             newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {\n        cutFront--;\n        cutEnd++;\n      }\n    }\n\n    newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\\u200b+/, \"\");\n    newText[0] = newText[0].slice(cutFront).replace(/\\u200b+$/, \"\");\n\n    var chFrom = Pos(fromLine, cutFront);\n    var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);\n    if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {\n      replaceRange(cm.doc, newText, chFrom, chTo, \"+input\");\n      return true\n    }\n  };\n\n  ContentEditableInput.prototype.ensurePolled = function () {\n    this.forceCompositionEnd();\n  };\n  ContentEditableInput.prototype.reset = function () {\n    this.forceCompositionEnd();\n  };\n  ContentEditableInput.prototype.forceCompositionEnd = function () {\n    if (!this.composing) { return }\n    clearTimeout(this.readDOMTimeout);\n    this.composing = null;\n    this.updateFromDOM();\n    this.div.blur();\n    this.div.focus();\n  };\n  ContentEditableInput.prototype.readFromDOMSoon = function () {\n      var this$1 = this;\n\n    if (this.readDOMTimeout != null) { return }\n    this.readDOMTimeout = setTimeout(function () {\n      this$1.readDOMTimeout = null;\n      if (this$1.composing) {\n        if (this$1.composing.done) { this$1.composing = null; }\n        else { return }\n      }\n      this$1.updateFromDOM();\n    }, 80);\n  };\n\n  ContentEditableInput.prototype.updateFromDOM = function () {\n      var this$1 = this;\n\n    if (this.cm.isReadOnly() || !this.pollContent())\n      { runInOp(this.cm, function () { return regChange(this$1.cm); }); }\n  };\n\n  ContentEditableInput.prototype.setUneditable = function (node) {\n    node.contentEditable = \"false\";\n  };\n\n  ContentEditableInput.prototype.onKeyPress = function (e) {\n    if (e.charCode == 0 || this.composing) { return }\n    e.preventDefault();\n    if (!this.cm.isReadOnly())\n      { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); }\n  };\n\n  ContentEditableInput.prototype.readOnlyChanged = function (val) {\n    this.div.contentEditable = String(val != \"nocursor\");\n  };\n\n  ContentEditableInput.prototype.onContextMenu = function () {};\n  ContentEditableInput.prototype.resetPosition = function () {};\n\n  ContentEditableInput.prototype.needsContentAttribute = true;\n\n  function posToDOM(cm, pos) {\n    var view = findViewForLine(cm, pos.line);\n    if (!view || view.hidden) { return null }\n    var line = getLine(cm.doc, pos.line);\n    var info = mapFromLineView(view, line, pos.line);\n\n    var order = getOrder(line, cm.doc.direction), side = \"left\";\n    if (order) {\n      var partPos = getBidiPartAt(order, pos.ch);\n      side = partPos % 2 ? \"right\" : \"left\";\n    }\n    var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);\n    result.offset = result.collapse == \"right\" ? result.end : result.start;\n    return result\n  }\n\n  function isInGutter(node) {\n    for (var scan = node; scan; scan = scan.parentNode)\n      { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }\n    return false\n  }\n\n  function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }\n\n  function domTextBetween(cm, from, to, fromLine, toLine) {\n    var text = \"\", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false;\n    function recognizeMarker(id) { return function (marker) { return marker.id == id; } }\n    function close() {\n      if (closing) {\n        text += lineSep;\n        if (extraLinebreak) { text += lineSep; }\n        closing = extraLinebreak = false;\n      }\n    }\n    function addText(str) {\n      if (str) {\n        close();\n        text += str;\n      }\n    }\n    function walk(node) {\n      if (node.nodeType == 1) {\n        var cmText = node.getAttribute(\"cm-text\");\n        if (cmText) {\n          addText(cmText);\n          return\n        }\n        var markerID = node.getAttribute(\"cm-marker\"), range;\n        if (markerID) {\n          var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));\n          if (found.length && (range = found[0].find(0)))\n            { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); }\n          return\n        }\n        if (node.getAttribute(\"contenteditable\") == \"false\") { return }\n        var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName);\n        if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return }\n\n        if (isBlock) { close(); }\n        for (var i = 0; i < node.childNodes.length; i++)\n          { walk(node.childNodes[i]); }\n\n        if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; }\n        if (isBlock) { closing = true; }\n      } else if (node.nodeType == 3) {\n        addText(node.nodeValue.replace(/\\u200b/g, \"\").replace(/\\u00a0/g, \" \"));\n      }\n    }\n    for (;;) {\n      walk(from);\n      if (from == to) { break }\n      from = from.nextSibling;\n      extraLinebreak = false;\n    }\n    return text\n  }\n\n  function domToPos(cm, node, offset) {\n    var lineNode;\n    if (node == cm.display.lineDiv) {\n      lineNode = cm.display.lineDiv.childNodes[offset];\n      if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) }\n      node = null; offset = 0;\n    } else {\n      for (lineNode = node;; lineNode = lineNode.parentNode) {\n        if (!lineNode || lineNode == cm.display.lineDiv) { return null }\n        if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break }\n      }\n    }\n    for (var i = 0; i < cm.display.view.length; i++) {\n      var lineView = cm.display.view[i];\n      if (lineView.node == lineNode)\n        { return locateNodeInLineView(lineView, node, offset) }\n    }\n  }\n\n  function locateNodeInLineView(lineView, node, offset) {\n    var wrapper = lineView.text.firstChild, bad = false;\n    if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) }\n    if (node == wrapper) {\n      bad = true;\n      node = wrapper.childNodes[offset];\n      offset = 0;\n      if (!node) {\n        var line = lineView.rest ? lst(lineView.rest) : lineView.line;\n        return badPos(Pos(lineNo(line), line.text.length), bad)\n      }\n    }\n\n    var textNode = node.nodeType == 3 ? node : null, topNode = node;\n    if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {\n      textNode = node.firstChild;\n      if (offset) { offset = textNode.nodeValue.length; }\n    }\n    while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; }\n    var measure = lineView.measure, maps = measure.maps;\n\n    function find(textNode, topNode, offset) {\n      for (var i = -1; i < (maps ? maps.length : 0); i++) {\n        var map = i < 0 ? measure.map : maps[i];\n        for (var j = 0; j < map.length; j += 3) {\n          var curNode = map[j + 2];\n          if (curNode == textNode || curNode == topNode) {\n            var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);\n            var ch = map[j] + offset;\n            if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)]; }\n            return Pos(line, ch)\n          }\n        }\n      }\n    }\n    var found = find(textNode, topNode, offset);\n    if (found) { return badPos(found, bad) }\n\n    // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems\n    for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {\n      found = find(after, after.firstChild, 0);\n      if (found)\n        { return badPos(Pos(found.line, found.ch - dist), bad) }\n      else\n        { dist += after.textContent.length; }\n    }\n    for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) {\n      found = find(before, before.firstChild, -1);\n      if (found)\n        { return badPos(Pos(found.line, found.ch + dist$1), bad) }\n      else\n        { dist$1 += before.textContent.length; }\n    }\n  }\n\n  // TEXTAREA INPUT STYLE\n\n  var TextareaInput = function(cm) {\n    this.cm = cm;\n    // See input.poll and input.reset\n    this.prevInput = \"\";\n\n    // Flag that indicates whether we expect input to appear real soon\n    // now (after some event like 'keypress' or 'input') and are\n    // polling intensively.\n    this.pollingFast = false;\n    // Self-resetting timeout for the poller\n    this.polling = new Delayed();\n    // Used to work around IE issue with selection being forgotten when focus moves away from textarea\n    this.hasSelection = false;\n    this.composing = null;\n  };\n\n  TextareaInput.prototype.init = function (display) {\n      var this$1 = this;\n\n    var input = this, cm = this.cm;\n    this.createField(display);\n    var te = this.textarea;\n\n    display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild);\n\n    // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)\n    if (ios) { te.style.width = \"0px\"; }\n\n    on(te, \"input\", function () {\n      if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; }\n      input.poll();\n    });\n\n    on(te, \"paste\", function (e) {\n      if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }\n\n      cm.state.pasteIncoming = +new Date;\n      input.fastPoll();\n    });\n\n    function prepareCopyCut(e) {\n      if (signalDOMEvent(cm, e)) { return }\n      if (cm.somethingSelected()) {\n        setLastCopied({lineWise: false, text: cm.getSelections()});\n      } else if (!cm.options.lineWiseCopyCut) {\n        return\n      } else {\n        var ranges = copyableRanges(cm);\n        setLastCopied({lineWise: true, text: ranges.text});\n        if (e.type == \"cut\") {\n          cm.setSelections(ranges.ranges, null, sel_dontScroll);\n        } else {\n          input.prevInput = \"\";\n          te.value = ranges.text.join(\"\\n\");\n          selectInput(te);\n        }\n      }\n      if (e.type == \"cut\") { cm.state.cutIncoming = +new Date; }\n    }\n    on(te, \"cut\", prepareCopyCut);\n    on(te, \"copy\", prepareCopyCut);\n\n    on(display.scroller, \"paste\", function (e) {\n      if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }\n      if (!te.dispatchEvent) {\n        cm.state.pasteIncoming = +new Date;\n        input.focus();\n        return\n      }\n\n      // Pass the `paste` event to the textarea so it's handled by its event listener.\n      var event = new Event(\"paste\");\n      event.clipboardData = e.clipboardData;\n      te.dispatchEvent(event);\n    });\n\n    // Prevent normal selection in the editor (we handle our own)\n    on(display.lineSpace, \"selectstart\", function (e) {\n      if (!eventInWidget(display, e)) { e_preventDefault(e); }\n    });\n\n    on(te, \"compositionstart\", function () {\n      var start = cm.getCursor(\"from\");\n      if (input.composing) { input.composing.range.clear(); }\n      input.composing = {\n        start: start,\n        range: cm.markText(start, cm.getCursor(\"to\"), {className: \"CodeMirror-composing\"})\n      };\n    });\n    on(te, \"compositionend\", function () {\n      if (input.composing) {\n        input.poll();\n        input.composing.range.clear();\n        input.composing = null;\n      }\n    });\n  };\n\n  TextareaInput.prototype.createField = function (_display) {\n    // Wraps and hides input textarea\n    this.wrapper = hiddenTextarea();\n    // The semihidden textarea that is focused when the editor is\n    // focused, and receives input.\n    this.textarea = this.wrapper.firstChild;\n  };\n\n  TextareaInput.prototype.screenReaderLabelChanged = function (label) {\n    // Label for screenreaders, accessibility\n    if(label) {\n      this.textarea.setAttribute('aria-label', label);\n    } else {\n      this.textarea.removeAttribute('aria-label');\n    }\n  };\n\n  TextareaInput.prototype.prepareSelection = function () {\n    // Redraw the selection and/or cursor\n    var cm = this.cm, display = cm.display, doc = cm.doc;\n    var result = prepareSelection(cm);\n\n    // Move the hidden textarea near the cursor to prevent scrolling artifacts\n    if (cm.options.moveInputWithCursor) {\n      var headPos = cursorCoords(cm, doc.sel.primary().head, \"div\");\n      var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();\n      result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,\n                                          headPos.top + lineOff.top - wrapOff.top));\n      result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,\n                                           headPos.left + lineOff.left - wrapOff.left));\n    }\n\n    return result\n  };\n\n  TextareaInput.prototype.showSelection = function (drawn) {\n    var cm = this.cm, display = cm.display;\n    removeChildrenAndAdd(display.cursorDiv, drawn.cursors);\n    removeChildrenAndAdd(display.selectionDiv, drawn.selection);\n    if (drawn.teTop != null) {\n      this.wrapper.style.top = drawn.teTop + \"px\";\n      this.wrapper.style.left = drawn.teLeft + \"px\";\n    }\n  };\n\n  // Reset the input to correspond to the selection (or to be empty,\n  // when not typing and nothing is selected)\n  TextareaInput.prototype.reset = function (typing) {\n    if (this.contextMenuPending || this.composing) { return }\n    var cm = this.cm;\n    if (cm.somethingSelected()) {\n      this.prevInput = \"\";\n      var content = cm.getSelection();\n      this.textarea.value = content;\n      if (cm.state.focused) { selectInput(this.textarea); }\n      if (ie && ie_version >= 9) { this.hasSelection = content; }\n    } else if (!typing) {\n      this.prevInput = this.textarea.value = \"\";\n      if (ie && ie_version >= 9) { this.hasSelection = null; }\n    }\n  };\n\n  TextareaInput.prototype.getField = function () { return this.textarea };\n\n  TextareaInput.prototype.supportsTouch = function () { return false };\n\n  TextareaInput.prototype.focus = function () {\n    if (this.cm.options.readOnly != \"nocursor\" && (!mobile || activeElt() != this.textarea)) {\n      try { this.textarea.focus(); }\n      catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM\n    }\n  };\n\n  TextareaInput.prototype.blur = function () { this.textarea.blur(); };\n\n  TextareaInput.prototype.resetPosition = function () {\n    this.wrapper.style.top = this.wrapper.style.left = 0;\n  };\n\n  TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); };\n\n  // Poll for input changes, using the normal rate of polling. This\n  // runs as long as the editor is focused.\n  TextareaInput.prototype.slowPoll = function () {\n      var this$1 = this;\n\n    if (this.pollingFast) { return }\n    this.polling.set(this.cm.options.pollInterval, function () {\n      this$1.poll();\n      if (this$1.cm.state.focused) { this$1.slowPoll(); }\n    });\n  };\n\n  // When an event has just come in that is likely to add or change\n  // something in the input textarea, we poll faster, to ensure that\n  // the change appears on the screen quickly.\n  TextareaInput.prototype.fastPoll = function () {\n    var missed = false, input = this;\n    input.pollingFast = true;\n    function p() {\n      var changed = input.poll();\n      if (!changed && !missed) {missed = true; input.polling.set(60, p);}\n      else {input.pollingFast = false; input.slowPoll();}\n    }\n    input.polling.set(20, p);\n  };\n\n  // Read input from the textarea, and update the document to match.\n  // When something is selected, it is present in the textarea, and\n  // selected (unless it is huge, in which case a placeholder is\n  // used). When nothing is selected, the cursor sits after previously\n  // seen text (can be empty), which is stored in prevInput (we must\n  // not reset the textarea when typing, because that breaks IME).\n  TextareaInput.prototype.poll = function () {\n      var this$1 = this;\n\n    var cm = this.cm, input = this.textarea, prevInput = this.prevInput;\n    // Since this is called a *lot*, try to bail out as cheaply as\n    // possible when it is clear that nothing happened. hasSelection\n    // will be the case when there is a lot of text in the textarea,\n    // in which case reading its value would be expensive.\n    if (this.contextMenuPending || !cm.state.focused ||\n        (hasSelection(input) && !prevInput && !this.composing) ||\n        cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)\n      { return false }\n\n    var text = input.value;\n    // If nothing changed, bail.\n    if (text == prevInput && !cm.somethingSelected()) { return false }\n    // Work around nonsensical selection resetting in IE9/10, and\n    // inexplicable appearance of private area unicode characters on\n    // some key combos in Mac (#2689).\n    if (ie && ie_version >= 9 && this.hasSelection === text ||\n        mac && /[\\uf700-\\uf7ff]/.test(text)) {\n      cm.display.input.reset();\n      return false\n    }\n\n    if (cm.doc.sel == cm.display.selForContextMenu) {\n      var first = text.charCodeAt(0);\n      if (first == 0x200b && !prevInput) { prevInput = \"\\u200b\"; }\n      if (first == 0x21da) { this.reset(); return this.cm.execCommand(\"undo\") }\n    }\n    // Find the part of the input that is actually new\n    var same = 0, l = Math.min(prevInput.length, text.length);\n    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; }\n\n    runInOp(cm, function () {\n      applyTextInput(cm, text.slice(same), prevInput.length - same,\n                     null, this$1.composing ? \"*compose\" : null);\n\n      // Don't leave long text in the textarea, since it makes further polling slow\n      if (text.length > 1000 || text.indexOf(\"\\n\") > -1) { input.value = this$1.prevInput = \"\"; }\n      else { this$1.prevInput = text; }\n\n      if (this$1.composing) {\n        this$1.composing.range.clear();\n        this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor(\"to\"),\n                                           {className: \"CodeMirror-composing\"});\n      }\n    });\n    return true\n  };\n\n  TextareaInput.prototype.ensurePolled = function () {\n    if (this.pollingFast && this.poll()) { this.pollingFast = false; }\n  };\n\n  TextareaInput.prototype.onKeyPress = function () {\n    if (ie && ie_version >= 9) { this.hasSelection = null; }\n    this.fastPoll();\n  };\n\n  TextareaInput.prototype.onContextMenu = function (e) {\n    var input = this, cm = input.cm, display = cm.display, te = input.textarea;\n    if (input.contextMenuPending) { input.contextMenuPending(); }\n    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;\n    if (!pos || presto) { return } // Opera is difficult.\n\n    // Reset the current text selection only if the click is done outside of the selection\n    // and 'resetSelectionOnContextMenu' option is true.\n    var reset = cm.options.resetSelectionOnContextMenu;\n    if (reset && cm.doc.sel.contains(pos) == -1)\n      { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); }\n\n    var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;\n    var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect();\n    input.wrapper.style.cssText = \"position: static\";\n    te.style.cssText = \"position: absolute; width: 30px; height: 30px;\\n      top: \" + (e.clientY - wrapperBox.top - 5) + \"px; left: \" + (e.clientX - wrapperBox.left - 5) + \"px;\\n      z-index: 1000; background: \" + (ie ? \"rgba(255, 255, 255, .05)\" : \"transparent\") + \";\\n      outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);\";\n    var oldScrollY;\n    if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712)\n    display.input.focus();\n    if (webkit) { window.scrollTo(null, oldScrollY); }\n    display.input.reset();\n    // Adds \"Select all\" to context menu in FF\n    if (!cm.somethingSelected()) { te.value = input.prevInput = \" \"; }\n    input.contextMenuPending = rehide;\n    display.selForContextMenu = cm.doc.sel;\n    clearTimeout(display.detectingSelectAll);\n\n    // Select-all will be greyed out if there's nothing to select, so\n    // this adds a zero-width space so that we can later check whether\n    // it got selected.\n    function prepareSelectAllHack() {\n      if (te.selectionStart != null) {\n        var selected = cm.somethingSelected();\n        var extval = \"\\u200b\" + (selected ? te.value : \"\");\n        te.value = \"\\u21da\"; // Used to catch context-menu undo\n        te.value = extval;\n        input.prevInput = selected ? \"\" : \"\\u200b\";\n        te.selectionStart = 1; te.selectionEnd = extval.length;\n        // Re-set this, in case some other handler touched the\n        // selection in the meantime.\n        display.selForContextMenu = cm.doc.sel;\n      }\n    }\n    function rehide() {\n      if (input.contextMenuPending != rehide) { return }\n      input.contextMenuPending = false;\n      input.wrapper.style.cssText = oldWrapperCSS;\n      te.style.cssText = oldCSS;\n      if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); }\n\n      // Try to detect the user choosing select-all\n      if (te.selectionStart != null) {\n        if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); }\n        var i = 0, poll = function () {\n          if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&\n              te.selectionEnd > 0 && input.prevInput == \"\\u200b\") {\n            operation(cm, selectAll)(cm);\n          } else if (i++ < 10) {\n            display.detectingSelectAll = setTimeout(poll, 500);\n          } else {\n            display.selForContextMenu = null;\n            display.input.reset();\n          }\n        };\n        display.detectingSelectAll = setTimeout(poll, 200);\n      }\n    }\n\n    if (ie && ie_version >= 9) { prepareSelectAllHack(); }\n    if (captureRightClick) {\n      e_stop(e);\n      var mouseup = function () {\n        off(window, \"mouseup\", mouseup);\n        setTimeout(rehide, 20);\n      };\n      on(window, \"mouseup\", mouseup);\n    } else {\n      setTimeout(rehide, 50);\n    }\n  };\n\n  TextareaInput.prototype.readOnlyChanged = function (val) {\n    if (!val) { this.reset(); }\n    this.textarea.disabled = val == \"nocursor\";\n  };\n\n  TextareaInput.prototype.setUneditable = function () {};\n\n  TextareaInput.prototype.needsContentAttribute = false;\n\n  function fromTextArea(textarea, options) {\n    options = options ? copyObj(options) : {};\n    options.value = textarea.value;\n    if (!options.tabindex && textarea.tabIndex)\n      { options.tabindex = textarea.tabIndex; }\n    if (!options.placeholder && textarea.placeholder)\n      { options.placeholder = textarea.placeholder; }\n    // Set autofocus to true if this textarea is focused, or if it has\n    // autofocus and no other element is focused.\n    if (options.autofocus == null) {\n      var hasFocus = activeElt();\n      options.autofocus = hasFocus == textarea ||\n        textarea.getAttribute(\"autofocus\") != null && hasFocus == document.body;\n    }\n\n    function save() {textarea.value = cm.getValue();}\n\n    var realSubmit;\n    if (textarea.form) {\n      on(textarea.form, \"submit\", save);\n      // Deplorable hack to make the submit method do the right thing.\n      if (!options.leaveSubmitMethodAlone) {\n        var form = textarea.form;\n        realSubmit = form.submit;\n        try {\n          var wrappedSubmit = form.submit = function () {\n            save();\n            form.submit = realSubmit;\n            form.submit();\n            form.submit = wrappedSubmit;\n          };\n        } catch(e) {}\n      }\n    }\n\n    options.finishInit = function (cm) {\n      cm.save = save;\n      cm.getTextArea = function () { return textarea; };\n      cm.toTextArea = function () {\n        cm.toTextArea = isNaN; // Prevent this from being ran twice\n        save();\n        textarea.parentNode.removeChild(cm.getWrapperElement());\n        textarea.style.display = \"\";\n        if (textarea.form) {\n          off(textarea.form, \"submit\", save);\n          if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == \"function\")\n            { textarea.form.submit = realSubmit; }\n        }\n      };\n    };\n\n    textarea.style.display = \"none\";\n    var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },\n      options);\n    return cm\n  }\n\n  function addLegacyProps(CodeMirror) {\n    CodeMirror.off = off;\n    CodeMirror.on = on;\n    CodeMirror.wheelEventPixels = wheelEventPixels;\n    CodeMirror.Doc = Doc;\n    CodeMirror.splitLines = splitLinesAuto;\n    CodeMirror.countColumn = countColumn;\n    CodeMirror.findColumn = findColumn;\n    CodeMirror.isWordChar = isWordCharBasic;\n    CodeMirror.Pass = Pass;\n    CodeMirror.signal = signal;\n    CodeMirror.Line = Line;\n    CodeMirror.changeEnd = changeEnd;\n    CodeMirror.scrollbarModel = scrollbarModel;\n    CodeMirror.Pos = Pos;\n    CodeMirror.cmpPos = cmp;\n    CodeMirror.modes = modes;\n    CodeMirror.mimeModes = mimeModes;\n    CodeMirror.resolveMode = resolveMode;\n    CodeMirror.getMode = getMode;\n    CodeMirror.modeExtensions = modeExtensions;\n    CodeMirror.extendMode = extendMode;\n    CodeMirror.copyState = copyState;\n    CodeMirror.startState = startState;\n    CodeMirror.innerMode = innerMode;\n    CodeMirror.commands = commands;\n    CodeMirror.keyMap = keyMap;\n    CodeMirror.keyName = keyName;\n    CodeMirror.isModifierKey = isModifierKey;\n    CodeMirror.lookupKey = lookupKey;\n    CodeMirror.normalizeKeyMap = normalizeKeyMap;\n    CodeMirror.StringStream = StringStream;\n    CodeMirror.SharedTextMarker = SharedTextMarker;\n    CodeMirror.TextMarker = TextMarker;\n    CodeMirror.LineWidget = LineWidget;\n    CodeMirror.e_preventDefault = e_preventDefault;\n    CodeMirror.e_stopPropagation = e_stopPropagation;\n    CodeMirror.e_stop = e_stop;\n    CodeMirror.addClass = addClass;\n    CodeMirror.contains = contains;\n    CodeMirror.rmClass = rmClass;\n    CodeMirror.keyNames = keyNames;\n  }\n\n  // EDITOR CONSTRUCTOR\n\n  defineOptions(CodeMirror);\n\n  addEditorMethods(CodeMirror);\n\n  // Set up methods on CodeMirror's prototype to redirect to the editor's document.\n  var dontDelegate = \"iter insert remove copy getEditor constructor\".split(\" \");\n  for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)\n    { CodeMirror.prototype[prop] = (function(method) {\n      return function() {return method.apply(this.doc, arguments)}\n    })(Doc.prototype[prop]); } }\n\n  eventMixin(Doc);\n  CodeMirror.inputStyles = {\"textarea\": TextareaInput, \"contenteditable\": ContentEditableInput};\n\n  // Extra arguments are stored as the mode's dependencies, which is\n  // used by (legacy) mechanisms like loadmode.js to automatically\n  // load a mode. (Preferred mechanism is the require/define calls.)\n  CodeMirror.defineMode = function(name/*, mode, …*/) {\n    if (!CodeMirror.defaults.mode && name != \"null\") { CodeMirror.defaults.mode = name; }\n    defineMode.apply(this, arguments);\n  };\n\n  CodeMirror.defineMIME = defineMIME;\n\n  // Minimal default mode.\n  CodeMirror.defineMode(\"null\", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });\n  CodeMirror.defineMIME(\"text/plain\", \"null\");\n\n  // EXTENSIONS\n\n  CodeMirror.defineExtension = function (name, func) {\n    CodeMirror.prototype[name] = func;\n  };\n  CodeMirror.defineDocExtension = function (name, func) {\n    Doc.prototype[name] = func;\n  };\n\n  CodeMirror.fromTextArea = fromTextArea;\n\n  addLegacyProps(CodeMirror);\n\n  CodeMirror.version = \"5.56.0\";\n\n  return CodeMirror;\n\n})));\n\n\n/* ---- extension/simple.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineSimpleMode = function(name, states) {\n    CodeMirror.defineMode(name, function(config) {\n      return CodeMirror.simpleMode(config, states);\n    });\n  };\n\n  CodeMirror.simpleMode = function(config, states) {\n    ensureState(states, \"start\");\n    var states_ = {}, meta = states.meta || {}, hasIndentation = false;\n    for (var state in states) if (state != meta && states.hasOwnProperty(state)) {\n      var list = states_[state] = [], orig = states[state];\n      for (var i = 0; i < orig.length; i++) {\n        var data = orig[i];\n        list.push(new Rule(data, states));\n        if (data.indent || data.dedent) hasIndentation = true;\n      }\n    }\n    var mode = {\n      startState: function() {\n        return {state: \"start\", pending: null,\n                local: null, localState: null,\n                indent: hasIndentation ? [] : null};\n      },\n      copyState: function(state) {\n        var s = {state: state.state, pending: state.pending,\n                 local: state.local, localState: null,\n                 indent: state.indent && state.indent.slice(0)};\n        if (state.localState)\n          s.localState = CodeMirror.copyState(state.local.mode, state.localState);\n        if (state.stack)\n          s.stack = state.stack.slice(0);\n        for (var pers = state.persistentStates; pers; pers = pers.next)\n          s.persistentStates = {mode: pers.mode,\n                                spec: pers.spec,\n                                state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),\n                                next: s.persistentStates};\n        return s;\n      },\n      token: tokenFunction(states_, config),\n      innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },\n      indent: indentFunction(states_, meta)\n    };\n    if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))\n      mode[prop] = meta[prop];\n    return mode;\n  };\n\n  function ensureState(states, name) {\n    if (!states.hasOwnProperty(name))\n      throw new Error(\"Undefined state \" + name + \" in simple mode\");\n  }\n\n  function toRegex(val, caret) {\n    if (!val) return /(?:)/;\n    var flags = \"\";\n    if (val instanceof RegExp) {\n      if (val.ignoreCase) flags = \"i\";\n      val = val.source;\n    } else {\n      val = String(val);\n    }\n    return new RegExp((caret === false ? \"\" : \"^\") + \"(?:\" + val + \")\", flags);\n  }\n\n  function asToken(val) {\n    if (!val) return null;\n    if (val.apply) return val\n    if (typeof val == \"string\") return val.replace(/\\./g, \" \");\n    var result = [];\n    for (var i = 0; i < val.length; i++)\n      result.push(val[i] && val[i].replace(/\\./g, \" \"));\n    return result;\n  }\n\n  function Rule(data, states) {\n    if (data.next || data.push) ensureState(states, data.next || data.push);\n    this.regex = toRegex(data.regex);\n    this.token = asToken(data.token);\n    this.data = data;\n  }\n\n  function tokenFunction(states, config) {\n    return function(stream, state) {\n      if (state.pending) {\n        var pend = state.pending.shift();\n        if (state.pending.length == 0) state.pending = null;\n        stream.pos += pend.text.length;\n        return pend.token;\n      }\n\n      if (state.local) {\n        if (state.local.end && stream.match(state.local.end)) {\n          var tok = state.local.endToken || null;\n          state.local = state.localState = null;\n          return tok;\n        } else {\n          var tok = state.local.mode.token(stream, state.localState), m;\n          if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))\n            stream.pos = stream.start + m.index;\n          return tok;\n        }\n      }\n\n      var curState = states[state.state];\n      for (var i = 0; i < curState.length; i++) {\n        var rule = curState[i];\n        var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);\n        if (matches) {\n          if (rule.data.next) {\n            state.state = rule.data.next;\n          } else if (rule.data.push) {\n            (state.stack || (state.stack = [])).push(state.state);\n            state.state = rule.data.push;\n          } else if (rule.data.pop && state.stack && state.stack.length) {\n            state.state = state.stack.pop();\n          }\n\n          if (rule.data.mode)\n            enterLocalMode(config, state, rule.data.mode, rule.token);\n          if (rule.data.indent)\n            state.indent.push(stream.indentation() + config.indentUnit);\n          if (rule.data.dedent)\n            state.indent.pop();\n          var token = rule.token\n          if (token && token.apply) token = token(matches)\n          if (matches.length > 2 && rule.token && typeof rule.token != \"string\") {\n            state.pending = [];\n            for (var j = 2; j < matches.length; j++)\n              if (matches[j])\n                state.pending.push({text: matches[j], token: rule.token[j - 1]});\n            stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));\n            return token[0];\n          } else if (token && token.join) {\n            return token[0];\n          } else {\n            return token;\n          }\n        }\n      }\n      stream.next();\n      return null;\n    };\n  }\n\n  function cmp(a, b) {\n    if (a === b) return true;\n    if (!a || typeof a != \"object\" || !b || typeof b != \"object\") return false;\n    var props = 0;\n    for (var prop in a) if (a.hasOwnProperty(prop)) {\n      if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;\n      props++;\n    }\n    for (var prop in b) if (b.hasOwnProperty(prop)) props--;\n    return props == 0;\n  }\n\n  function enterLocalMode(config, state, spec, token) {\n    var pers;\n    if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)\n      if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;\n    var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);\n    var lState = pers ? pers.state : CodeMirror.startState(mode);\n    if (spec.persistent && !pers)\n      state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};\n\n    state.localState = lState;\n    state.local = {mode: mode,\n                   end: spec.end && toRegex(spec.end),\n                   endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),\n                   endToken: token && token.join ? token[token.length - 1] : token};\n  }\n\n  function indexOf(val, arr) {\n    for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;\n  }\n\n  function indentFunction(states, meta) {\n    return function(state, textAfter, line) {\n      if (state.local && state.local.mode.indent)\n        return state.local.mode.indent(state.localState, textAfter, line);\n      if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)\n        return CodeMirror.Pass;\n\n      var pos = state.indent.length - 1, rules = states[state.state];\n      scan: for (;;) {\n        for (var i = 0; i < rules.length; i++) {\n          var rule = rules[i];\n          if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {\n            var m = rule.regex.exec(textAfter);\n            if (m && m[0]) {\n              pos--;\n              if (rule.next || rule.push) rules = states[rule.next || rule.push];\n              textAfter = textAfter.slice(m[0].length);\n              continue scan;\n            }\n          }\n        }\n        break;\n      }\n      return pos < 0 ? 0 : state.indent[pos];\n    };\n  }\n});\n\n\n/* ---- extension/sublime.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// A rough approximation of Sublime Text's keybindings\n// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../lib/codemirror\"), require(\"../addon/search/searchcursor\"), require(\"../addon/edit/matchbrackets\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../lib/codemirror\", \"../addon/search/searchcursor\", \"../addon/edit/matchbrackets\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var cmds = CodeMirror.commands;\n  var Pos = CodeMirror.Pos;\n\n  // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.\n  function findPosSubword(doc, start, dir) {\n    if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));\n    var line = doc.getLine(start.line);\n    if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));\n    var state = \"start\", type, startPos = start.ch;\n    for (var pos = startPos, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {\n      var next = line.charAt(dir < 0 ? pos - 1 : pos);\n      var cat = next != \"_\" && CodeMirror.isWordChar(next) ? \"w\" : \"o\";\n      if (cat == \"w\" && next.toUpperCase() == next) cat = \"W\";\n      if (state == \"start\") {\n        if (cat != \"o\") { state = \"in\"; type = cat; }\n        else startPos = pos + dir\n      } else if (state == \"in\") {\n        if (type != cat) {\n          if (type == \"w\" && cat == \"W\" && dir < 0) pos--;\n          if (type == \"W\" && cat == \"w\" && dir > 0) { // From uppercase to lowercase\n            if (pos == startPos + 1) { type = \"w\"; continue; }\n            else pos--;\n          }\n          break;\n        }\n      }\n    }\n    return Pos(start.line, pos);\n  }\n\n  function moveSubword(cm, dir) {\n    cm.extendSelectionsBy(function(range) {\n      if (cm.display.shift || cm.doc.extend || range.empty())\n        return findPosSubword(cm.doc, range.head, dir);\n      else\n        return dir < 0 ? range.from() : range.to();\n    });\n  }\n\n  cmds.goSubwordLeft = function(cm) { moveSubword(cm, -1); };\n  cmds.goSubwordRight = function(cm) { moveSubword(cm, 1); };\n\n  cmds.scrollLineUp = function(cm) {\n    var info = cm.getScrollInfo();\n    if (!cm.somethingSelected()) {\n      var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, \"local\");\n      if (cm.getCursor().line >= visibleBottomLine)\n        cm.execCommand(\"goLineUp\");\n    }\n    cm.scrollTo(null, info.top - cm.defaultTextHeight());\n  };\n  cmds.scrollLineDown = function(cm) {\n    var info = cm.getScrollInfo();\n    if (!cm.somethingSelected()) {\n      var visibleTopLine = cm.lineAtHeight(info.top, \"local\")+1;\n      if (cm.getCursor().line <= visibleTopLine)\n        cm.execCommand(\"goLineDown\");\n    }\n    cm.scrollTo(null, info.top + cm.defaultTextHeight());\n  };\n\n  cmds.splitSelectionByLine = function(cm) {\n    var ranges = cm.listSelections(), lineRanges = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var from = ranges[i].from(), to = ranges[i].to();\n      for (var line = from.line; line <= to.line; ++line)\n        if (!(to.line > from.line && line == to.line && to.ch == 0))\n          lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),\n                           head: line == to.line ? to : Pos(line)});\n    }\n    cm.setSelections(lineRanges, 0);\n  };\n\n  cmds.singleSelectionTop = function(cm) {\n    var range = cm.listSelections()[0];\n    cm.setSelection(range.anchor, range.head, {scroll: false});\n  };\n\n  cmds.selectLine = function(cm) {\n    var ranges = cm.listSelections(), extended = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      extended.push({anchor: Pos(range.from().line, 0),\n                     head: Pos(range.to().line + 1, 0)});\n    }\n    cm.setSelections(extended);\n  };\n\n  function insertLine(cm, above) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    cm.operation(function() {\n      var len = cm.listSelections().length, newSelection = [], last = -1;\n      for (var i = 0; i < len; i++) {\n        var head = cm.listSelections()[i].head;\n        if (head.line <= last) continue;\n        var at = Pos(head.line + (above ? 0 : 1), 0);\n        cm.replaceRange(\"\\n\", at, null, \"+insertLine\");\n        cm.indentLine(at.line, null, true);\n        newSelection.push({head: at, anchor: at});\n        last = head.line + 1;\n      }\n      cm.setSelections(newSelection);\n    });\n    cm.execCommand(\"indentAuto\");\n  }\n\n  cmds.insertLineAfter = function(cm) { return insertLine(cm, false); };\n\n  cmds.insertLineBefore = function(cm) { return insertLine(cm, true); };\n\n  function wordAt(cm, pos) {\n    var start = pos.ch, end = start, line = cm.getLine(pos.line);\n    while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;\n    while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;\n    return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};\n  }\n\n  cmds.selectNextOccurrence = function(cm) {\n    var from = cm.getCursor(\"from\"), to = cm.getCursor(\"to\");\n    var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;\n    if (CodeMirror.cmpPos(from, to) == 0) {\n      var word = wordAt(cm, from);\n      if (!word.word) return;\n      cm.setSelection(word.from, word.to);\n      fullWord = true;\n    } else {\n      var text = cm.getRange(from, to);\n      var query = fullWord ? new RegExp(\"\\\\b\" + text + \"\\\\b\") : text;\n      var cur = cm.getSearchCursor(query, to);\n      var found = cur.findNext();\n      if (!found) {\n        cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0));\n        found = cur.findNext();\n      }\n      if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to())) return\n      cm.addSelection(cur.from(), cur.to());\n    }\n    if (fullWord)\n      cm.state.sublimeFindFullWord = cm.doc.sel;\n  };\n\n  cmds.skipAndSelectNextOccurrence = function(cm) {\n    var prevAnchor = cm.getCursor(\"anchor\"), prevHead = cm.getCursor(\"head\");\n    cmds.selectNextOccurrence(cm);\n    if (CodeMirror.cmpPos(prevAnchor, prevHead) != 0) {\n      cm.doc.setSelections(cm.doc.listSelections()\n          .filter(function (sel) {\n            return sel.anchor != prevAnchor || sel.head != prevHead;\n          }));\n    }\n  }\n\n  function addCursorToSelection(cm, dir) {\n    var ranges = cm.listSelections(), newRanges = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      var newAnchor = cm.findPosV(\n          range.anchor, dir, \"line\", range.anchor.goalColumn);\n      var newHead = cm.findPosV(\n          range.head, dir, \"line\", range.head.goalColumn);\n      newAnchor.goalColumn = range.anchor.goalColumn != null ?\n          range.anchor.goalColumn : cm.cursorCoords(range.anchor, \"div\").left;\n      newHead.goalColumn = range.head.goalColumn != null ?\n          range.head.goalColumn : cm.cursorCoords(range.head, \"div\").left;\n      var newRange = {anchor: newAnchor, head: newHead};\n      newRanges.push(range);\n      newRanges.push(newRange);\n    }\n    cm.setSelections(newRanges);\n  }\n  cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };\n  cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };\n\n  function isSelectedRange(ranges, from, to) {\n    for (var i = 0; i < ranges.length; i++)\n      if (CodeMirror.cmpPos(ranges[i].from(), from) == 0 &&\n          CodeMirror.cmpPos(ranges[i].to(), to) == 0) return true\n    return false\n  }\n\n  var mirror = \"(){}[]\";\n  function selectBetweenBrackets(cm) {\n    var ranges = cm.listSelections(), newRanges = []\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], pos = range.head, opening = cm.scanForBracket(pos, -1);\n      if (!opening) return false;\n      for (;;) {\n        var closing = cm.scanForBracket(pos, 1);\n        if (!closing) return false;\n        if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) {\n          var startPos = Pos(opening.pos.line, opening.pos.ch + 1);\n          if (CodeMirror.cmpPos(startPos, range.from()) == 0 &&\n              CodeMirror.cmpPos(closing.pos, range.to()) == 0) {\n            opening = cm.scanForBracket(opening.pos, -1);\n            if (!opening) return false;\n          } else {\n            newRanges.push({anchor: startPos, head: closing.pos});\n            break;\n          }\n        }\n        pos = Pos(closing.pos.line, closing.pos.ch + 1);\n      }\n    }\n    cm.setSelections(newRanges);\n    return true;\n  }\n\n  cmds.selectScope = function(cm) {\n    selectBetweenBrackets(cm) || cm.execCommand(\"selectAll\");\n  };\n  cmds.selectBetweenBrackets = function(cm) {\n    if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;\n  };\n\n  function puncType(type) {\n    return !type ? null : /\\bpunctuation\\b/.test(type) ? type : undefined\n  }\n\n  cmds.goToBracket = function(cm) {\n    cm.extendSelectionsBy(function(range) {\n      var next = cm.scanForBracket(range.head, 1, puncType(cm.getTokenTypeAt(range.head)));\n      if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;\n      var prev = cm.scanForBracket(range.head, -1, puncType(cm.getTokenTypeAt(Pos(range.head.line, range.head.ch + 1))));\n      return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;\n    });\n  };\n\n  cmds.swapLineUp = function(cm) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], from = range.from().line - 1, to = range.to().line;\n      newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch),\n                    head: Pos(range.head.line - 1, range.head.ch)});\n      if (range.to().ch == 0 && !range.empty()) --to;\n      if (from > at) linesToMove.push(from, to);\n      else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;\n      at = to;\n    }\n    cm.operation(function() {\n      for (var i = 0; i < linesToMove.length; i += 2) {\n        var from = linesToMove[i], to = linesToMove[i + 1];\n        var line = cm.getLine(from);\n        cm.replaceRange(\"\", Pos(from, 0), Pos(from + 1, 0), \"+swapLine\");\n        if (to > cm.lastLine())\n          cm.replaceRange(\"\\n\" + line, Pos(cm.lastLine()), null, \"+swapLine\");\n        else\n          cm.replaceRange(line + \"\\n\", Pos(to, 0), null, \"+swapLine\");\n      }\n      cm.setSelections(newSels);\n      cm.scrollIntoView();\n    });\n  };\n\n  cmds.swapLineDown = function(cm) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;\n    for (var i = ranges.length - 1; i >= 0; i--) {\n      var range = ranges[i], from = range.to().line + 1, to = range.from().line;\n      if (range.to().ch == 0 && !range.empty()) from--;\n      if (from < at) linesToMove.push(from, to);\n      else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;\n      at = to;\n    }\n    cm.operation(function() {\n      for (var i = linesToMove.length - 2; i >= 0; i -= 2) {\n        var from = linesToMove[i], to = linesToMove[i + 1];\n        var line = cm.getLine(from);\n        if (from == cm.lastLine())\n          cm.replaceRange(\"\", Pos(from - 1), Pos(from), \"+swapLine\");\n        else\n          cm.replaceRange(\"\", Pos(from, 0), Pos(from + 1, 0), \"+swapLine\");\n        cm.replaceRange(line + \"\\n\", Pos(to, 0), null, \"+swapLine\");\n      }\n      cm.scrollIntoView();\n    });\n  };\n\n  cmds.toggleCommentIndented = function(cm) {\n    cm.toggleComment({ indent: true });\n  }\n\n  cmds.joinLines = function(cm) {\n    var ranges = cm.listSelections(), joined = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], from = range.from();\n      var start = from.line, end = range.to().line;\n      while (i < ranges.length - 1 && ranges[i + 1].from().line == end)\n        end = ranges[++i].to().line;\n      joined.push({start: start, end: end, anchor: !range.empty() && from});\n    }\n    cm.operation(function() {\n      var offset = 0, ranges = [];\n      for (var i = 0; i < joined.length; i++) {\n        var obj = joined[i];\n        var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head;\n        for (var line = obj.start; line <= obj.end; line++) {\n          var actual = line - offset;\n          if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1);\n          if (actual < cm.lastLine()) {\n            cm.replaceRange(\" \", Pos(actual), Pos(actual + 1, /^\\s*/.exec(cm.getLine(actual + 1))[0].length));\n            ++offset;\n          }\n        }\n        ranges.push({anchor: anchor || head, head: head});\n      }\n      cm.setSelections(ranges, 0);\n    });\n  };\n\n  cmds.duplicateLine = function(cm) {\n    cm.operation(function() {\n      var rangeCount = cm.listSelections().length;\n      for (var i = 0; i < rangeCount; i++) {\n        var range = cm.listSelections()[i];\n        if (range.empty())\n          cm.replaceRange(cm.getLine(range.head.line) + \"\\n\", Pos(range.head.line, 0));\n        else\n          cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());\n      }\n      cm.scrollIntoView();\n    });\n  };\n\n\n  function sortLines(cm, caseSensitive) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    var ranges = cm.listSelections(), toSort = [], selected;\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      if (range.empty()) continue;\n      var from = range.from().line, to = range.to().line;\n      while (i < ranges.length - 1 && ranges[i + 1].from().line == to)\n        to = ranges[++i].to().line;\n      if (!ranges[i].to().ch) to--;\n      toSort.push(from, to);\n    }\n    if (toSort.length) selected = true;\n    else toSort.push(cm.firstLine(), cm.lastLine());\n\n    cm.operation(function() {\n      var ranges = [];\n      for (var i = 0; i < toSort.length; i += 2) {\n        var from = toSort[i], to = toSort[i + 1];\n        var start = Pos(from, 0), end = Pos(to);\n        var lines = cm.getRange(start, end, false);\n        if (caseSensitive)\n          lines.sort();\n        else\n          lines.sort(function(a, b) {\n            var au = a.toUpperCase(), bu = b.toUpperCase();\n            if (au != bu) { a = au; b = bu; }\n            return a < b ? -1 : a == b ? 0 : 1;\n          });\n        cm.replaceRange(lines, start, end);\n        if (selected) ranges.push({anchor: start, head: Pos(to + 1, 0)});\n      }\n      if (selected) cm.setSelections(ranges, 0);\n    });\n  }\n\n  cmds.sortLines = function(cm) { sortLines(cm, true); };\n  cmds.sortLinesInsensitive = function(cm) { sortLines(cm, false); };\n\n  cmds.nextBookmark = function(cm) {\n    var marks = cm.state.sublimeBookmarks;\n    if (marks) while (marks.length) {\n      var current = marks.shift();\n      var found = current.find();\n      if (found) {\n        marks.push(current);\n        return cm.setSelection(found.from, found.to);\n      }\n    }\n  };\n\n  cmds.prevBookmark = function(cm) {\n    var marks = cm.state.sublimeBookmarks;\n    if (marks) while (marks.length) {\n      marks.unshift(marks.pop());\n      var found = marks[marks.length - 1].find();\n      if (!found)\n        marks.pop();\n      else\n        return cm.setSelection(found.from, found.to);\n    }\n  };\n\n  cmds.toggleBookmark = function(cm) {\n    var ranges = cm.listSelections();\n    var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);\n    for (var i = 0; i < ranges.length; i++) {\n      var from = ranges[i].from(), to = ranges[i].to();\n      var found = ranges[i].empty() ? cm.findMarksAt(from) : cm.findMarks(from, to);\n      for (var j = 0; j < found.length; j++) {\n        if (found[j].sublimeBookmark) {\n          found[j].clear();\n          for (var k = 0; k < marks.length; k++)\n            if (marks[k] == found[j])\n              marks.splice(k--, 1);\n          break;\n        }\n      }\n      if (j == found.length)\n        marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));\n    }\n  };\n\n  cmds.clearBookmarks = function(cm) {\n    var marks = cm.state.sublimeBookmarks;\n    if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();\n    marks.length = 0;\n  };\n\n  cmds.selectBookmarks = function(cm) {\n    var marks = cm.state.sublimeBookmarks, ranges = [];\n    if (marks) for (var i = 0; i < marks.length; i++) {\n      var found = marks[i].find();\n      if (!found)\n        marks.splice(i--, 0);\n      else\n        ranges.push({anchor: found.from, head: found.to});\n    }\n    if (ranges.length)\n      cm.setSelections(ranges, 0);\n  };\n\n  function modifyWordOrSelection(cm, mod) {\n    cm.operation(function() {\n      var ranges = cm.listSelections(), indices = [], replacements = [];\n      for (var i = 0; i < ranges.length; i++) {\n        var range = ranges[i];\n        if (range.empty()) { indices.push(i); replacements.push(\"\"); }\n        else replacements.push(mod(cm.getRange(range.from(), range.to())));\n      }\n      cm.replaceSelections(replacements, \"around\", \"case\");\n      for (var i = indices.length - 1, at; i >= 0; i--) {\n        var range = ranges[indices[i]];\n        if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;\n        var word = wordAt(cm, range.head);\n        at = word.from;\n        cm.replaceRange(mod(word.word), word.from, word.to);\n      }\n    });\n  }\n\n  cmds.smartBackspace = function(cm) {\n    if (cm.somethingSelected()) return CodeMirror.Pass;\n\n    cm.operation(function() {\n      var cursors = cm.listSelections();\n      var indentUnit = cm.getOption(\"indentUnit\");\n\n      for (var i = cursors.length - 1; i >= 0; i--) {\n        var cursor = cursors[i].head;\n        var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);\n        var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption(\"tabSize\"));\n\n        // Delete by one character by default\n        var deletePos = cm.findPosH(cursor, -1, \"char\", false);\n\n        if (toStartOfLine && !/\\S/.test(toStartOfLine) && column % indentUnit == 0) {\n          var prevIndent = new Pos(cursor.line,\n            CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));\n\n          // Smart delete only if we found a valid prevIndent location\n          if (prevIndent.ch != cursor.ch) deletePos = prevIndent;\n        }\n\n        cm.replaceRange(\"\", deletePos, cursor, \"+delete\");\n      }\n    });\n  };\n\n  cmds.delLineRight = function(cm) {\n    cm.operation(function() {\n      var ranges = cm.listSelections();\n      for (var i = ranges.length - 1; i >= 0; i--)\n        cm.replaceRange(\"\", ranges[i].anchor, Pos(ranges[i].to().line), \"+delete\");\n      cm.scrollIntoView();\n    });\n  };\n\n  cmds.upcaseAtCursor = function(cm) {\n    modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });\n  };\n  cmds.downcaseAtCursor = function(cm) {\n    modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });\n  };\n\n  cmds.setSublimeMark = function(cm) {\n    if (cm.state.sublimeMark) cm.state.sublimeMark.clear();\n    cm.state.sublimeMark = cm.setBookmark(cm.getCursor());\n  };\n  cmds.selectToSublimeMark = function(cm) {\n    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();\n    if (found) cm.setSelection(cm.getCursor(), found);\n  };\n  cmds.deleteToSublimeMark = function(cm) {\n    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();\n    if (found) {\n      var from = cm.getCursor(), to = found;\n      if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }\n      cm.state.sublimeKilled = cm.getRange(from, to);\n      cm.replaceRange(\"\", from, to);\n    }\n  };\n  cmds.swapWithSublimeMark = function(cm) {\n    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();\n    if (found) {\n      cm.state.sublimeMark.clear();\n      cm.state.sublimeMark = cm.setBookmark(cm.getCursor());\n      cm.setCursor(found);\n    }\n  };\n  cmds.sublimeYank = function(cm) {\n    if (cm.state.sublimeKilled != null)\n      cm.replaceSelection(cm.state.sublimeKilled, null, \"paste\");\n  };\n\n  cmds.showInCenter = function(cm) {\n    var pos = cm.cursorCoords(null, \"local\");\n    cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);\n  };\n\n  function getTarget(cm) {\n    var from = cm.getCursor(\"from\"), to = cm.getCursor(\"to\");\n    if (CodeMirror.cmpPos(from, to) == 0) {\n      var word = wordAt(cm, from);\n      if (!word.word) return;\n      from = word.from;\n      to = word.to;\n    }\n    return {from: from, to: to, query: cm.getRange(from, to), word: word};\n  }\n\n  function findAndGoTo(cm, forward) {\n    var target = getTarget(cm);\n    if (!target) return;\n    var query = target.query;\n    var cur = cm.getSearchCursor(query, forward ? target.to : target.from);\n\n    if (forward ? cur.findNext() : cur.findPrevious()) {\n      cm.setSelection(cur.from(), cur.to());\n    } else {\n      cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)\n                                              : cm.clipPos(Pos(cm.lastLine())));\n      if (forward ? cur.findNext() : cur.findPrevious())\n        cm.setSelection(cur.from(), cur.to());\n      else if (target.word)\n        cm.setSelection(target.from, target.to);\n    }\n  };\n  cmds.findUnder = function(cm) { findAndGoTo(cm, true); };\n  cmds.findUnderPrevious = function(cm) { findAndGoTo(cm,false); };\n  cmds.findAllUnder = function(cm) {\n    var target = getTarget(cm);\n    if (!target) return;\n    var cur = cm.getSearchCursor(target.query);\n    var matches = [];\n    var primaryIndex = -1;\n    while (cur.findNext()) {\n      matches.push({anchor: cur.from(), head: cur.to()});\n      if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)\n        primaryIndex++;\n    }\n    cm.setSelections(matches, primaryIndex);\n  };\n\n\n  var keyMap = CodeMirror.keyMap;\n  keyMap.macSublime = {\n    \"Cmd-Left\": \"goLineStartSmart\",\n    \"Shift-Tab\": \"indentLess\",\n    \"Shift-Ctrl-K\": \"deleteLine\",\n    \"Alt-Q\": \"wrapLines\",\n    \"Ctrl-Left\": \"goSubwordLeft\",\n    \"Ctrl-Right\": \"goSubwordRight\",\n    \"Ctrl-Alt-Up\": \"scrollLineUp\",\n    \"Ctrl-Alt-Down\": \"scrollLineDown\",\n    \"Cmd-L\": \"selectLine\",\n    \"Shift-Cmd-L\": \"splitSelectionByLine\",\n    \"Esc\": \"singleSelectionTop\",\n    \"Cmd-Enter\": \"insertLineAfter\",\n    \"Shift-Cmd-Enter\": \"insertLineBefore\",\n    \"Cmd-D\": \"selectNextOccurrence\",\n    \"Shift-Cmd-Space\": \"selectScope\",\n    \"Shift-Cmd-M\": \"selectBetweenBrackets\",\n    \"Cmd-M\": \"goToBracket\",\n    \"Cmd-Ctrl-Up\": \"swapLineUp\",\n    \"Cmd-Ctrl-Down\": \"swapLineDown\",\n    \"Cmd-/\": \"toggleCommentIndented\",\n    \"Cmd-J\": \"joinLines\",\n    \"Shift-Cmd-D\": \"duplicateLine\",\n    \"F5\": \"sortLines\",\n    \"Cmd-F5\": \"sortLinesInsensitive\",\n    \"F2\": \"nextBookmark\",\n    \"Shift-F2\": \"prevBookmark\",\n    \"Cmd-F2\": \"toggleBookmark\",\n    \"Shift-Cmd-F2\": \"clearBookmarks\",\n    \"Alt-F2\": \"selectBookmarks\",\n    \"Backspace\": \"smartBackspace\",\n    \"Cmd-K Cmd-D\": \"skipAndSelectNextOccurrence\",\n    \"Cmd-K Cmd-K\": \"delLineRight\",\n    \"Cmd-K Cmd-U\": \"upcaseAtCursor\",\n    \"Cmd-K Cmd-L\": \"downcaseAtCursor\",\n    \"Cmd-K Cmd-Space\": \"setSublimeMark\",\n    \"Cmd-K Cmd-A\": \"selectToSublimeMark\",\n    \"Cmd-K Cmd-W\": \"deleteToSublimeMark\",\n    \"Cmd-K Cmd-X\": \"swapWithSublimeMark\",\n    \"Cmd-K Cmd-Y\": \"sublimeYank\",\n    \"Cmd-K Cmd-C\": \"showInCenter\",\n    \"Cmd-K Cmd-G\": \"clearBookmarks\",\n    \"Cmd-K Cmd-Backspace\": \"delLineLeft\",\n    \"Cmd-K Cmd-1\": \"foldAll\",\n    \"Cmd-K Cmd-0\": \"unfoldAll\",\n    \"Cmd-K Cmd-J\": \"unfoldAll\",\n    \"Ctrl-Shift-Up\": \"addCursorToPrevLine\",\n    \"Ctrl-Shift-Down\": \"addCursorToNextLine\",\n    \"Cmd-F3\": \"findUnder\",\n    \"Shift-Cmd-F3\": \"findUnderPrevious\",\n    \"Alt-F3\": \"findAllUnder\",\n    \"Shift-Cmd-[\": \"fold\",\n    \"Shift-Cmd-]\": \"unfold\",\n    \"Cmd-I\": \"findIncremental\",\n    \"Shift-Cmd-I\": \"findIncrementalReverse\",\n    \"Cmd-H\": \"replace\",\n    \"F3\": \"findNext\",\n    \"Shift-F3\": \"findPrev\",\n    \"fallthrough\": \"macDefault\"\n  };\n  CodeMirror.normalizeKeyMap(keyMap.macSublime);\n\n  keyMap.pcSublime = {\n    \"Shift-Tab\": \"indentLess\",\n    \"Shift-Ctrl-K\": \"deleteLine\",\n    \"Alt-Q\": \"wrapLines\",\n    \"Ctrl-T\": \"transposeChars\",\n    \"Alt-Left\": \"goSubwordLeft\",\n    \"Alt-Right\": \"goSubwordRight\",\n    \"Ctrl-Up\": \"scrollLineUp\",\n    \"Ctrl-Down\": \"scrollLineDown\",\n    \"Ctrl-L\": \"selectLine\",\n    \"Shift-Ctrl-L\": \"splitSelectionByLine\",\n    \"Esc\": \"singleSelectionTop\",\n    \"Ctrl-Enter\": \"insertLineAfter\",\n    \"Shift-Ctrl-Enter\": \"insertLineBefore\",\n    \"Ctrl-D\": \"selectNextOccurrence\",\n    \"Shift-Ctrl-Space\": \"selectScope\",\n    \"Shift-Ctrl-M\": \"selectBetweenBrackets\",\n    \"Ctrl-M\": \"goToBracket\",\n    \"Shift-Ctrl-Up\": \"swapLineUp\",\n    \"Shift-Ctrl-Down\": \"swapLineDown\",\n    \"Ctrl-/\": \"toggleCommentIndented\",\n    \"Ctrl-J\": \"joinLines\",\n    \"Shift-Ctrl-D\": \"duplicateLine\",\n    \"F9\": \"sortLines\",\n    \"Ctrl-F9\": \"sortLinesInsensitive\",\n    \"F2\": \"nextBookmark\",\n    \"Shift-F2\": \"prevBookmark\",\n    \"Ctrl-F2\": \"toggleBookmark\",\n    \"Shift-Ctrl-F2\": \"clearBookmarks\",\n    \"Alt-F2\": \"selectBookmarks\",\n    \"Backspace\": \"smartBackspace\",\n    \"Ctrl-K Ctrl-D\": \"skipAndSelectNextOccurrence\",\n    \"Ctrl-K Ctrl-K\": \"delLineRight\",\n    \"Ctrl-K Ctrl-U\": \"upcaseAtCursor\",\n    \"Ctrl-K Ctrl-L\": \"downcaseAtCursor\",\n    \"Ctrl-K Ctrl-Space\": \"setSublimeMark\",\n    \"Ctrl-K Ctrl-A\": \"selectToSublimeMark\",\n    \"Ctrl-K Ctrl-W\": \"deleteToSublimeMark\",\n    \"Ctrl-K Ctrl-X\": \"swapWithSublimeMark\",\n    \"Ctrl-K Ctrl-Y\": \"sublimeYank\",\n    \"Ctrl-K Ctrl-C\": \"showInCenter\",\n    \"Ctrl-K Ctrl-G\": \"clearBookmarks\",\n    \"Ctrl-K Ctrl-Backspace\": \"delLineLeft\",\n    \"Ctrl-K Ctrl-1\": \"foldAll\",\n    \"Ctrl-K Ctrl-0\": \"unfoldAll\",\n    \"Ctrl-K Ctrl-J\": \"unfoldAll\",\n    \"Ctrl-Alt-Up\": \"addCursorToPrevLine\",\n    \"Ctrl-Alt-Down\": \"addCursorToNextLine\",\n    \"Ctrl-F3\": \"findUnder\",\n    \"Shift-Ctrl-F3\": \"findUnderPrevious\",\n    \"Alt-F3\": \"findAllUnder\",\n    \"Shift-Ctrl-[\": \"fold\",\n    \"Shift-Ctrl-]\": \"unfold\",\n    \"Ctrl-I\": \"findIncremental\",\n    \"Shift-Ctrl-I\": \"findIncrementalReverse\",\n    \"Ctrl-H\": \"replace\",\n    \"F3\": \"findNext\",\n    \"Shift-F3\": \"findPrev\",\n    \"fallthrough\": \"pcDefault\"\n  };\n  CodeMirror.normalizeKeyMap(keyMap.pcSublime);\n\n  var mac = keyMap.default == keyMap.macDefault;\n  keyMap.sublime = mac ? keyMap.macSublime : keyMap.pcSublime;\n});\n\n\n/* ---- extension/dialog/dialog.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Open simple dialogs on top of an editor. Relies on dialog.css.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  function dialogDiv(cm, template, bottom) {\n    var wrap = cm.getWrapperElement();\n    var dialog;\n    dialog = wrap.appendChild(document.createElement(\"div\"));\n    if (bottom)\n      dialog.className = \"CodeMirror-dialog CodeMirror-dialog-bottom\";\n    else\n      dialog.className = \"CodeMirror-dialog CodeMirror-dialog-top\";\n\n    if (typeof template == \"string\") {\n      dialog.innerHTML = template;\n    } else { // Assuming it's a detached DOM element.\n      dialog.appendChild(template);\n    }\n    CodeMirror.addClass(wrap, 'dialog-opened');\n    return dialog;\n  }\n\n  function closeNotification(cm, newVal) {\n    if (cm.state.currentNotificationClose)\n      cm.state.currentNotificationClose();\n    cm.state.currentNotificationClose = newVal;\n  }\n\n  CodeMirror.defineExtension(\"openDialog\", function(template, callback, options) {\n    if (!options) options = {};\n\n    closeNotification(this, null);\n\n    var dialog = dialogDiv(this, template, options.bottom);\n    var closed = false, me = this;\n    function close(newVal) {\n      if (typeof newVal == 'string') {\n        inp.value = newVal;\n      } else {\n        if (closed) return;\n        closed = true;\n        CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');\n        dialog.parentNode.removeChild(dialog);\n        me.focus();\n\n        if (options.onClose) options.onClose(dialog);\n      }\n    }\n\n    var inp = dialog.getElementsByTagName(\"input\")[0], button;\n    if (inp) {\n      inp.focus();\n\n      if (options.value) {\n        inp.value = options.value;\n        if (options.selectValueOnOpen !== false) {\n          inp.select();\n        }\n      }\n\n      if (options.onInput)\n        CodeMirror.on(inp, \"input\", function(e) { options.onInput(e, inp.value, close);});\n      if (options.onKeyUp)\n        CodeMirror.on(inp, \"keyup\", function(e) {options.onKeyUp(e, inp.value, close);});\n\n      CodeMirror.on(inp, \"keydown\", function(e) {\n        if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }\n        if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {\n          inp.blur();\n          CodeMirror.e_stop(e);\n          close();\n        }\n        if (e.keyCode == 13) callback(inp.value, e);\n      });\n\n      if (options.closeOnBlur !== false) CodeMirror.on(dialog, \"focusout\", function (evt) {\n        if (evt.relatedTarget !== null) close();\n      });\n    } else if (button = dialog.getElementsByTagName(\"button\")[0]) {\n      CodeMirror.on(button, \"click\", function() {\n        close();\n        me.focus();\n      });\n\n      if (options.closeOnBlur !== false) CodeMirror.on(button, \"blur\", close);\n\n      button.focus();\n    }\n    return close;\n  });\n\n  CodeMirror.defineExtension(\"openConfirm\", function(template, callbacks, options) {\n    closeNotification(this, null);\n    var dialog = dialogDiv(this, template, options && options.bottom);\n    var buttons = dialog.getElementsByTagName(\"button\");\n    var closed = false, me = this, blurring = 1;\n    function close() {\n      if (closed) return;\n      closed = true;\n      CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');\n      dialog.parentNode.removeChild(dialog);\n      me.focus();\n    }\n    buttons[0].focus();\n    for (var i = 0; i < buttons.length; ++i) {\n      var b = buttons[i];\n      (function(callback) {\n        CodeMirror.on(b, \"click\", function(e) {\n          CodeMirror.e_preventDefault(e);\n          close();\n          if (callback) callback(me);\n        });\n      })(callbacks[i]);\n      CodeMirror.on(b, \"blur\", function() {\n        --blurring;\n        setTimeout(function() { if (blurring <= 0) close(); }, 200);\n      });\n      CodeMirror.on(b, \"focus\", function() { ++blurring; });\n    }\n  });\n\n  /*\n   * openNotification\n   * Opens a notification, that can be closed with an optional timer\n   * (default 5000ms timer) and always closes on click.\n   *\n   * If a notification is opened while another is opened, it will close the\n   * currently opened one and open the new one immediately.\n   */\n  CodeMirror.defineExtension(\"openNotification\", function(template, options) {\n    closeNotification(this, close);\n    var dialog = dialogDiv(this, template, options && options.bottom);\n    var closed = false, doneTimer;\n    var duration = options && typeof options.duration !== \"undefined\" ? options.duration : 5000;\n\n    function close() {\n      if (closed) return;\n      closed = true;\n      clearTimeout(doneTimer);\n      CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');\n      dialog.parentNode.removeChild(dialog);\n    }\n\n    CodeMirror.on(dialog, 'click', function(e) {\n      CodeMirror.e_preventDefault(e);\n      close();\n    });\n\n    if (duration)\n      doneTimer = setTimeout(close, duration);\n\n    return close;\n  });\n});\n\n\n/* ---- extension/edit/closebrackets.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  var defaults = {\n    pairs: \"()[]{}''\\\"\\\"\",\n    closeBefore: \")]}'\\\":;>\",\n    triples: \"\",\n    explode: \"[]{}\"\n  };\n\n  var Pos = CodeMirror.Pos;\n\n  CodeMirror.defineOption(\"autoCloseBrackets\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.removeKeyMap(keyMap);\n      cm.state.closeBrackets = null;\n    }\n    if (val) {\n      ensureBound(getOption(val, \"pairs\"))\n      cm.state.closeBrackets = val;\n      cm.addKeyMap(keyMap);\n    }\n  });\n\n  function getOption(conf, name) {\n    if (name == \"pairs\" && typeof conf == \"string\") return conf;\n    if (typeof conf == \"object\" && conf[name] != null) return conf[name];\n    return defaults[name];\n  }\n\n  var keyMap = {Backspace: handleBackspace, Enter: handleEnter};\n  function ensureBound(chars) {\n    for (var i = 0; i < chars.length; i++) {\n      var ch = chars.charAt(i), key = \"'\" + ch + \"'\"\n      if (!keyMap[key]) keyMap[key] = handler(ch)\n    }\n  }\n  ensureBound(defaults.pairs + \"`\")\n\n  function handler(ch) {\n    return function(cm) { return handleChar(cm, ch); };\n  }\n\n  function getConfig(cm) {\n    var deflt = cm.state.closeBrackets;\n    if (!deflt || deflt.override) return deflt;\n    var mode = cm.getModeAt(cm.getCursor());\n    return mode.closeBrackets || deflt;\n  }\n\n  function handleBackspace(cm) {\n    var conf = getConfig(cm);\n    if (!conf || cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n\n    var pairs = getOption(conf, \"pairs\");\n    var ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var around = charsAround(cm, ranges[i].head);\n      if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;\n    }\n    for (var i = ranges.length - 1; i >= 0; i--) {\n      var cur = ranges[i].head;\n      cm.replaceRange(\"\", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), \"+delete\");\n    }\n  }\n\n  function handleEnter(cm) {\n    var conf = getConfig(cm);\n    var explode = conf && getOption(conf, \"explode\");\n    if (!explode || cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n\n    var ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var around = charsAround(cm, ranges[i].head);\n      if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;\n    }\n    cm.operation(function() {\n      var linesep = cm.lineSeparator() || \"\\n\";\n      cm.replaceSelection(linesep + linesep, null);\n      cm.execCommand(\"goCharLeft\");\n      ranges = cm.listSelections();\n      for (var i = 0; i < ranges.length; i++) {\n        var line = ranges[i].head.line;\n        cm.indentLine(line, null, true);\n        cm.indentLine(line + 1, null, true);\n      }\n    });\n  }\n\n  function contractSelection(sel) {\n    var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;\n    return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),\n            head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};\n  }\n\n  function handleChar(cm, ch) {\n    var conf = getConfig(cm);\n    if (!conf || cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n\n    var pairs = getOption(conf, \"pairs\");\n    var pos = pairs.indexOf(ch);\n    if (pos == -1) return CodeMirror.Pass;\n\n    var closeBefore = getOption(conf,\"closeBefore\");\n\n    var triples = getOption(conf, \"triples\");\n\n    var identical = pairs.charAt(pos + 1) == ch;\n    var ranges = cm.listSelections();\n    var opening = pos % 2 == 0;\n\n    var type;\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], cur = range.head, curType;\n      var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));\n      if (opening && !range.empty()) {\n        curType = \"surround\";\n      } else if ((identical || !opening) && next == ch) {\n        if (identical && stringStartsAfter(cm, cur))\n          curType = \"both\";\n        else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)\n          curType = \"skipThree\";\n        else\n          curType = \"skip\";\n      } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&\n                 cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {\n        if (cur.ch > 2 && /\\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;\n        curType = \"addFour\";\n      } else if (identical) {\n        var prev = cur.ch == 0 ? \" \" : cm.getRange(Pos(cur.line, cur.ch - 1), cur)\n        if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = \"both\";\n        else return CodeMirror.Pass;\n      } else if (opening && (next.length === 0 || /\\s/.test(next) || closeBefore.indexOf(next) > -1)) {\n        curType = \"both\";\n      } else {\n        return CodeMirror.Pass;\n      }\n      if (!type) type = curType;\n      else if (type != curType) return CodeMirror.Pass;\n    }\n\n    var left = pos % 2 ? pairs.charAt(pos - 1) : ch;\n    var right = pos % 2 ? ch : pairs.charAt(pos + 1);\n    cm.operation(function() {\n      if (type == \"skip\") {\n        cm.execCommand(\"goCharRight\");\n      } else if (type == \"skipThree\") {\n        for (var i = 0; i < 3; i++)\n          cm.execCommand(\"goCharRight\");\n      } else if (type == \"surround\") {\n        var sels = cm.getSelections();\n        for (var i = 0; i < sels.length; i++)\n          sels[i] = left + sels[i] + right;\n        cm.replaceSelections(sels, \"around\");\n        sels = cm.listSelections().slice();\n        for (var i = 0; i < sels.length; i++)\n          sels[i] = contractSelection(sels[i]);\n        cm.setSelections(sels);\n      } else if (type == \"both\") {\n        cm.replaceSelection(left + right, null);\n        cm.triggerElectric(left + right);\n        cm.execCommand(\"goCharLeft\");\n      } else if (type == \"addFour\") {\n        cm.replaceSelection(left + left + left + left, \"before\");\n        cm.execCommand(\"goCharRight\");\n      }\n    });\n  }\n\n  function charsAround(cm, pos) {\n    var str = cm.getRange(Pos(pos.line, pos.ch - 1),\n                          Pos(pos.line, pos.ch + 1));\n    return str.length == 2 ? str : null;\n  }\n\n  function stringStartsAfter(cm, pos) {\n    var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))\n    return /\\bstring/.test(token.type) && token.start == pos.ch &&\n      (pos.ch == 0 || !/\\bstring/.test(cm.getTokenTypeAt(pos)))\n  }\n});\n\n\n/* ---- extension/edit/closetag.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n/**\n * Tag-closer extension for CodeMirror.\n *\n * This extension adds an \"autoCloseTags\" option that can be set to\n * either true to get the default behavior, or an object to further\n * configure its behavior.\n *\n * These are supported options:\n *\n * `whenClosing` (default true)\n *   Whether to autoclose when the '/' of a closing tag is typed.\n * `whenOpening` (default true)\n *   Whether to autoclose the tag when the final '>' of an opening\n *   tag is typed.\n * `dontCloseTags` (default is empty tags for HTML, none for XML)\n *   An array of tag names that should not be autoclosed.\n * `indentTags` (default is block tags for HTML, none for XML)\n *   An array of tag names that should, when opened, cause a\n *   blank line to be added inside the tag, and the blank line and\n *   closing line to be indented.\n * `emptyTags` (default is none)\n *   An array of XML tag names that should be autoclosed with '/>'.\n *\n * See demos/closetag.html for a usage example.\n */\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../fold/xml-fold\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../fold/xml-fold\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  CodeMirror.defineOption(\"autoCloseTags\", false, function(cm, val, old) {\n    if (old != CodeMirror.Init && old)\n      cm.removeKeyMap(\"autoCloseTags\");\n    if (!val) return;\n    var map = {name: \"autoCloseTags\"};\n    if (typeof val != \"object\" || val.whenClosing !== false)\n      map[\"'/'\"] = function(cm) { return autoCloseSlash(cm); };\n    if (typeof val != \"object\" || val.whenOpening !== false)\n      map[\"'>'\"] = function(cm) { return autoCloseGT(cm); };\n    cm.addKeyMap(map);\n  });\n\n  var htmlDontClose = [\"area\", \"base\", \"br\", \"col\", \"command\", \"embed\", \"hr\", \"img\", \"input\", \"keygen\", \"link\", \"meta\", \"param\",\n                       \"source\", \"track\", \"wbr\"];\n  var htmlIndent = [\"applet\", \"blockquote\", \"body\", \"button\", \"div\", \"dl\", \"fieldset\", \"form\", \"frameset\", \"h1\", \"h2\", \"h3\", \"h4\",\n                    \"h5\", \"h6\", \"head\", \"html\", \"iframe\", \"layer\", \"legend\", \"object\", \"ol\", \"p\", \"select\", \"table\", \"ul\"];\n\n  function autoCloseGT(cm) {\n    if (cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n    var ranges = cm.listSelections(), replacements = [];\n    var opt = cm.getOption(\"autoCloseTags\");\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var pos = ranges[i].head, tok = cm.getTokenAt(pos);\n      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;\n      var tagInfo = inner.mode.xmlCurrentTag && inner.mode.xmlCurrentTag(state)\n      var tagName = tagInfo && tagInfo.name\n      if (!tagName) return CodeMirror.Pass\n\n      var html = inner.mode.configuration == \"html\";\n      var dontCloseTags = (typeof opt == \"object\" && opt.dontCloseTags) || (html && htmlDontClose);\n      var indentTags = (typeof opt == \"object\" && opt.indentTags) || (html && htmlIndent);\n\n      if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);\n      var lowerTagName = tagName.toLowerCase();\n      // Don't process the '>' at the end of an end-tag or self-closing tag\n      if (!tagName ||\n          tok.type == \"string\" && (tok.end != pos.ch || !/[\\\"\\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||\n          tok.type == \"tag\" && tagInfo.close ||\n          tok.string.indexOf(\"/\") == (pos.ch - tok.start - 1) || // match something like <someTagName />\n          dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||\n          closingTagExists(cm, inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state) || [], tagName, pos, true))\n        return CodeMirror.Pass;\n\n      var emptyTags = typeof opt == \"object\" && opt.emptyTags;\n      if (emptyTags && indexOf(emptyTags, tagName) > -1) {\n        replacements[i] = { text: \"/>\", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) };\n        continue;\n      }\n\n      var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;\n      replacements[i] = {indent: indent,\n                         text: \">\" + (indent ? \"\\n\\n\" : \"\") + \"</\" + tagName + \">\",\n                         newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};\n    }\n\n    var dontIndentOnAutoClose = (typeof opt == \"object\" && opt.dontIndentOnAutoClose);\n    for (var i = ranges.length - 1; i >= 0; i--) {\n      var info = replacements[i];\n      cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, \"+insert\");\n      var sel = cm.listSelections().slice(0);\n      sel[i] = {head: info.newPos, anchor: info.newPos};\n      cm.setSelections(sel);\n      if (!dontIndentOnAutoClose && info.indent) {\n        cm.indentLine(info.newPos.line, null, true);\n        cm.indentLine(info.newPos.line + 1, null, true);\n      }\n    }\n  }\n\n  function autoCloseCurrent(cm, typingSlash) {\n    var ranges = cm.listSelections(), replacements = [];\n    var head = typingSlash ? \"/\" : \"</\";\n    var opt = cm.getOption(\"autoCloseTags\");\n    var dontIndentOnAutoClose = (typeof opt == \"object\" && opt.dontIndentOnSlash);\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var pos = ranges[i].head, tok = cm.getTokenAt(pos);\n      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;\n      if (typingSlash && (tok.type == \"string\" || tok.string.charAt(0) != \"<\" ||\n                          tok.start != pos.ch - 1))\n        return CodeMirror.Pass;\n      // Kludge to get around the fact that we are not in XML mode\n      // when completing in JS/CSS snippet in htmlmixed mode. Does not\n      // work for other XML embedded languages (there is no general\n      // way to go from a mixed mode to its current XML state).\n      var replacement, mixed = inner.mode.name != \"xml\" && cm.getMode().name == \"htmlmixed\"\n      if (mixed && inner.mode.name == \"javascript\") {\n        replacement = head + \"script\";\n      } else if (mixed && inner.mode.name == \"css\") {\n        replacement = head + \"style\";\n      } else {\n        var context = inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state)\n        if (!context || (context.length && closingTagExists(cm, context, context[context.length - 1], pos)))\n          return CodeMirror.Pass;\n        replacement = head + context[context.length - 1]\n      }\n      if (cm.getLine(pos.line).charAt(tok.end) != \">\") replacement += \">\";\n      replacements[i] = replacement;\n    }\n    cm.replaceSelections(replacements);\n    ranges = cm.listSelections();\n    if (!dontIndentOnAutoClose) {\n        for (var i = 0; i < ranges.length; i++)\n            if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)\n                cm.indentLine(ranges[i].head.line);\n    }\n  }\n\n  function autoCloseSlash(cm) {\n    if (cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n    return autoCloseCurrent(cm, true);\n  }\n\n  CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };\n\n  function indexOf(collection, elt) {\n    if (collection.indexOf) return collection.indexOf(elt);\n    for (var i = 0, e = collection.length; i < e; ++i)\n      if (collection[i] == elt) return i;\n    return -1;\n  }\n\n  // If xml-fold is loaded, we use its functionality to try and verify\n  // whether a given tag is actually unclosed.\n  function closingTagExists(cm, context, tagName, pos, newTag) {\n    if (!CodeMirror.scanForClosingTag) return false;\n    var end = Math.min(cm.lastLine() + 1, pos.line + 500);\n    var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);\n    if (!nextClose || nextClose.tag != tagName) return false;\n    // If the immediate wrapping context contains onCx instances of\n    // the same tag, a closing tag only exists if there are at least\n    // that many closing tags of that type following.\n    var onCx = newTag ? 1 : 0\n    for (var i = context.length - 1; i >= 0; i--) {\n      if (context[i] == tagName) ++onCx\n      else break\n    }\n    pos = nextClose.to;\n    for (var i = 1; i < onCx; i++) {\n      var next = CodeMirror.scanForClosingTag(cm, pos, null, end);\n      if (!next || next.tag != tagName) return false;\n      pos = next.to;\n    }\n    return true;\n  }\n});\n\n\n/* ---- extension/edit/continuelist.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var listRE = /^(\\s*)(>[> ]*|[*+-] \\[[x ]\\]\\s|[*+-]\\s|(\\d+)([.)]))(\\s*)/,\n      emptyListRE = /^(\\s*)(>[> ]*|[*+-] \\[[x ]\\]|[*+-]|(\\d+)[.)])(\\s*)$/,\n      unorderedListRE = /[*+-]\\s/;\n\n  CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {\n    if (cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n    var ranges = cm.listSelections(), replacements = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var pos = ranges[i].head;\n\n      // If we're not in Markdown mode, fall back to normal newlineAndIndent\n      var eolState = cm.getStateAfter(pos.line);\n      var inner = CodeMirror.innerMode(cm.getMode(), eolState);\n      if (inner.mode.name !== \"markdown\") {\n        cm.execCommand(\"newlineAndIndent\");\n        return;\n      } else {\n        eolState = inner.state;\n      }\n\n      var inList = eolState.list !== false;\n      var inQuote = eolState.quote !== 0;\n\n      var line = cm.getLine(pos.line), match = listRE.exec(line);\n      var cursorBeforeBullet = /^\\s*$/.test(line.slice(0, pos.ch));\n      if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) {\n        cm.execCommand(\"newlineAndIndent\");\n        return;\n      }\n      if (emptyListRE.test(line)) {\n        var endOfQuote = inQuote && />\\s*$/.test(line)\n        var endOfList = !/>\\s*$/.test(line)\n        if (endOfQuote || endOfList) cm.replaceRange(\"\", {\n          line: pos.line, ch: 0\n        }, {\n          line: pos.line, ch: pos.ch + 1\n        });\n        replacements[i] = \"\\n\";\n      } else {\n        var indent = match[1], after = match[5];\n        var numbered = !(unorderedListRE.test(match[2]) || match[2].indexOf(\">\") >= 0);\n        var bullet = numbered ? (parseInt(match[3], 10) + 1) + match[4] : match[2].replace(\"x\", \" \");\n        replacements[i] = \"\\n\" + indent + bullet + after;\n\n        if (numbered) incrementRemainingMarkdownListNumbers(cm, pos);\n      }\n    }\n\n    cm.replaceSelections(replacements);\n  };\n\n  // Auto-updating Markdown list numbers when a new item is added to the\n  // middle of a list\n  function incrementRemainingMarkdownListNumbers(cm, pos) {\n    var startLine = pos.line, lookAhead = 0, skipCount = 0;\n    var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1];\n\n    do {\n      lookAhead += 1;\n      var nextLineNumber = startLine + lookAhead;\n      var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine);\n\n      if (nextItem) {\n        var nextIndent = nextItem[1];\n        var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount);\n        var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber;\n\n        if (startIndent === nextIndent && !isNaN(nextNumber)) {\n          if (newNumber === nextNumber) itemNumber = nextNumber + 1;\n          if (newNumber > nextNumber) itemNumber = newNumber + 1;\n          cm.replaceRange(\n            nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]),\n          {\n            line: nextLineNumber, ch: 0\n          }, {\n            line: nextLineNumber, ch: nextLine.length\n          });\n        } else {\n          if (startIndent.length > nextIndent.length) return;\n          // This doesn't run if the next line immediatley indents, as it is\n          // not clear of the users intention (new indented item or same level)\n          if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return;\n          skipCount += 1;\n        }\n      }\n    } while (nextItem);\n  }\n});\n\n\n/* ---- extension/edit/matchbrackets.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  var ie_lt8 = /MSIE \\d/.test(navigator.userAgent) &&\n    (document.documentMode == null || document.documentMode < 8);\n\n  var Pos = CodeMirror.Pos;\n\n  var matching = {\"(\": \")>\", \")\": \"(<\", \"[\": \"]>\", \"]\": \"[<\", \"{\": \"}>\", \"}\": \"{<\", \"<\": \">>\", \">\": \"<<\"};\n\n  function bracketRegex(config) {\n    return config && config.bracketRegex || /[(){}[\\]]/\n  }\n\n  function findMatchingBracket(cm, where, config) {\n    var line = cm.getLineHandle(where.line), pos = where.ch - 1;\n    var afterCursor = config && config.afterCursor\n    if (afterCursor == null)\n      afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)\n    var re = bracketRegex(config)\n\n    // A cursor is defined as between two characters, but in in vim command mode\n    // (i.e. not insert mode), the cursor is visually represented as a\n    // highlighted box on top of the 2nd character. Otherwise, we allow matches\n    // from before or after the cursor.\n    var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||\n        re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];\n    if (!match) return null;\n    var dir = match.charAt(1) == \">\" ? 1 : -1;\n    if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;\n    var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));\n\n    var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);\n    if (found == null) return null;\n    return {from: Pos(where.line, pos), to: found && found.pos,\n            match: found && found.ch == match.charAt(0), forward: dir > 0};\n  }\n\n  // bracketRegex is used to specify which type of bracket to scan\n  // should be a regexp, e.g. /[[\\]]/\n  //\n  // Note: If \"where\" is on an open bracket, then this bracket is ignored.\n  //\n  // Returns false when no bracket was found, null when it reached\n  // maxScanLines and gave up\n  function scanForBracket(cm, where, dir, style, config) {\n    var maxScanLen = (config && config.maxScanLineLength) || 10000;\n    var maxScanLines = (config && config.maxScanLines) || 1000;\n\n    var stack = [];\n    var re = bracketRegex(config)\n    var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)\n                          : Math.max(cm.firstLine() - 1, where.line - maxScanLines);\n    for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {\n      var line = cm.getLine(lineNo);\n      if (!line) continue;\n      var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;\n      if (line.length > maxScanLen) continue;\n      if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);\n      for (; pos != end; pos += dir) {\n        var ch = line.charAt(pos);\n        if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {\n          var match = matching[ch];\n          if (match && (match.charAt(1) == \">\") == (dir > 0)) stack.push(ch);\n          else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};\n          else stack.pop();\n        }\n      }\n    }\n    return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;\n  }\n\n  function matchBrackets(cm, autoclear, config) {\n    // Disable brace matching in long lines, since it'll cause hugely slow updates\n    var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;\n    var marks = [], ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++) {\n      var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);\n      if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {\n        var style = match.match ? \"CodeMirror-matchingbracket\" : \"CodeMirror-nonmatchingbracket\";\n        marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));\n        if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)\n          marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));\n      }\n    }\n\n    if (marks.length) {\n      // Kludge to work around the IE bug from issue #1193, where text\n      // input stops going to the textare whever this fires.\n      if (ie_lt8 && cm.state.focused) cm.focus();\n\n      var clear = function() {\n        cm.operation(function() {\n          for (var i = 0; i < marks.length; i++) marks[i].clear();\n        });\n      };\n      if (autoclear) setTimeout(clear, 800);\n      else return clear;\n    }\n  }\n\n  function doMatchBrackets(cm) {\n    cm.operation(function() {\n      if (cm.state.matchBrackets.currentlyHighlighted) {\n        cm.state.matchBrackets.currentlyHighlighted();\n        cm.state.matchBrackets.currentlyHighlighted = null;\n      }\n      cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);\n    });\n  }\n\n  CodeMirror.defineOption(\"matchBrackets\", false, function(cm, val, old) {\n    function clear(cm) {\n      if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {\n        cm.state.matchBrackets.currentlyHighlighted();\n        cm.state.matchBrackets.currentlyHighlighted = null;\n      }\n    }\n\n    if (old && old != CodeMirror.Init) {\n      cm.off(\"cursorActivity\", doMatchBrackets);\n      cm.off(\"focus\", doMatchBrackets)\n      cm.off(\"blur\", clear)\n      clear(cm);\n    }\n    if (val) {\n      cm.state.matchBrackets = typeof val == \"object\" ? val : {};\n      cm.on(\"cursorActivity\", doMatchBrackets);\n      cm.on(\"focus\", doMatchBrackets)\n      cm.on(\"blur\", clear)\n    }\n  });\n\n  CodeMirror.defineExtension(\"matchBrackets\", function() {matchBrackets(this, true);});\n  CodeMirror.defineExtension(\"findMatchingBracket\", function(pos, config, oldConfig){\n    // Backwards-compatibility kludge\n    if (oldConfig || typeof config == \"boolean\") {\n      if (!oldConfig) {\n        config = config ? {strict: true} : null\n      } else {\n        oldConfig.strict = config\n        config = oldConfig\n      }\n    }\n    return findMatchingBracket(this, pos, config)\n  });\n  CodeMirror.defineExtension(\"scanForBracket\", function(pos, dir, style, config){\n    return scanForBracket(this, pos, dir, style, config);\n  });\n});\n\n\n/* ---- extension/edit/matchtags.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../fold/xml-fold\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../fold/xml-fold\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"matchTags\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.off(\"cursorActivity\", doMatchTags);\n      cm.off(\"viewportChange\", maybeUpdateMatch);\n      clear(cm);\n    }\n    if (val) {\n      cm.state.matchBothTags = typeof val == \"object\" && val.bothTags;\n      cm.on(\"cursorActivity\", doMatchTags);\n      cm.on(\"viewportChange\", maybeUpdateMatch);\n      doMatchTags(cm);\n    }\n  });\n\n  function clear(cm) {\n    if (cm.state.tagHit) cm.state.tagHit.clear();\n    if (cm.state.tagOther) cm.state.tagOther.clear();\n    cm.state.tagHit = cm.state.tagOther = null;\n  }\n\n  function doMatchTags(cm) {\n    cm.state.failedTagMatch = false;\n    cm.operation(function() {\n      clear(cm);\n      if (cm.somethingSelected()) return;\n      var cur = cm.getCursor(), range = cm.getViewport();\n      range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);\n      var match = CodeMirror.findMatchingTag(cm, cur, range);\n      if (!match) return;\n      if (cm.state.matchBothTags) {\n        var hit = match.at == \"open\" ? match.open : match.close;\n        if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: \"CodeMirror-matchingtag\"});\n      }\n      var other = match.at == \"close\" ? match.open : match.close;\n      if (other)\n        cm.state.tagOther = cm.markText(other.from, other.to, {className: \"CodeMirror-matchingtag\"});\n      else\n        cm.state.failedTagMatch = true;\n    });\n  }\n\n  function maybeUpdateMatch(cm) {\n    if (cm.state.failedTagMatch) doMatchTags(cm);\n  }\n\n  CodeMirror.commands.toMatchingTag = function(cm) {\n    var found = CodeMirror.findMatchingTag(cm, cm.getCursor());\n    if (found) {\n      var other = found.at == \"close\" ? found.open : found.close;\n      if (other) cm.extendSelection(other.to, other.from);\n    }\n  };\n});\n\n\n/* ---- extension/edit/trailingspace.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  CodeMirror.defineOption(\"showTrailingSpace\", false, function(cm, val, prev) {\n    if (prev == CodeMirror.Init) prev = false;\n    if (prev && !val)\n      cm.removeOverlay(\"trailingspace\");\n    else if (!prev && val)\n      cm.addOverlay({\n        token: function(stream) {\n          for (var l = stream.string.length, i = l; i && /\\s/.test(stream.string.charAt(i - 1)); --i) {}\n          if (i > stream.pos) { stream.pos = i; return null; }\n          stream.pos = l;\n          return \"trailingspace\";\n        },\n        name: \"trailingspace\"\n      });\n  });\n});\n\n\n/* ---- extension/fold/brace-fold.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerHelper(\"fold\", \"brace\", function(cm, start) {\n  var line = start.line, lineText = cm.getLine(line);\n  var tokenType;\n\n  function findOpening(openCh) {\n    for (var at = start.ch, pass = 0;;) {\n      var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);\n      if (found == -1) {\n        if (pass == 1) break;\n        pass = 1;\n        at = lineText.length;\n        continue;\n      }\n      if (pass == 1 && found < start.ch) break;\n      tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));\n      if (!/^(comment|string)/.test(tokenType)) return found + 1;\n      at = found - 1;\n    }\n  }\n\n  var startToken = \"{\", endToken = \"}\", startCh = findOpening(\"{\");\n  if (startCh == null) {\n    startToken = \"[\", endToken = \"]\";\n    startCh = findOpening(\"[\");\n  }\n\n  if (startCh == null) return;\n  var count = 1, lastLine = cm.lastLine(), end, endCh;\n  outer: for (var i = line; i <= lastLine; ++i) {\n    var text = cm.getLine(i), pos = i == line ? startCh : 0;\n    for (;;) {\n      var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);\n      if (nextOpen < 0) nextOpen = text.length;\n      if (nextClose < 0) nextClose = text.length;\n      pos = Math.min(nextOpen, nextClose);\n      if (pos == text.length) break;\n      if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {\n        if (pos == nextOpen) ++count;\n        else if (!--count) { end = i; endCh = pos; break outer; }\n      }\n      ++pos;\n    }\n  }\n  if (end == null || line == end) return;\n  return {from: CodeMirror.Pos(line, startCh),\n          to: CodeMirror.Pos(end, endCh)};\n});\n\nCodeMirror.registerHelper(\"fold\", \"import\", function(cm, start) {\n  function hasImport(line) {\n    if (line < cm.firstLine() || line > cm.lastLine()) return null;\n    var start = cm.getTokenAt(CodeMirror.Pos(line, 1));\n    if (!/\\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));\n    if (start.type != \"keyword\" || start.string != \"import\") return null;\n    // Now find closing semicolon, return its position\n    for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {\n      var text = cm.getLine(i), semi = text.indexOf(\";\");\n      if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};\n    }\n  }\n\n  var startLine = start.line, has = hasImport(startLine), prev;\n  if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))\n    return null;\n  for (var end = has.end;;) {\n    var next = hasImport(end.line + 1);\n    if (next == null) break;\n    end = next.end;\n  }\n  return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};\n});\n\nCodeMirror.registerHelper(\"fold\", \"include\", function(cm, start) {\n  function hasInclude(line) {\n    if (line < cm.firstLine() || line > cm.lastLine()) return null;\n    var start = cm.getTokenAt(CodeMirror.Pos(line, 1));\n    if (!/\\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));\n    if (start.type == \"meta\" && start.string.slice(0, 8) == \"#include\") return start.start + 8;\n  }\n\n  var startLine = start.line, has = hasInclude(startLine);\n  if (has == null || hasInclude(startLine - 1) != null) return null;\n  for (var end = startLine;;) {\n    var next = hasInclude(end + 1);\n    if (next == null) break;\n    ++end;\n  }\n  return {from: CodeMirror.Pos(startLine, has + 1),\n          to: cm.clipPos(CodeMirror.Pos(end))};\n});\n\n});\n\n\n/* ---- extension/fold/comment-fold.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerGlobalHelper(\"fold\", \"comment\", function(mode) {\n  return mode.blockCommentStart && mode.blockCommentEnd;\n}, function(cm, start) {\n  var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;\n  if (!startToken || !endToken) return;\n  var line = start.line, lineText = cm.getLine(line);\n\n  var startCh;\n  for (var at = start.ch, pass = 0;;) {\n    var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);\n    if (found == -1) {\n      if (pass == 1) return;\n      pass = 1;\n      at = lineText.length;\n      continue;\n    }\n    if (pass == 1 && found < start.ch) return;\n    if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&\n        (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||\n         !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {\n      startCh = found + startToken.length;\n      break;\n    }\n    at = found - 1;\n  }\n\n  var depth = 1, lastLine = cm.lastLine(), end, endCh;\n  outer: for (var i = line; i <= lastLine; ++i) {\n    var text = cm.getLine(i), pos = i == line ? startCh : 0;\n    for (;;) {\n      var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);\n      if (nextOpen < 0) nextOpen = text.length;\n      if (nextClose < 0) nextClose = text.length;\n      pos = Math.min(nextOpen, nextClose);\n      if (pos == text.length) break;\n      if (pos == nextOpen) ++depth;\n      else if (!--depth) { end = i; endCh = pos; break outer; }\n      ++pos;\n    }\n  }\n  if (end == null || line == end && endCh == startCh) return;\n  return {from: CodeMirror.Pos(line, startCh),\n          to: CodeMirror.Pos(end, endCh)};\n});\n\n});\n\n\n/* ---- extension/fold/foldcode.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function doFold(cm, pos, options, force) {\n    if (options && options.call) {\n      var finder = options;\n      options = null;\n    } else {\n      var finder = getOption(cm, options, \"rangeFinder\");\n    }\n    if (typeof pos == \"number\") pos = CodeMirror.Pos(pos, 0);\n    var minSize = getOption(cm, options, \"minFoldSize\");\n\n    function getRange(allowFolded) {\n      var range = finder(cm, pos);\n      if (!range || range.to.line - range.from.line < minSize) return null;\n      var marks = cm.findMarksAt(range.from);\n      for (var i = 0; i < marks.length; ++i) {\n        if (marks[i].__isFold && force !== \"fold\") {\n          if (!allowFolded) return null;\n          range.cleared = true;\n          marks[i].clear();\n        }\n      }\n      return range;\n    }\n\n    var range = getRange(true);\n    if (getOption(cm, options, \"scanUp\")) while (!range && pos.line > cm.firstLine()) {\n      pos = CodeMirror.Pos(pos.line - 1, 0);\n      range = getRange(false);\n    }\n    if (!range || range.cleared || force === \"unfold\") return;\n\n    var myWidget = makeWidget(cm, options, range);\n    CodeMirror.on(myWidget, \"mousedown\", function(e) {\n      myRange.clear();\n      CodeMirror.e_preventDefault(e);\n    });\n    var myRange = cm.markText(range.from, range.to, {\n      replacedWith: myWidget,\n      clearOnEnter: getOption(cm, options, \"clearOnEnter\"),\n      __isFold: true\n    });\n    myRange.on(\"clear\", function(from, to) {\n      CodeMirror.signal(cm, \"unfold\", cm, from, to);\n    });\n    CodeMirror.signal(cm, \"fold\", cm, range.from, range.to);\n  }\n\n  function makeWidget(cm, options, range) {\n    var widget = getOption(cm, options, \"widget\");\n\n    if (typeof widget == \"function\") {\n      widget = widget(range.from, range.to);\n    }\n\n    if (typeof widget == \"string\") {\n      var text = document.createTextNode(widget);\n      widget = document.createElement(\"span\");\n      widget.appendChild(text);\n      widget.className = \"CodeMirror-foldmarker\";\n    } else if (widget) {\n      widget = widget.cloneNode(true)\n    }\n    return widget;\n  }\n\n  // Clumsy backwards-compatible interface\n  CodeMirror.newFoldFunction = function(rangeFinder, widget) {\n    return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };\n  };\n\n  // New-style interface\n  CodeMirror.defineExtension(\"foldCode\", function(pos, options, force) {\n    doFold(this, pos, options, force);\n  });\n\n  CodeMirror.defineExtension(\"isFolded\", function(pos) {\n    var marks = this.findMarksAt(pos);\n    for (var i = 0; i < marks.length; ++i)\n      if (marks[i].__isFold) return true;\n  });\n\n  CodeMirror.commands.toggleFold = function(cm) {\n    cm.foldCode(cm.getCursor());\n  };\n  CodeMirror.commands.fold = function(cm) {\n    cm.foldCode(cm.getCursor(), null, \"fold\");\n  };\n  CodeMirror.commands.unfold = function(cm) {\n    cm.foldCode(cm.getCursor(), null, \"unfold\");\n  };\n  CodeMirror.commands.foldAll = function(cm) {\n    cm.operation(function() {\n      for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)\n        cm.foldCode(CodeMirror.Pos(i, 0), null, \"fold\");\n    });\n  };\n  CodeMirror.commands.unfoldAll = function(cm) {\n    cm.operation(function() {\n      for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)\n        cm.foldCode(CodeMirror.Pos(i, 0), null, \"unfold\");\n    });\n  };\n\n  CodeMirror.registerHelper(\"fold\", \"combine\", function() {\n    var funcs = Array.prototype.slice.call(arguments, 0);\n    return function(cm, start) {\n      for (var i = 0; i < funcs.length; ++i) {\n        var found = funcs[i](cm, start);\n        if (found) return found;\n      }\n    };\n  });\n\n  CodeMirror.registerHelper(\"fold\", \"auto\", function(cm, start) {\n    var helpers = cm.getHelpers(start, \"fold\");\n    for (var i = 0; i < helpers.length; i++) {\n      var cur = helpers[i](cm, start);\n      if (cur) return cur;\n    }\n  });\n\n  var defaultOptions = {\n    rangeFinder: CodeMirror.fold.auto,\n    widget: \"\\u2194\",\n    minFoldSize: 0,\n    scanUp: false,\n    clearOnEnter: true\n  };\n\n  CodeMirror.defineOption(\"foldOptions\", null);\n\n  function getOption(cm, options, name) {\n    if (options && options[name] !== undefined)\n      return options[name];\n    var editorOptions = cm.options.foldOptions;\n    if (editorOptions && editorOptions[name] !== undefined)\n      return editorOptions[name];\n    return defaultOptions[name];\n  }\n\n  CodeMirror.defineExtension(\"foldOption\", function(options, name) {\n    return getOption(this, options, name);\n  });\n});\n\n\n/* ---- extension/fold/foldgutter.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./foldcode\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./foldcode\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"foldGutter\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.clearGutter(cm.state.foldGutter.options.gutter);\n      cm.state.foldGutter = null;\n      cm.off(\"gutterClick\", onGutterClick);\n      cm.off(\"changes\", onChange);\n      cm.off(\"viewportChange\", onViewportChange);\n      cm.off(\"fold\", onFold);\n      cm.off(\"unfold\", onFold);\n      cm.off(\"swapDoc\", onChange);\n    }\n    if (val) {\n      cm.state.foldGutter = new State(parseOptions(val));\n      updateInViewport(cm);\n      cm.on(\"gutterClick\", onGutterClick);\n      cm.on(\"changes\", onChange);\n      cm.on(\"viewportChange\", onViewportChange);\n      cm.on(\"fold\", onFold);\n      cm.on(\"unfold\", onFold);\n      cm.on(\"swapDoc\", onChange);\n    }\n  });\n\n  var Pos = CodeMirror.Pos;\n\n  function State(options) {\n    this.options = options;\n    this.from = this.to = 0;\n  }\n\n  function parseOptions(opts) {\n    if (opts === true) opts = {};\n    if (opts.gutter == null) opts.gutter = \"CodeMirror-foldgutter\";\n    if (opts.indicatorOpen == null) opts.indicatorOpen = \"CodeMirror-foldgutter-open\";\n    if (opts.indicatorFolded == null) opts.indicatorFolded = \"CodeMirror-foldgutter-folded\";\n    return opts;\n  }\n\n  function isFolded(cm, line) {\n    var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));\n    for (var i = 0; i < marks.length; ++i) {\n      if (marks[i].__isFold) {\n        var fromPos = marks[i].find(-1);\n        if (fromPos && fromPos.line === line)\n          return marks[i];\n      }\n    }\n  }\n\n  function marker(spec) {\n    if (typeof spec == \"string\") {\n      var elt = document.createElement(\"div\");\n      elt.className = spec + \" CodeMirror-guttermarker-subtle\";\n      return elt;\n    } else {\n      return spec.cloneNode(true);\n    }\n  }\n\n  function updateFoldInfo(cm, from, to) {\n    var opts = cm.state.foldGutter.options, cur = from - 1;\n    var minSize = cm.foldOption(opts, \"minFoldSize\");\n    var func = cm.foldOption(opts, \"rangeFinder\");\n    // we can reuse the built-in indicator element if its className matches the new state\n    var clsFolded = typeof opts.indicatorFolded == \"string\" && classTest(opts.indicatorFolded);\n    var clsOpen = typeof opts.indicatorOpen == \"string\" && classTest(opts.indicatorOpen);\n    cm.eachLine(from, to, function(line) {\n      ++cur;\n      var mark = null;\n      var old = line.gutterMarkers;\n      if (old) old = old[opts.gutter];\n      if (isFolded(cm, cur)) {\n        if (clsFolded && old && clsFolded.test(old.className)) return;\n        mark = marker(opts.indicatorFolded);\n      } else {\n        var pos = Pos(cur, 0);\n        var range = func && func(cm, pos);\n        if (range && range.to.line - range.from.line >= minSize) {\n          if (clsOpen && old && clsOpen.test(old.className)) return;\n          mark = marker(opts.indicatorOpen);\n        }\n      }\n      if (!mark && !old) return;\n      cm.setGutterMarker(line, opts.gutter, mark);\n    });\n  }\n\n  // copied from CodeMirror/src/util/dom.js\n  function classTest(cls) { return new RegExp(\"(^|\\\\s)\" + cls + \"(?:$|\\\\s)\\\\s*\") }\n\n  function updateInViewport(cm) {\n    var vp = cm.getViewport(), state = cm.state.foldGutter;\n    if (!state) return;\n    cm.operation(function() {\n      updateFoldInfo(cm, vp.from, vp.to);\n    });\n    state.from = vp.from; state.to = vp.to;\n  }\n\n  function onGutterClick(cm, line, gutter) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var opts = state.options;\n    if (gutter != opts.gutter) return;\n    var folded = isFolded(cm, line);\n    if (folded) folded.clear();\n    else cm.foldCode(Pos(line, 0), opts);\n  }\n\n  function onChange(cm) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var opts = state.options;\n    state.from = state.to = 0;\n    clearTimeout(state.changeUpdate);\n    state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);\n  }\n\n  function onViewportChange(cm) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var opts = state.options;\n    clearTimeout(state.changeUpdate);\n    state.changeUpdate = setTimeout(function() {\n      var vp = cm.getViewport();\n      if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {\n        updateInViewport(cm);\n      } else {\n        cm.operation(function() {\n          if (vp.from < state.from) {\n            updateFoldInfo(cm, vp.from, state.from);\n            state.from = vp.from;\n          }\n          if (vp.to > state.to) {\n            updateFoldInfo(cm, state.to, vp.to);\n            state.to = vp.to;\n          }\n        });\n      }\n    }, opts.updateViewportTimeSpan || 400);\n  }\n\n  function onFold(cm, from) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var line = from.line;\n    if (line >= state.from && line < state.to)\n      updateFoldInfo(cm, line, line + 1);\n  }\n});\n\n\n/* ---- extension/fold/indent-fold.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nfunction lineIndent(cm, lineNo) {\n  var text = cm.getLine(lineNo)\n  var spaceTo = text.search(/\\S/)\n  if (spaceTo == -1 || /\\bcomment\\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))\n    return -1\n  return CodeMirror.countColumn(text, null, cm.getOption(\"tabSize\"))\n}\n\nCodeMirror.registerHelper(\"fold\", \"indent\", function(cm, start) {\n  var myIndent = lineIndent(cm, start.line)\n  if (myIndent < 0) return\n  var lastLineInFold = null\n\n  // Go through lines until we find a line that definitely doesn't belong in\n  // the block we're folding, or to the end.\n  for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {\n    var indent = lineIndent(cm, i)\n    if (indent == -1) {\n    } else if (indent > myIndent) {\n      // Lines with a greater indent are considered part of the block.\n      lastLineInFold = i;\n    } else {\n      // If this line has non-space, non-comment content, and is\n      // indented less or equal to the start line, it is the start of\n      // another block.\n      break;\n    }\n  }\n  if (lastLineInFold) return {\n    from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),\n    to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)\n  };\n});\n\n});\n\n\n/* ---- extension/fold/markdown-fold.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerHelper(\"fold\", \"markdown\", function(cm, start) {\n  var maxDepth = 100;\n\n  function isHeader(lineNo) {\n    var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));\n    return tokentype && /\\bheader\\b/.test(tokentype);\n  }\n\n  function headerLevel(lineNo, line, nextLine) {\n    var match = line && line.match(/^#+/);\n    if (match && isHeader(lineNo)) return match[0].length;\n    match = nextLine && nextLine.match(/^[=\\-]+\\s*$/);\n    if (match && isHeader(lineNo + 1)) return nextLine[0] == \"=\" ? 1 : 2;\n    return maxDepth;\n  }\n\n  var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);\n  var level = headerLevel(start.line, firstLine, nextLine);\n  if (level === maxDepth) return undefined;\n\n  var lastLineNo = cm.lastLine();\n  var end = start.line, nextNextLine = cm.getLine(end + 2);\n  while (end < lastLineNo) {\n    if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;\n    ++end;\n    nextLine = nextNextLine;\n    nextNextLine = cm.getLine(end + 2);\n  }\n\n  return {\n    from: CodeMirror.Pos(start.line, firstLine.length),\n    to: CodeMirror.Pos(end, cm.getLine(end).length)\n  };\n});\n\n});\n\n\n/* ---- extension/fold/xml-fold.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var Pos = CodeMirror.Pos;\n  function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }\n\n  var nameStartChar = \"A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD\";\n  var nameChar = nameStartChar + \"\\-\\:\\.0-9\\\\u00B7\\\\u0300-\\\\u036F\\\\u203F-\\\\u2040\";\n  var xmlTagStart = new RegExp(\"<(/?)([\" + nameStartChar + \"][\" + nameChar + \"]*)\", \"g\");\n\n  function Iter(cm, line, ch, range) {\n    this.line = line; this.ch = ch;\n    this.cm = cm; this.text = cm.getLine(line);\n    this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();\n    this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();\n  }\n\n  function tagAt(iter, ch) {\n    var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch));\n    return type && /\\btag\\b/.test(type);\n  }\n\n  function nextLine(iter) {\n    if (iter.line >= iter.max) return;\n    iter.ch = 0;\n    iter.text = iter.cm.getLine(++iter.line);\n    return true;\n  }\n  function prevLine(iter) {\n    if (iter.line <= iter.min) return;\n    iter.text = iter.cm.getLine(--iter.line);\n    iter.ch = iter.text.length;\n    return true;\n  }\n\n  function toTagEnd(iter) {\n    for (;;) {\n      var gt = iter.text.indexOf(\">\", iter.ch);\n      if (gt == -1) { if (nextLine(iter)) continue; else return; }\n      if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; }\n      var lastSlash = iter.text.lastIndexOf(\"/\", gt);\n      var selfClose = lastSlash > -1 && !/\\S/.test(iter.text.slice(lastSlash + 1, gt));\n      iter.ch = gt + 1;\n      return selfClose ? \"selfClose\" : \"regular\";\n    }\n  }\n  function toTagStart(iter) {\n    for (;;) {\n      var lt = iter.ch ? iter.text.lastIndexOf(\"<\", iter.ch - 1) : -1;\n      if (lt == -1) { if (prevLine(iter)) continue; else return; }\n      if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; }\n      xmlTagStart.lastIndex = lt;\n      iter.ch = lt;\n      var match = xmlTagStart.exec(iter.text);\n      if (match && match.index == lt) return match;\n    }\n  }\n\n  function toNextTag(iter) {\n    for (;;) {\n      xmlTagStart.lastIndex = iter.ch;\n      var found = xmlTagStart.exec(iter.text);\n      if (!found) { if (nextLine(iter)) continue; else return; }\n      if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; }\n      iter.ch = found.index + found[0].length;\n      return found;\n    }\n  }\n  function toPrevTag(iter) {\n    for (;;) {\n      var gt = iter.ch ? iter.text.lastIndexOf(\">\", iter.ch - 1) : -1;\n      if (gt == -1) { if (prevLine(iter)) continue; else return; }\n      if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; }\n      var lastSlash = iter.text.lastIndexOf(\"/\", gt);\n      var selfClose = lastSlash > -1 && !/\\S/.test(iter.text.slice(lastSlash + 1, gt));\n      iter.ch = gt + 1;\n      return selfClose ? \"selfClose\" : \"regular\";\n    }\n  }\n\n  function findMatchingClose(iter, tag) {\n    var stack = [];\n    for (;;) {\n      var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0);\n      if (!next || !(end = toTagEnd(iter))) return;\n      if (end == \"selfClose\") continue;\n      if (next[1]) { // closing tag\n        for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) {\n          stack.length = i;\n          break;\n        }\n        if (i < 0 && (!tag || tag == next[2])) return {\n          tag: next[2],\n          from: Pos(startLine, startCh),\n          to: Pos(iter.line, iter.ch)\n        };\n      } else { // opening tag\n        stack.push(next[2]);\n      }\n    }\n  }\n  function findMatchingOpen(iter, tag) {\n    var stack = [];\n    for (;;) {\n      var prev = toPrevTag(iter);\n      if (!prev) return;\n      if (prev == \"selfClose\") { toTagStart(iter); continue; }\n      var endLine = iter.line, endCh = iter.ch;\n      var start = toTagStart(iter);\n      if (!start) return;\n      if (start[1]) { // closing tag\n        stack.push(start[2]);\n      } else { // opening tag\n        for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) {\n          stack.length = i;\n          break;\n        }\n        if (i < 0 && (!tag || tag == start[2])) return {\n          tag: start[2],\n          from: Pos(iter.line, iter.ch),\n          to: Pos(endLine, endCh)\n        };\n      }\n    }\n  }\n\n  CodeMirror.registerHelper(\"fold\", \"xml\", function(cm, start) {\n    var iter = new Iter(cm, start.line, 0);\n    for (;;) {\n      var openTag = toNextTag(iter)\n      if (!openTag || iter.line != start.line) return\n      var end = toTagEnd(iter)\n      if (!end) return\n      if (!openTag[1] && end != \"selfClose\") {\n        var startPos = Pos(iter.line, iter.ch);\n        var endPos = findMatchingClose(iter, openTag[2]);\n        return endPos && cmp(endPos.from, startPos) > 0 ? {from: startPos, to: endPos.from} : null\n      }\n    }\n  });\n  CodeMirror.findMatchingTag = function(cm, pos, range) {\n    var iter = new Iter(cm, pos.line, pos.ch, range);\n    if (iter.text.indexOf(\">\") == -1 && iter.text.indexOf(\"<\") == -1) return;\n    var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);\n    var start = end && toTagStart(iter);\n    if (!end || !start || cmp(iter, pos) > 0) return;\n    var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};\n    if (end == \"selfClose\") return {open: here, close: null, at: \"open\"};\n\n    if (start[1]) { // closing tag\n      return {open: findMatchingOpen(iter, start[2]), close: here, at: \"close\"};\n    } else { // opening tag\n      iter = new Iter(cm, to.line, to.ch, range);\n      return {open: here, close: findMatchingClose(iter, start[2]), at: \"open\"};\n    }\n  };\n\n  CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {\n    var iter = new Iter(cm, pos.line, pos.ch, range);\n    for (;;) {\n      var open = findMatchingOpen(iter, tag);\n      if (!open) break;\n      var forward = new Iter(cm, pos.line, pos.ch, range);\n      var close = findMatchingClose(forward, open.tag);\n      if (close) return {open: open, close: close};\n    }\n  };\n\n  // Used by addon/edit/closetag.js\n  CodeMirror.scanForClosingTag = function(cm, pos, name, end) {\n    var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);\n    return findMatchingClose(iter, name);\n  };\n});\n\n\n/* ---- extension/hint/anyword-hint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var WORD = /[\\w$]+/, RANGE = 500;\n\n  CodeMirror.registerHelper(\"hint\", \"anyword\", function(editor, options) {\n    var word = options && options.word || WORD;\n    var range = options && options.range || RANGE;\n    var cur = editor.getCursor(), curLine = editor.getLine(cur.line);\n    var end = cur.ch, start = end;\n    while (start && word.test(curLine.charAt(start - 1))) --start;\n    var curWord = start != end && curLine.slice(start, end);\n\n    var list = options && options.list || [], seen = {};\n    var re = new RegExp(word.source, \"g\");\n    for (var dir = -1; dir <= 1; dir += 2) {\n      var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;\n      for (; line != endLine; line += dir) {\n        var text = editor.getLine(line), m;\n        while (m = re.exec(text)) {\n          if (line == cur.line && m[0] === curWord) continue;\n          if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {\n            seen[m[0]] = true;\n            list.push(m[0]);\n          }\n        }\n      }\n    }\n    return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};\n  });\n});\n\n\n/* ---- extension/hint/html-hint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./xml-hint\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./xml-hint\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var langs = \"ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu\".split(\" \");\n  var targets = [\"_blank\", \"_self\", \"_top\", \"_parent\"];\n  var charsets = [\"ascii\", \"utf-8\", \"utf-16\", \"latin1\", \"latin1\"];\n  var methods = [\"get\", \"post\", \"put\", \"delete\"];\n  var encs = [\"application/x-www-form-urlencoded\", \"multipart/form-data\", \"text/plain\"];\n  var media = [\"all\", \"screen\", \"print\", \"embossed\", \"braille\", \"handheld\", \"print\", \"projection\", \"screen\", \"tty\", \"tv\", \"speech\",\n               \"3d-glasses\", \"resolution [>][<][=] [X]\", \"device-aspect-ratio: X/Y\", \"orientation:portrait\",\n               \"orientation:landscape\", \"device-height: [X]\", \"device-width: [X]\"];\n  var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags\n\n  var data = {\n    a: {\n      attrs: {\n        href: null, ping: null, type: null,\n        media: media,\n        target: targets,\n        hreflang: langs\n      }\n    },\n    abbr: s,\n    acronym: s,\n    address: s,\n    applet: s,\n    area: {\n      attrs: {\n        alt: null, coords: null, href: null, target: null, ping: null,\n        media: media, hreflang: langs, type: null,\n        shape: [\"default\", \"rect\", \"circle\", \"poly\"]\n      }\n    },\n    article: s,\n    aside: s,\n    audio: {\n      attrs: {\n        src: null, mediagroup: null,\n        crossorigin: [\"anonymous\", \"use-credentials\"],\n        preload: [\"none\", \"metadata\", \"auto\"],\n        autoplay: [\"\", \"autoplay\"],\n        loop: [\"\", \"loop\"],\n        controls: [\"\", \"controls\"]\n      }\n    },\n    b: s,\n    base: { attrs: { href: null, target: targets } },\n    basefont: s,\n    bdi: s,\n    bdo: s,\n    big: s,\n    blockquote: { attrs: { cite: null } },\n    body: s,\n    br: s,\n    button: {\n      attrs: {\n        form: null, formaction: null, name: null, value: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"autofocus\"],\n        formenctype: encs,\n        formmethod: methods,\n        formnovalidate: [\"\", \"novalidate\"],\n        formtarget: targets,\n        type: [\"submit\", \"reset\", \"button\"]\n      }\n    },\n    canvas: { attrs: { width: null, height: null } },\n    caption: s,\n    center: s,\n    cite: s,\n    code: s,\n    col: { attrs: { span: null } },\n    colgroup: { attrs: { span: null } },\n    command: {\n      attrs: {\n        type: [\"command\", \"checkbox\", \"radio\"],\n        label: null, icon: null, radiogroup: null, command: null, title: null,\n        disabled: [\"\", \"disabled\"],\n        checked: [\"\", \"checked\"]\n      }\n    },\n    data: { attrs: { value: null } },\n    datagrid: { attrs: { disabled: [\"\", \"disabled\"], multiple: [\"\", \"multiple\"] } },\n    datalist: { attrs: { data: null } },\n    dd: s,\n    del: { attrs: { cite: null, datetime: null } },\n    details: { attrs: { open: [\"\", \"open\"] } },\n    dfn: s,\n    dir: s,\n    div: s,\n    dl: s,\n    dt: s,\n    em: s,\n    embed: { attrs: { src: null, type: null, width: null, height: null } },\n    eventsource: { attrs: { src: null } },\n    fieldset: { attrs: { disabled: [\"\", \"disabled\"], form: null, name: null } },\n    figcaption: s,\n    figure: s,\n    font: s,\n    footer: s,\n    form: {\n      attrs: {\n        action: null, name: null,\n        \"accept-charset\": charsets,\n        autocomplete: [\"on\", \"off\"],\n        enctype: encs,\n        method: methods,\n        novalidate: [\"\", \"novalidate\"],\n        target: targets\n      }\n    },\n    frame: s,\n    frameset: s,\n    h1: s, h2: s, h3: s, h4: s, h5: s, h6: s,\n    head: {\n      attrs: {},\n      children: [\"title\", \"base\", \"link\", \"style\", \"meta\", \"script\", \"noscript\", \"command\"]\n    },\n    header: s,\n    hgroup: s,\n    hr: s,\n    html: {\n      attrs: { manifest: null },\n      children: [\"head\", \"body\"]\n    },\n    i: s,\n    iframe: {\n      attrs: {\n        src: null, srcdoc: null, name: null, width: null, height: null,\n        sandbox: [\"allow-top-navigation\", \"allow-same-origin\", \"allow-forms\", \"allow-scripts\"],\n        seamless: [\"\", \"seamless\"]\n      }\n    },\n    img: {\n      attrs: {\n        alt: null, src: null, ismap: null, usemap: null, width: null, height: null,\n        crossorigin: [\"anonymous\", \"use-credentials\"]\n      }\n    },\n    input: {\n      attrs: {\n        alt: null, dirname: null, form: null, formaction: null,\n        height: null, list: null, max: null, maxlength: null, min: null,\n        name: null, pattern: null, placeholder: null, size: null, src: null,\n        step: null, value: null, width: null,\n        accept: [\"audio/*\", \"video/*\", \"image/*\"],\n        autocomplete: [\"on\", \"off\"],\n        autofocus: [\"\", \"autofocus\"],\n        checked: [\"\", \"checked\"],\n        disabled: [\"\", \"disabled\"],\n        formenctype: encs,\n        formmethod: methods,\n        formnovalidate: [\"\", \"novalidate\"],\n        formtarget: targets,\n        multiple: [\"\", \"multiple\"],\n        readonly: [\"\", \"readonly\"],\n        required: [\"\", \"required\"],\n        type: [\"hidden\", \"text\", \"search\", \"tel\", \"url\", \"email\", \"password\", \"datetime\", \"date\", \"month\",\n               \"week\", \"time\", \"datetime-local\", \"number\", \"range\", \"color\", \"checkbox\", \"radio\",\n               \"file\", \"submit\", \"image\", \"reset\", \"button\"]\n      }\n    },\n    ins: { attrs: { cite: null, datetime: null } },\n    kbd: s,\n    keygen: {\n      attrs: {\n        challenge: null, form: null, name: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"disabled\"],\n        keytype: [\"RSA\"]\n      }\n    },\n    label: { attrs: { \"for\": null, form: null } },\n    legend: s,\n    li: { attrs: { value: null } },\n    link: {\n      attrs: {\n        href: null, type: null,\n        hreflang: langs,\n        media: media,\n        sizes: [\"all\", \"16x16\", \"16x16 32x32\", \"16x16 32x32 64x64\"]\n      }\n    },\n    map: { attrs: { name: null } },\n    mark: s,\n    menu: { attrs: { label: null, type: [\"list\", \"context\", \"toolbar\"] } },\n    meta: {\n      attrs: {\n        content: null,\n        charset: charsets,\n        name: [\"viewport\", \"application-name\", \"author\", \"description\", \"generator\", \"keywords\"],\n        \"http-equiv\": [\"content-language\", \"content-type\", \"default-style\", \"refresh\"]\n      }\n    },\n    meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } },\n    nav: s,\n    noframes: s,\n    noscript: s,\n    object: {\n      attrs: {\n        data: null, type: null, name: null, usemap: null, form: null, width: null, height: null,\n        typemustmatch: [\"\", \"typemustmatch\"]\n      }\n    },\n    ol: { attrs: { reversed: [\"\", \"reversed\"], start: null, type: [\"1\", \"a\", \"A\", \"i\", \"I\"] } },\n    optgroup: { attrs: { disabled: [\"\", \"disabled\"], label: null } },\n    option: { attrs: { disabled: [\"\", \"disabled\"], label: null, selected: [\"\", \"selected\"], value: null } },\n    output: { attrs: { \"for\": null, form: null, name: null } },\n    p: s,\n    param: { attrs: { name: null, value: null } },\n    pre: s,\n    progress: { attrs: { value: null, max: null } },\n    q: { attrs: { cite: null } },\n    rp: s,\n    rt: s,\n    ruby: s,\n    s: s,\n    samp: s,\n    script: {\n      attrs: {\n        type: [\"text/javascript\"],\n        src: null,\n        async: [\"\", \"async\"],\n        defer: [\"\", \"defer\"],\n        charset: charsets\n      }\n    },\n    section: s,\n    select: {\n      attrs: {\n        form: null, name: null, size: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"disabled\"],\n        multiple: [\"\", \"multiple\"]\n      }\n    },\n    small: s,\n    source: { attrs: { src: null, type: null, media: null } },\n    span: s,\n    strike: s,\n    strong: s,\n    style: {\n      attrs: {\n        type: [\"text/css\"],\n        media: media,\n        scoped: null\n      }\n    },\n    sub: s,\n    summary: s,\n    sup: s,\n    table: s,\n    tbody: s,\n    td: { attrs: { colspan: null, rowspan: null, headers: null } },\n    textarea: {\n      attrs: {\n        dirname: null, form: null, maxlength: null, name: null, placeholder: null,\n        rows: null, cols: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"disabled\"],\n        readonly: [\"\", \"readonly\"],\n        required: [\"\", \"required\"],\n        wrap: [\"soft\", \"hard\"]\n      }\n    },\n    tfoot: s,\n    th: { attrs: { colspan: null, rowspan: null, headers: null, scope: [\"row\", \"col\", \"rowgroup\", \"colgroup\"] } },\n    thead: s,\n    time: { attrs: { datetime: null } },\n    title: s,\n    tr: s,\n    track: {\n      attrs: {\n        src: null, label: null, \"default\": null,\n        kind: [\"subtitles\", \"captions\", \"descriptions\", \"chapters\", \"metadata\"],\n        srclang: langs\n      }\n    },\n    tt: s,\n    u: s,\n    ul: s,\n    \"var\": s,\n    video: {\n      attrs: {\n        src: null, poster: null, width: null, height: null,\n        crossorigin: [\"anonymous\", \"use-credentials\"],\n        preload: [\"auto\", \"metadata\", \"none\"],\n        autoplay: [\"\", \"autoplay\"],\n        mediagroup: [\"movie\"],\n        muted: [\"\", \"muted\"],\n        controls: [\"\", \"controls\"]\n      }\n    },\n    wbr: s\n  };\n\n  var globalAttrs = {\n    accesskey: [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"],\n    \"class\": null,\n    contenteditable: [\"true\", \"false\"],\n    contextmenu: null,\n    dir: [\"ltr\", \"rtl\", \"auto\"],\n    draggable: [\"true\", \"false\", \"auto\"],\n    dropzone: [\"copy\", \"move\", \"link\", \"string:\", \"file:\"],\n    hidden: [\"hidden\"],\n    id: null,\n    inert: [\"inert\"],\n    itemid: null,\n    itemprop: null,\n    itemref: null,\n    itemscope: [\"itemscope\"],\n    itemtype: null,\n    lang: [\"en\", \"es\"],\n    spellcheck: [\"true\", \"false\"],\n    autocorrect: [\"true\", \"false\"],\n    autocapitalize: [\"true\", \"false\"],\n    style: null,\n    tabindex: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"],\n    title: null,\n    translate: [\"yes\", \"no\"],\n    onclick: null,\n    rel: [\"stylesheet\", \"alternate\", \"author\", \"bookmark\", \"help\", \"license\", \"next\", \"nofollow\", \"noreferrer\", \"prefetch\", \"prev\", \"search\", \"tag\"]\n  };\n  function populate(obj) {\n    for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr))\n      obj.attrs[attr] = globalAttrs[attr];\n  }\n\n  populate(s);\n  for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s)\n    populate(data[tag]);\n\n  CodeMirror.htmlSchema = data;\n  function htmlHint(cm, options) {\n    var local = {schemaInfo: data};\n    if (options) for (var opt in options) local[opt] = options[opt];\n    return CodeMirror.hint.xml(cm, local);\n  }\n  CodeMirror.registerHelper(\"hint\", \"html\", htmlHint);\n});\n\n\n/* ---- extension/hint/show-hint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var HINT_ELEMENT_CLASS        = \"CodeMirror-hint\";\n  var ACTIVE_HINT_ELEMENT_CLASS = \"CodeMirror-hint-active\";\n\n  // This is the old interface, kept around for now to stay\n  // backwards-compatible.\n  CodeMirror.showHint = function(cm, getHints, options) {\n    if (!getHints) return cm.showHint(options);\n    if (options && options.async) getHints.async = true;\n    var newOpts = {hint: getHints};\n    if (options) for (var prop in options) newOpts[prop] = options[prop];\n    return cm.showHint(newOpts);\n  };\n\n  CodeMirror.defineExtension(\"showHint\", function(options) {\n    options = parseOptions(this, this.getCursor(\"start\"), options);\n    var selections = this.listSelections()\n    if (selections.length > 1) return;\n    // By default, don't allow completion when something is selected.\n    // A hint function can have a `supportsSelection` property to\n    // indicate that it can handle selections.\n    if (this.somethingSelected()) {\n      if (!options.hint.supportsSelection) return;\n      // Don't try with cross-line selections\n      for (var i = 0; i < selections.length; i++)\n        if (selections[i].head.line != selections[i].anchor.line) return;\n    }\n\n    if (this.state.completionActive) this.state.completionActive.close();\n    var completion = this.state.completionActive = new Completion(this, options);\n    if (!completion.options.hint) return;\n\n    CodeMirror.signal(this, \"startCompletion\", this);\n    completion.update(true);\n  });\n\n  CodeMirror.defineExtension(\"closeHint\", function() {\n    if (this.state.completionActive) this.state.completionActive.close()\n  })\n\n  function Completion(cm, options) {\n    this.cm = cm;\n    this.options = options;\n    this.widget = null;\n    this.debounce = 0;\n    this.tick = 0;\n    this.startPos = this.cm.getCursor(\"start\");\n    this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;\n\n    var self = this;\n    cm.on(\"cursorActivity\", this.activityFunc = function() { self.cursorActivity(); });\n  }\n\n  var requestAnimationFrame = window.requestAnimationFrame || function(fn) {\n    return setTimeout(fn, 1000/60);\n  };\n  var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;\n\n  Completion.prototype = {\n    close: function() {\n      if (!this.active()) return;\n      this.cm.state.completionActive = null;\n      this.tick = null;\n      this.cm.off(\"cursorActivity\", this.activityFunc);\n\n      if (this.widget && this.data) CodeMirror.signal(this.data, \"close\");\n      if (this.widget) this.widget.close();\n      CodeMirror.signal(this.cm, \"endCompletion\", this.cm);\n    },\n\n    active: function() {\n      return this.cm.state.completionActive == this;\n    },\n\n    pick: function(data, i) {\n      var completion = data.list[i], self = this;\n      this.cm.operation(function() {\n        if (completion.hint)\n          completion.hint(self.cm, data, completion);\n        else\n          self.cm.replaceRange(getText(completion), completion.from || data.from,\n                               completion.to || data.to, \"complete\");\n        CodeMirror.signal(data, \"pick\", completion);\n        self.cm.scrollIntoView();\n      })\n      this.close();\n    },\n\n    cursorActivity: function() {\n      if (this.debounce) {\n        cancelAnimationFrame(this.debounce);\n        this.debounce = 0;\n      }\n\n      var identStart = this.startPos;\n      if(this.data) {\n        identStart = this.data.from;\n      }\n\n      var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);\n      if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||\n          pos.ch < identStart.ch || this.cm.somethingSelected() ||\n          (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {\n        this.close();\n      } else {\n        var self = this;\n        this.debounce = requestAnimationFrame(function() {self.update();});\n        if (this.widget) this.widget.disable();\n      }\n    },\n\n    update: function(first) {\n      if (this.tick == null) return\n      var self = this, myTick = ++this.tick\n      fetchHints(this.options.hint, this.cm, this.options, function(data) {\n        if (self.tick == myTick) self.finishUpdate(data, first)\n      })\n    },\n\n    finishUpdate: function(data, first) {\n      if (this.data) CodeMirror.signal(this.data, \"update\");\n\n      var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);\n      if (this.widget) this.widget.close();\n\n      this.data = data;\n\n      if (data && data.list.length) {\n        if (picked && data.list.length == 1) {\n          this.pick(data, 0);\n        } else {\n          this.widget = new Widget(this, data);\n          CodeMirror.signal(data, \"shown\");\n        }\n      }\n    }\n  };\n\n  function parseOptions(cm, pos, options) {\n    var editor = cm.options.hintOptions;\n    var out = {};\n    for (var prop in defaultOptions) out[prop] = defaultOptions[prop];\n    if (editor) for (var prop in editor)\n      if (editor[prop] !== undefined) out[prop] = editor[prop];\n    if (options) for (var prop in options)\n      if (options[prop] !== undefined) out[prop] = options[prop];\n    if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)\n    return out;\n  }\n\n  function getText(completion) {\n    if (typeof completion == \"string\") return completion;\n    else return completion.text;\n  }\n\n  function buildKeyMap(completion, handle) {\n    var baseMap = {\n      Up: function() {handle.moveFocus(-1);},\n      Down: function() {handle.moveFocus(1);},\n      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},\n      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},\n      Home: function() {handle.setFocus(0);},\n      End: function() {handle.setFocus(handle.length - 1);},\n      Enter: handle.pick,\n      Tab: handle.pick,\n      Esc: handle.close\n    };\n\n    var mac = /Mac/.test(navigator.platform);\n\n    if (mac) {\n      baseMap[\"Ctrl-P\"] = function() {handle.moveFocus(-1);};\n      baseMap[\"Ctrl-N\"] = function() {handle.moveFocus(1);};\n    }\n\n    var custom = completion.options.customKeys;\n    var ourMap = custom ? {} : baseMap;\n    function addBinding(key, val) {\n      var bound;\n      if (typeof val != \"string\")\n        bound = function(cm) { return val(cm, handle); };\n      // This mechanism is deprecated\n      else if (baseMap.hasOwnProperty(val))\n        bound = baseMap[val];\n      else\n        bound = val;\n      ourMap[key] = bound;\n    }\n    if (custom)\n      for (var key in custom) if (custom.hasOwnProperty(key))\n        addBinding(key, custom[key]);\n    var extra = completion.options.extraKeys;\n    if (extra)\n      for (var key in extra) if (extra.hasOwnProperty(key))\n        addBinding(key, extra[key]);\n    return ourMap;\n  }\n\n  function getHintElement(hintsElement, el) {\n    while (el && el != hintsElement) {\n      if (el.nodeName.toUpperCase() === \"LI\" && el.parentNode == hintsElement) return el;\n      el = el.parentNode;\n    }\n  }\n\n  function Widget(completion, data) {\n    this.completion = completion;\n    this.data = data;\n    this.picked = false;\n    var widget = this, cm = completion.cm;\n    var ownerDocument = cm.getInputField().ownerDocument;\n    var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;\n\n    var hints = this.hints = ownerDocument.createElement(\"ul\");\n    var theme = completion.cm.options.theme;\n    hints.className = \"CodeMirror-hints \" + theme;\n    this.selectedHint = data.selectedHint || 0;\n\n    var completions = data.list;\n    for (var i = 0; i < completions.length; ++i) {\n      var elt = hints.appendChild(ownerDocument.createElement(\"li\")), cur = completions[i];\n      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? \"\" : \" \" + ACTIVE_HINT_ELEMENT_CLASS);\n      if (cur.className != null) className = cur.className + \" \" + className;\n      elt.className = className;\n      if (cur.render) cur.render(elt, data, cur);\n      else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));\n      elt.hintId = i;\n    }\n\n    var container = completion.options.container || ownerDocument.body;\n    var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);\n    var left = pos.left, top = pos.bottom, below = true;\n    var offsetLeft = 0, offsetTop = 0;\n    if (container !== ownerDocument.body) {\n      // We offset the cursor position because left and top are relative to the offsetParent's top left corner.\n      var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;\n      var offsetParent = isContainerPositioned ? container : container.offsetParent;\n      var offsetParentPosition = offsetParent.getBoundingClientRect();\n      var bodyPosition = ownerDocument.body.getBoundingClientRect();\n      offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);\n      offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);\n    }\n    hints.style.left = (left - offsetLeft) + \"px\";\n    hints.style.top = (top - offsetTop) + \"px\";\n\n    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.\n    var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);\n    var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);\n    container.appendChild(hints);\n    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;\n    var scrolls = hints.scrollHeight > hints.clientHeight + 1\n    var startScroll = cm.getScrollInfo();\n\n    if (overlapY > 0) {\n      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);\n      if (curTop - height > 0) { // Fits above cursor\n        hints.style.top = (top = pos.top - height - offsetTop) + \"px\";\n        below = false;\n      } else if (height > winH) {\n        hints.style.height = (winH - 5) + \"px\";\n        hints.style.top = (top = pos.bottom - box.top - offsetTop) + \"px\";\n        var cursor = cm.getCursor();\n        if (data.from.ch != cursor.ch) {\n          pos = cm.cursorCoords(cursor);\n          hints.style.left = (left = pos.left - offsetLeft) + \"px\";\n          box = hints.getBoundingClientRect();\n        }\n      }\n    }\n    var overlapX = box.right - winW;\n    if (overlapX > 0) {\n      if (box.right - box.left > winW) {\n        hints.style.width = (winW - 5) + \"px\";\n        overlapX -= (box.right - box.left) - winW;\n      }\n      hints.style.left = (left = pos.left - overlapX - offsetLeft) + \"px\";\n    }\n    if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)\n      node.style.paddingRight = cm.display.nativeBarWidth + \"px\"\n\n    cm.addKeyMap(this.keyMap = buildKeyMap(completion, {\n      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },\n      setFocus: function(n) { widget.changeActive(n); },\n      menuSize: function() { return widget.screenAmount(); },\n      length: completions.length,\n      close: function() { completion.close(); },\n      pick: function() { widget.pick(); },\n      data: data\n    }));\n\n    if (completion.options.closeOnUnfocus) {\n      var closingOnBlur;\n      cm.on(\"blur\", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });\n      cm.on(\"focus\", this.onFocus = function() { clearTimeout(closingOnBlur); });\n    }\n\n    cm.on(\"scroll\", this.onScroll = function() {\n      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();\n      var newTop = top + startScroll.top - curScroll.top;\n      var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);\n      if (!below) point += hints.offsetHeight;\n      if (point <= editor.top || point >= editor.bottom) return completion.close();\n      hints.style.top = newTop + \"px\";\n      hints.style.left = (left + startScroll.left - curScroll.left) + \"px\";\n    });\n\n    CodeMirror.on(hints, \"dblclick\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}\n    });\n\n    CodeMirror.on(hints, \"click\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {\n        widget.changeActive(t.hintId);\n        if (completion.options.completeOnSingleClick) widget.pick();\n      }\n    });\n\n    CodeMirror.on(hints, \"mousedown\", function() {\n      setTimeout(function(){cm.focus();}, 20);\n    });\n    this.scrollToActive()\n\n    CodeMirror.signal(data, \"select\", completions[this.selectedHint], hints.childNodes[this.selectedHint]);\n    return true;\n  }\n\n  Widget.prototype = {\n    close: function() {\n      if (this.completion.widget != this) return;\n      this.completion.widget = null;\n      this.hints.parentNode.removeChild(this.hints);\n      this.completion.cm.removeKeyMap(this.keyMap);\n\n      var cm = this.completion.cm;\n      if (this.completion.options.closeOnUnfocus) {\n        cm.off(\"blur\", this.onBlur);\n        cm.off(\"focus\", this.onFocus);\n      }\n      cm.off(\"scroll\", this.onScroll);\n    },\n\n    disable: function() {\n      this.completion.cm.removeKeyMap(this.keyMap);\n      var widget = this;\n      this.keyMap = {Enter: function() { widget.picked = true; }};\n      this.completion.cm.addKeyMap(this.keyMap);\n    },\n\n    pick: function() {\n      this.completion.pick(this.data, this.selectedHint);\n    },\n\n    changeActive: function(i, avoidWrap) {\n      if (i >= this.data.list.length)\n        i = avoidWrap ? this.data.list.length - 1 : 0;\n      else if (i < 0)\n        i = avoidWrap ? 0  : this.data.list.length - 1;\n      if (this.selectedHint == i) return;\n      var node = this.hints.childNodes[this.selectedHint];\n      if (node) node.className = node.className.replace(\" \" + ACTIVE_HINT_ELEMENT_CLASS, \"\");\n      node = this.hints.childNodes[this.selectedHint = i];\n      node.className += \" \" + ACTIVE_HINT_ELEMENT_CLASS;\n      this.scrollToActive()\n      CodeMirror.signal(this.data, \"select\", this.data.list[this.selectedHint], node);\n    },\n\n    scrollToActive: function() {\n      var margin = this.completion.options.scrollMargin || 0;\n      var node1 = this.hints.childNodes[Math.max(0, this.selectedHint - margin)];\n      var node2 = this.hints.childNodes[Math.min(this.data.list.length - 1, this.selectedHint + margin)];\n      var firstNode = this.hints.firstChild;\n      if (node1.offsetTop < this.hints.scrollTop)\n        this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;\n      else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)\n        this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;\n    },\n\n    screenAmount: function() {\n      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;\n    }\n  };\n\n  function applicableHelpers(cm, helpers) {\n    if (!cm.somethingSelected()) return helpers\n    var result = []\n    for (var i = 0; i < helpers.length; i++)\n      if (helpers[i].supportsSelection) result.push(helpers[i])\n    return result\n  }\n\n  function fetchHints(hint, cm, options, callback) {\n    if (hint.async) {\n      hint(cm, callback, options)\n    } else {\n      var result = hint(cm, options)\n      if (result && result.then) result.then(callback)\n      else callback(result)\n    }\n  }\n\n  function resolveAutoHints(cm, pos) {\n    var helpers = cm.getHelpers(pos, \"hint\"), words\n    if (helpers.length) {\n      var resolved = function(cm, callback, options) {\n        var app = applicableHelpers(cm, helpers);\n        function run(i) {\n          if (i == app.length) return callback(null)\n          fetchHints(app[i], cm, options, function(result) {\n            if (result && result.list.length > 0) callback(result)\n            else run(i + 1)\n          })\n        }\n        run(0)\n      }\n      resolved.async = true\n      resolved.supportsSelection = true\n      return resolved\n    } else if (words = cm.getHelper(cm.getCursor(), \"hintWords\")) {\n      return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }\n    } else if (CodeMirror.hint.anyword) {\n      return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }\n    } else {\n      return function() {}\n    }\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"auto\", {\n    resolve: resolveAutoHints\n  });\n\n  CodeMirror.registerHelper(\"hint\", \"fromList\", function(cm, options) {\n    var cur = cm.getCursor(), token = cm.getTokenAt(cur)\n    var term, from = CodeMirror.Pos(cur.line, token.start), to = cur\n    if (token.start < cur.ch && /\\w/.test(token.string.charAt(cur.ch - token.start - 1))) {\n      term = token.string.substr(0, cur.ch - token.start)\n    } else {\n      term = \"\"\n      from = cur\n    }\n    var found = [];\n    for (var i = 0; i < options.words.length; i++) {\n      var word = options.words[i];\n      if (word.slice(0, term.length) == term)\n        found.push(word);\n    }\n\n    if (found.length) return {list: found, from: from, to: to};\n  });\n\n  CodeMirror.commands.autocomplete = CodeMirror.showHint;\n\n  var defaultOptions = {\n    hint: CodeMirror.hint.auto,\n    completeSingle: true,\n    alignWithWord: true,\n    closeCharacters: /[\\s()\\[\\]{};:>,]/,\n    closeOnUnfocus: true,\n    completeOnSingleClick: true,\n    container: null,\n    customKeys: null,\n    extraKeys: null\n  };\n\n  CodeMirror.defineOption(\"hintOptions\", null);\n});\n\n\n/* ---- extension/hint/sql-hint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../../mode/sql/sql\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../../mode/sql/sql\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var tables;\n  var defaultTable;\n  var keywords;\n  var identifierQuote;\n  var CONS = {\n    QUERY_DIV: \";\",\n    ALIAS_KEYWORD: \"AS\"\n  };\n  var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;\n\n  function isArray(val) { return Object.prototype.toString.call(val) == \"[object Array]\" }\n\n  function getKeywords(editor) {\n    var mode = editor.doc.modeOption;\n    if (mode === \"sql\") mode = \"text/x-sql\";\n    return CodeMirror.resolveMode(mode).keywords;\n  }\n\n  function getIdentifierQuote(editor) {\n    var mode = editor.doc.modeOption;\n    if (mode === \"sql\") mode = \"text/x-sql\";\n    return CodeMirror.resolveMode(mode).identifierQuote || \"`\";\n  }\n\n  function getText(item) {\n    return typeof item == \"string\" ? item : item.text;\n  }\n\n  function wrapTable(name, value) {\n    if (isArray(value)) value = {columns: value}\n    if (!value.text) value.text = name\n    return value\n  }\n\n  function parseTables(input) {\n    var result = {}\n    if (isArray(input)) {\n      for (var i = input.length - 1; i >= 0; i--) {\n        var item = input[i]\n        result[getText(item).toUpperCase()] = wrapTable(getText(item), item)\n      }\n    } else if (input) {\n      for (var name in input)\n        result[name.toUpperCase()] = wrapTable(name, input[name])\n    }\n    return result\n  }\n\n  function getTable(name) {\n    return tables[name.toUpperCase()]\n  }\n\n  function shallowClone(object) {\n    var result = {};\n    for (var key in object) if (object.hasOwnProperty(key))\n      result[key] = object[key];\n    return result;\n  }\n\n  function match(string, word) {\n    var len = string.length;\n    var sub = getText(word).substr(0, len);\n    return string.toUpperCase() === sub.toUpperCase();\n  }\n\n  function addMatches(result, search, wordlist, formatter) {\n    if (isArray(wordlist)) {\n      for (var i = 0; i < wordlist.length; i++)\n        if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))\n    } else {\n      for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {\n        var val = wordlist[word]\n        if (!val || val === true)\n          val = word\n        else\n          val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text\n        if (match(search, val)) result.push(formatter(val))\n      }\n    }\n  }\n\n  function cleanName(name) {\n    // Get rid name from identifierQuote and preceding dot(.)\n    if (name.charAt(0) == \".\") {\n      name = name.substr(1);\n    }\n    // replace doublicated identifierQuotes with single identifierQuotes\n    // and remove single identifierQuotes\n    var nameParts = name.split(identifierQuote+identifierQuote);\n    for (var i = 0; i < nameParts.length; i++)\n      nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,\"g\"), \"\");\n    return nameParts.join(identifierQuote);\n  }\n\n  function insertIdentifierQuotes(name) {\n    var nameParts = getText(name).split(\".\");\n    for (var i = 0; i < nameParts.length; i++)\n      nameParts[i] = identifierQuote +\n        // doublicate identifierQuotes\n        nameParts[i].replace(new RegExp(identifierQuote,\"g\"), identifierQuote+identifierQuote) +\n        identifierQuote;\n    var escaped = nameParts.join(\".\");\n    if (typeof name == \"string\") return escaped;\n    name = shallowClone(name);\n    name.text = escaped;\n    return name;\n  }\n\n  function nameCompletion(cur, token, result, editor) {\n    // Try to complete table, column names and return start position of completion\n    var useIdentifierQuotes = false;\n    var nameParts = [];\n    var start = token.start;\n    var cont = true;\n    while (cont) {\n      cont = (token.string.charAt(0) == \".\");\n      useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);\n\n      start = token.start;\n      nameParts.unshift(cleanName(token.string));\n\n      token = editor.getTokenAt(Pos(cur.line, token.start));\n      if (token.string == \".\") {\n        cont = true;\n        token = editor.getTokenAt(Pos(cur.line, token.start));\n      }\n    }\n\n    // Try to complete table names\n    var string = nameParts.join(\".\");\n    addMatches(result, string, tables, function(w) {\n      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;\n    });\n\n    // Try to complete columns from defaultTable\n    addMatches(result, string, defaultTable, function(w) {\n      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;\n    });\n\n    // Try to complete columns\n    string = nameParts.pop();\n    var table = nameParts.join(\".\");\n\n    var alias = false;\n    var aliasTable = table;\n    // Check if table is available. If not, find table by Alias\n    if (!getTable(table)) {\n      var oldTable = table;\n      table = findTableByAlias(table, editor);\n      if (table !== oldTable) alias = true;\n    }\n\n    var columns = getTable(table);\n    if (columns && columns.columns)\n      columns = columns.columns;\n\n    if (columns) {\n      addMatches(result, string, columns, function(w) {\n        var tableInsert = table;\n        if (alias == true) tableInsert = aliasTable;\n        if (typeof w == \"string\") {\n          w = tableInsert + \".\" + w;\n        } else {\n          w = shallowClone(w);\n          w.text = tableInsert + \".\" + w.text;\n        }\n        return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;\n      });\n    }\n\n    return start;\n  }\n\n  function eachWord(lineText, f) {\n    var words = lineText.split(/\\s+/)\n    for (var i = 0; i < words.length; i++)\n      if (words[i]) f(words[i].replace(/[,;]/g, ''))\n  }\n\n  function findTableByAlias(alias, editor) {\n    var doc = editor.doc;\n    var fullQuery = doc.getValue();\n    var aliasUpperCase = alias.toUpperCase();\n    var previousWord = \"\";\n    var table = \"\";\n    var separator = [];\n    var validRange = {\n      start: Pos(0, 0),\n      end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)\n    };\n\n    //add separator\n    var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);\n    while(indexOfSeparator != -1) {\n      separator.push(doc.posFromIndex(indexOfSeparator));\n      indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);\n    }\n    separator.unshift(Pos(0, 0));\n    separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));\n\n    //find valid range\n    var prevItem = null;\n    var current = editor.getCursor()\n    for (var i = 0; i < separator.length; i++) {\n      if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {\n        validRange = {start: prevItem, end: separator[i]};\n        break;\n      }\n      prevItem = separator[i];\n    }\n\n    if (validRange.start) {\n      var query = doc.getRange(validRange.start, validRange.end, false);\n\n      for (var i = 0; i < query.length; i++) {\n        var lineText = query[i];\n        eachWord(lineText, function(word) {\n          var wordUpperCase = word.toUpperCase();\n          if (wordUpperCase === aliasUpperCase && getTable(previousWord))\n            table = previousWord;\n          if (wordUpperCase !== CONS.ALIAS_KEYWORD)\n            previousWord = word;\n        });\n        if (table) break;\n      }\n    }\n    return table;\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"sql\", function(editor, options) {\n    tables = parseTables(options && options.tables)\n    var defaultTableName = options && options.defaultTable;\n    var disableKeywords = options && options.disableKeywords;\n    defaultTable = defaultTableName && getTable(defaultTableName);\n    keywords = getKeywords(editor);\n    identifierQuote = getIdentifierQuote(editor);\n\n    if (defaultTableName && !defaultTable)\n      defaultTable = findTableByAlias(defaultTableName, editor);\n\n    defaultTable = defaultTable || [];\n\n    if (defaultTable.columns)\n      defaultTable = defaultTable.columns;\n\n    var cur = editor.getCursor();\n    var result = [];\n    var token = editor.getTokenAt(cur), start, end, search;\n    if (token.end > cur.ch) {\n      token.end = cur.ch;\n      token.string = token.string.slice(0, cur.ch - token.start);\n    }\n\n    if (token.string.match(/^[.`\"'\\w@][\\w$#]*$/g)) {\n      search = token.string;\n      start = token.start;\n      end = token.end;\n    } else {\n      start = end = cur.ch;\n      search = \"\";\n    }\n    if (search.charAt(0) == \".\" || search.charAt(0) == identifierQuote) {\n      start = nameCompletion(cur, token, result, editor);\n    } else {\n      var objectOrClass = function(w, className) {\n        if (typeof w === \"object\") {\n          w.className = className;\n        } else {\n          w = { text: w, className: className };\n        }\n        return w;\n      };\n    addMatches(result, search, defaultTable, function(w) {\n        return objectOrClass(w, \"CodeMirror-hint-table CodeMirror-hint-default-table\");\n    });\n    addMatches(\n        result,\n        search,\n        tables, function(w) {\n          return objectOrClass(w, \"CodeMirror-hint-table\");\n        }\n    );\n    if (!disableKeywords)\n      addMatches(result, search, keywords, function(w) {\n          return objectOrClass(w.toUpperCase(), \"CodeMirror-hint-keyword\");\n      });\n  }\n\n    return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};\n  });\n});\n\n\n/* ---- extension/hint/xml-hint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var Pos = CodeMirror.Pos;\n\n  function matches(hint, typed, matchInMiddle) {\n    if (matchInMiddle) return hint.indexOf(typed) >= 0;\n    else return hint.lastIndexOf(typed, 0) == 0;\n  }\n\n  function getHints(cm, options) {\n    var tags = options && options.schemaInfo;\n    var quote = (options && options.quoteChar) || '\"';\n    var matchInMiddle = options && options.matchInMiddle;\n    if (!tags) return;\n    var cur = cm.getCursor(), token = cm.getTokenAt(cur);\n    if (token.end > cur.ch) {\n      token.end = cur.ch;\n      token.string = token.string.slice(0, cur.ch - token.start);\n    }\n    var inner = CodeMirror.innerMode(cm.getMode(), token.state);\n    if (!inner.mode.xmlCurrentTag) return\n    var result = [], replaceToken = false, prefix;\n    var tag = /\\btag\\b/.test(token.type) && !/>$/.test(token.string);\n    var tagName = tag && /^\\w/.test(token.string), tagStart;\n\n    if (tagName) {\n      var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);\n      var tagType = /<\\/$/.test(before) ? \"close\" : /<$/.test(before) ? \"open\" : null;\n      if (tagType) tagStart = token.start - (tagType == \"close\" ? 2 : 1);\n    } else if (tag && token.string == \"<\") {\n      tagType = \"open\";\n    } else if (tag && token.string == \"</\") {\n      tagType = \"close\";\n    }\n\n    var tagInfo = inner.mode.xmlCurrentTag(inner.state)\n    if (!tag && !tagInfo || tagType) {\n      if (tagName)\n        prefix = token.string;\n      replaceToken = tagType;\n      var context = inner.mode.xmlCurrentContext ? inner.mode.xmlCurrentContext(inner.state) : []\n      var inner = context.length && context[context.length - 1]\n      var curTag = inner && tags[inner]\n      var childList = inner ? curTag && curTag.children : tags[\"!top\"];\n      if (childList && tagType != \"close\") {\n        for (var i = 0; i < childList.length; ++i) if (!prefix || matches(childList[i], prefix, matchInMiddle))\n          result.push(\"<\" + childList[i]);\n      } else if (tagType != \"close\") {\n        for (var name in tags)\n          if (tags.hasOwnProperty(name) && name != \"!top\" && name != \"!attrs\" && (!prefix || matches(name, prefix, matchInMiddle)))\n            result.push(\"<\" + name);\n      }\n      if (inner && (!prefix || tagType == \"close\" && matches(inner, prefix, matchInMiddle)))\n        result.push(\"</\" + inner + \">\");\n    } else {\n      // Attribute completion\n      var curTag = tagInfo && tags[tagInfo.name], attrs = curTag && curTag.attrs;\n      var globalAttrs = tags[\"!attrs\"];\n      if (!attrs && !globalAttrs) return;\n      if (!attrs) {\n        attrs = globalAttrs;\n      } else if (globalAttrs) { // Combine tag-local and global attributes\n        var set = {};\n        for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm];\n        for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm];\n        attrs = set;\n      }\n      if (token.type == \"string\" || token.string == \"=\") { // A value\n        var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),\n                                 Pos(cur.line, token.type == \"string\" ? token.start : token.end));\n        var atName = before.match(/([^\\s\\u00a0=<>\\\"\\']+)=$/), atValues;\n        if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return;\n        if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget\n        if (token.type == \"string\") {\n          prefix = token.string;\n          var n = 0;\n          if (/['\"]/.test(token.string.charAt(0))) {\n            quote = token.string.charAt(0);\n            prefix = token.string.slice(1);\n            n++;\n          }\n          var len = token.string.length;\n          if (/['\"]/.test(token.string.charAt(len - 1))) {\n            quote = token.string.charAt(len - 1);\n            prefix = token.string.substr(n, len - 2);\n          }\n          if (n) { // an opening quote\n            var line = cm.getLine(cur.line);\n            if (line.length > token.end && line.charAt(token.end) == quote) token.end++; // include a closing quote\n          }\n          replaceToken = true;\n        }\n        for (var i = 0; i < atValues.length; ++i) if (!prefix || matches(atValues[i], prefix, matchInMiddle))\n          result.push(quote + atValues[i] + quote);\n      } else { // An attribute name\n        if (token.type == \"attribute\") {\n          prefix = token.string;\n          replaceToken = true;\n        }\n        for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || matches(attr, prefix, matchInMiddle)))\n          result.push(attr);\n      }\n    }\n    return {\n      list: result,\n      from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,\n      to: replaceToken ? Pos(cur.line, token.end) : cur\n    };\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"xml\", getHints);\n});\n\n\n/* ---- extension/lint/json-lint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Depends on jsonlint.js from https://github.com/zaach/jsonlint\n\n// declare global: jsonlint\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerHelper(\"lint\", \"json\", function(text) {\n  var found = [];\n  if (!window.jsonlint) {\n    if (window.console) {\n      window.console.error(\"Error: window.jsonlint not defined, CodeMirror JSON linting cannot run.\");\n    }\n    return found;\n  }\n  // for jsonlint's web dist jsonlint is exported as an object with a single property parser, of which parseError\n  // is a subproperty\n  var jsonlint = window.jsonlint.parser || window.jsonlint\n  jsonlint.parseError = function(str, hash) {\n    var loc = hash.loc;\n    found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),\n                to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),\n                message: str});\n  };\n  try { jsonlint.parse(text); }\n  catch(e) {}\n  return found;\n});\n\n});\n\n\n/* ---- extension/lint/jsonlint.js ---- */\n\n\nvar jsonlint=function(){var a=!0,b=!1,c={},d=function(){var a={trace:function(){},yy:{},symbols_:{error:2,JSONString:3,STRING:4,JSONNumber:5,NUMBER:6,JSONNullLiteral:7,NULL:8,JSONBooleanLiteral:9,TRUE:10,FALSE:11,JSONText:12,JSONValue:13,EOF:14,JSONObject:15,JSONArray:16,\"{\":17,\"}\":18,JSONMemberList:19,JSONMember:20,\":\":21,\",\":22,\"[\":23,\"]\":24,JSONElementList:25,$accept:0,$end:1},terminals_:{2:\"error\",4:\"STRING\",6:\"NUMBER\",8:\"NULL\",10:\"TRUE\",11:\"FALSE\",14:\"EOF\",17:\"{\",18:\"}\",21:\":\",22:\",\",23:\"[\",24:\"]\"},productions_:[0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],performAction:function(b,c,d,e,f,g,h){var i=g.length-1;switch(f){case 1:this.$=b.replace(/\\\\(\\\\|\")/g,\"$1\").replace(/\\\\n/g,\"\\n\").replace(/\\\\r/g,\"\\r\").replace(/\\\\t/g,\"\t\").replace(/\\\\v/g,\"\u000b\").replace(/\\\\f/g,\"\\f\").replace(/\\\\b/g,\"\\b\");break;case 2:this.$=Number(b);break;case 3:this.$=null;break;case 4:this.$=!0;break;case 5:this.$=!1;break;case 6:return this.$=g[i-1];case 13:this.$={};break;case 14:this.$=g[i-1];break;case 15:this.$=[g[i-2],g[i]];break;case 16:this.$={},this.$[g[i][0]]=g[i][1];break;case 17:this.$=g[i-2],g[i-2][g[i][0]]=g[i][1];break;case 18:this.$=[];break;case 19:this.$=g[i-1];break;case 20:this.$=[g[i]];break;case 21:this.$=g[i-2],g[i-2].push(g[i])}},table:[{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],defaultActions:{16:[2,6]},parseError:function(b,c){throw new Error(b)},parse:function(b){function o(a){d.length=d.length-2*a,e.length=e.length-a,f.length=f.length-a}function p(){var a;return a=c.lexer.lex()||1,typeof a!=\"number\"&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h=\"\",i=0,j=0,k=0,l=2,m=1;this.lexer.setInput(b),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,typeof this.lexer.yylloc==\"undefined\"&&(this.lexer.yylloc={});var n=this.lexer.yylloc;f.push(n),typeof this.yy.parseError==\"function\"&&(this.parseError=this.yy.parseError);var q,r,s,t,u,v,w={},x,y,z,A;for(;;){s=d[d.length-1],this.defaultActions[s]?t=this.defaultActions[s]:(q==null&&(q=p()),t=g[s]&&g[s][q]);if(typeof t==\"undefined\"||!t.length||!t[0]){if(!k){A=[];for(x in g[s])this.terminals_[x]&&x>2&&A.push(\"'\"+this.terminals_[x]+\"'\");var B=\"\";this.lexer.showPosition?B=\"Parse error on line \"+(i+1)+\":\\n\"+this.lexer.showPosition()+\"\\nExpecting \"+A.join(\", \")+\", got '\"+this.terminals_[q]+\"'\":B=\"Parse error on line \"+(i+1)+\": Unexpected \"+(q==1?\"end of input\":\"'\"+(this.terminals_[q]||q)+\"'\"),this.parseError(B,{text:this.lexer.match,token:this.terminals_[q]||q,line:this.lexer.yylineno,loc:n,expected:A})}if(k==3){if(q==m)throw new Error(B||\"Parsing halted.\");j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,q=p()}for(;;){if(l.toString()in g[s])break;if(s==0)throw new Error(B||\"Parsing halted.\");o(1),s=d[d.length-1]}r=q,q=l,s=d[d.length-1],t=g[s]&&g[s][l],k=3}if(t[0]instanceof Array&&t.length>1)throw new Error(\"Parse Error: multiple actions possible at state: \"+s+\", token: \"+q);switch(t[0]){case 1:d.push(q),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(t[1]),q=null,r?(q=r,r=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,k>0&&k--);break;case 2:y=this.productions_[t[1]][1],w.$=e[e.length-y],w._$={first_line:f[f.length-(y||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(y||1)].first_column,last_column:f[f.length-1].last_column},v=this.performAction.call(w,h,j,i,this.yy,t[1],e,f);if(typeof v!=\"undefined\")return v;y&&(d=d.slice(0,-1*y*2),e=e.slice(0,-1*y),f=f.slice(0,-1*y)),d.push(this.productions_[t[1]][0]),e.push(w.$),f.push(w._$),z=g[d[d.length-2]][d[d.length-1]],d.push(z);break;case 3:return!0}}return!0}},b=function(){var a={EOF:1,parseError:function(b,c){if(!this.yy.parseError)throw new Error(b);this.yy.parseError(b,c)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match=\"\",this.conditionStack=[\"INITIAL\"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.match+=a,this.matched+=a;var b=a.match(/\\n/);return b&&this.yylineno++,this._input=this._input.slice(1),a},unput:function(a){return this._input=a+this._input,this},more:function(){return this._more=!0,this},less:function(a){this._input=this.match.slice(a)+this._input},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?\"...\":\"\")+a.substr(-20).replace(/\\n/g,\"\")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?\"...\":\"\")).replace(/\\n/g,\"\")},showPosition:function(){var a=this.pastInput(),b=(new Array(a.length+1)).join(\"-\");return a+this.upcomingInput()+\"\\n\"+b+\"^\"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e,f;this._more||(this.yytext=\"\",this.match=\"\");var g=this._currentRules();for(var h=0;h<g.length;h++){c=this._input.match(this.rules[g[h]]);if(c&&(!b||c[0].length>b[0].length)){b=c,d=h;if(!this.options.flex)break}}if(b){f=b[0].match(/\\n.*/g),f&&(this.yylineno+=f.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:f?f[f.length-1].length-1:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.yyleng=this.yytext.length,this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,g[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1);if(a)return a;return}if(this._input===\"\")return this.EOF;this.parseError(\"Lexical error on line \"+(this.yylineno+1)+\". Unrecognized text.\\n\"+this.showPosition(),{text:\"\",token:null,line:this.yylineno})},lex:function(){var b=this.next();return typeof b!=\"undefined\"?b:this.lex()},begin:function(b){this.conditionStack.push(b)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(b){this.begin(b)}};return a.options={},a.performAction=function(b,c,d,e){var f=e;switch(d){case 0:break;case 1:return 6;case 2:return c.yytext=c.yytext.substr(1,c.yyleng-2),4;case 3:return 17;case 4:return 18;case 5:return 23;case 6:return 24;case 7:return 22;case 8:return 21;case 9:return 10;case 10:return 11;case 11:return 8;case 12:return 14;case 13:return\"INVALID\"}},a.rules=[/^(?:\\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\\.[0-9]+)?([eE][-+]?[0-9]+)?\\b)/,/^(?:\"(?:\\\\[\\\\\"bfnrt/]|\\\\u[a-fA-F0-9]{4}|[^\\\\\\0-\\x09\\x0a-\\x1f\"])*\")/,/^(?:\\{)/,/^(?:\\})/,/^(?:\\[)/,/^(?:\\])/,/^(?:,)/,/^(?::)/,/^(?:true\\b)/,/^(?:false\\b)/,/^(?:null\\b)/,/^(?:$)/,/^(?:.)/],a.conditions={INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13],inclusive:!0}},a}();return a.lexer=b,a}();return typeof a!=\"undefined\"&&typeof c!=\"undefined\"&&(c.parser=d,c.parse=function(){return d.parse.apply(d,arguments)},c.main=function(d){if(!d[1])throw new Error(\"Usage: \"+d[0]+\" FILE\");if(typeof process!=\"undefined\")var e=a(\"fs\").readFileSync(a(\"path\").join(process.cwd(),d[1]),\"utf8\");else var f=a(\"file\").path(a(\"file\").cwd()),e=f.join(d[1]).read({charset:\"utf-8\"});return c.parser.parse(e)},typeof b!=\"undefined\"&&a.main===b&&c.main(typeof process!=\"undefined\"?process.argv.slice(1):a(\"system\").args)),c}();\n\n/* ---- extension/lint/lint.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n  var GUTTER_ID = \"CodeMirror-lint-markers\";\n\n  function showTooltip(cm, e, content) {\n    var tt = document.createElement(\"div\");\n    tt.className = \"CodeMirror-lint-tooltip cm-s-\" + cm.options.theme;\n    tt.appendChild(content.cloneNode(true));\n    if (cm.state.lint.options.selfContain)\n      cm.getWrapperElement().appendChild(tt);\n    else\n      document.body.appendChild(tt);\n\n    function position(e) {\n      if (!tt.parentNode) return CodeMirror.off(document, \"mousemove\", position);\n      tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + \"px\";\n      tt.style.left = (e.clientX + 5) + \"px\";\n    }\n    CodeMirror.on(document, \"mousemove\", position);\n    position(e);\n    if (tt.style.opacity != null) tt.style.opacity = 1;\n    return tt;\n  }\n  function rm(elt) {\n    if (elt.parentNode) elt.parentNode.removeChild(elt);\n  }\n  function hideTooltip(tt) {\n    if (!tt.parentNode) return;\n    if (tt.style.opacity == null) rm(tt);\n    tt.style.opacity = 0;\n    setTimeout(function() { rm(tt); }, 600);\n  }\n\n  function showTooltipFor(cm, e, content, node) {\n    var tooltip = showTooltip(cm, e, content);\n    function hide() {\n      CodeMirror.off(node, \"mouseout\", hide);\n      if (tooltip) { hideTooltip(tooltip); tooltip = null; }\n    }\n    var poll = setInterval(function() {\n      if (tooltip) for (var n = node;; n = n.parentNode) {\n        if (n && n.nodeType == 11) n = n.host;\n        if (n == document.body) return;\n        if (!n) { hide(); break; }\n      }\n      if (!tooltip) return clearInterval(poll);\n    }, 400);\n    CodeMirror.on(node, \"mouseout\", hide);\n  }\n\n  function LintState(cm, options, hasGutter) {\n    this.marked = [];\n    this.options = options;\n    this.timeout = null;\n    this.hasGutter = hasGutter;\n    this.onMouseOver = function(e) { onMouseOver(cm, e); };\n    this.waitingFor = 0\n  }\n\n  function parseOptions(_cm, options) {\n    if (options instanceof Function) return {getAnnotations: options};\n    if (!options || options === true) options = {};\n    return options;\n  }\n\n  function clearMarks(cm) {\n    var state = cm.state.lint;\n    if (state.hasGutter) cm.clearGutter(GUTTER_ID);\n    for (var i = 0; i < state.marked.length; ++i)\n      state.marked[i].clear();\n    state.marked.length = 0;\n  }\n\n  function makeMarker(cm, labels, severity, multiple, tooltips) {\n    var marker = document.createElement(\"div\"), inner = marker;\n    marker.className = \"CodeMirror-lint-marker-\" + severity;\n    if (multiple) {\n      inner = marker.appendChild(document.createElement(\"div\"));\n      inner.className = \"CodeMirror-lint-marker-multiple\";\n    }\n\n    if (tooltips != false) CodeMirror.on(inner, \"mouseover\", function(e) {\n      showTooltipFor(cm, e, labels, inner);\n    });\n\n    return marker;\n  }\n\n  function getMaxSeverity(a, b) {\n    if (a == \"error\") return a;\n    else return b;\n  }\n\n  function groupByLine(annotations) {\n    var lines = [];\n    for (var i = 0; i < annotations.length; ++i) {\n      var ann = annotations[i], line = ann.from.line;\n      (lines[line] || (lines[line] = [])).push(ann);\n    }\n    return lines;\n  }\n\n  function annotationTooltip(ann) {\n    var severity = ann.severity;\n    if (!severity) severity = \"error\";\n    var tip = document.createElement(\"div\");\n    tip.className = \"CodeMirror-lint-message-\" + severity;\n    if (typeof ann.messageHTML != 'undefined') {\n      tip.innerHTML = ann.messageHTML;\n    } else {\n      tip.appendChild(document.createTextNode(ann.message));\n    }\n    return tip;\n  }\n\n  function lintAsync(cm, getAnnotations, passOptions) {\n    var state = cm.state.lint\n    var id = ++state.waitingFor\n    function abort() {\n      id = -1\n      cm.off(\"change\", abort)\n    }\n    cm.on(\"change\", abort)\n    getAnnotations(cm.getValue(), function(annotations, arg2) {\n      cm.off(\"change\", abort)\n      if (state.waitingFor != id) return\n      if (arg2 && annotations instanceof CodeMirror) annotations = arg2\n      cm.operation(function() {updateLinting(cm, annotations)})\n    }, passOptions, cm);\n  }\n\n  function startLinting(cm) {\n    var state = cm.state.lint, options = state.options;\n    /*\n     * Passing rules in `options` property prevents JSHint (and other linters) from complaining\n     * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.\n     */\n    var passOptions = options.options || options;\n    var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), \"lint\");\n    if (!getAnnotations) return;\n    if (options.async || getAnnotations.async) {\n      lintAsync(cm, getAnnotations, passOptions)\n    } else {\n      var annotations = getAnnotations(cm.getValue(), passOptions, cm);\n      if (!annotations) return;\n      if (annotations.then) annotations.then(function(issues) {\n        cm.operation(function() {updateLinting(cm, issues)})\n      });\n      else cm.operation(function() {updateLinting(cm, annotations)})\n    }\n  }\n\n  function updateLinting(cm, annotationsNotSorted) {\n    clearMarks(cm);\n    var state = cm.state.lint, options = state.options;\n\n    var annotations = groupByLine(annotationsNotSorted);\n\n    for (var line = 0; line < annotations.length; ++line) {\n      var anns = annotations[line];\n      if (!anns) continue;\n\n      var maxSeverity = null;\n      var tipLabel = state.hasGutter && document.createDocumentFragment();\n\n      for (var i = 0; i < anns.length; ++i) {\n        var ann = anns[i];\n        var severity = ann.severity;\n        if (!severity) severity = \"error\";\n        maxSeverity = getMaxSeverity(maxSeverity, severity);\n\n        if (options.formatAnnotation) ann = options.formatAnnotation(ann);\n        if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));\n\n        if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {\n          className: \"CodeMirror-lint-mark-\" + severity,\n          __annotation: ann\n        }));\n      }\n\n      if (state.hasGutter)\n        cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,\n                                                       state.options.tooltips));\n    }\n    if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);\n  }\n\n  function onChange(cm) {\n    var state = cm.state.lint;\n    if (!state) return;\n    clearTimeout(state.timeout);\n    state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);\n  }\n\n  function popupTooltips(cm, annotations, e) {\n    var target = e.target || e.srcElement;\n    var tooltip = document.createDocumentFragment();\n    for (var i = 0; i < annotations.length; i++) {\n      var ann = annotations[i];\n      tooltip.appendChild(annotationTooltip(ann));\n    }\n    showTooltipFor(cm, e, tooltip, target);\n  }\n\n  function onMouseOver(cm, e) {\n    var target = e.target || e.srcElement;\n    if (!/\\bCodeMirror-lint-mark-/.test(target.className)) return;\n    var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;\n    var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, \"client\"));\n\n    var annotations = [];\n    for (var i = 0; i < spans.length; ++i) {\n      var ann = spans[i].__annotation;\n      if (ann) annotations.push(ann);\n    }\n    if (annotations.length) popupTooltips(cm, annotations, e);\n  }\n\n  CodeMirror.defineOption(\"lint\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      clearMarks(cm);\n      if (cm.state.lint.options.lintOnChange !== false)\n        cm.off(\"change\", onChange);\n      CodeMirror.off(cm.getWrapperElement(), \"mouseover\", cm.state.lint.onMouseOver);\n      clearTimeout(cm.state.lint.timeout);\n      delete cm.state.lint;\n    }\n\n    if (val) {\n      var gutters = cm.getOption(\"gutters\"), hasLintGutter = false;\n      for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;\n      var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);\n      if (state.options.lintOnChange !== false)\n        cm.on(\"change\", onChange);\n      if (state.options.tooltips != false && state.options.tooltips != \"gutter\")\n        CodeMirror.on(cm.getWrapperElement(), \"mouseover\", state.onMouseOver);\n\n      startLinting(cm);\n    }\n  });\n\n  CodeMirror.defineExtension(\"performLint\", function() {\n    if (this.state.lint) startLinting(this);\n  });\n});\n\n\n/* ---- extension/scroll/annotatescrollbar.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineExtension(\"annotateScrollbar\", function(options) {\n    if (typeof options == \"string\") options = {className: options};\n    return new Annotation(this, options);\n  });\n\n  CodeMirror.defineOption(\"scrollButtonHeight\", 0);\n\n  function Annotation(cm, options) {\n    this.cm = cm;\n    this.options = options;\n    this.buttonHeight = options.scrollButtonHeight || cm.getOption(\"scrollButtonHeight\");\n    this.annotations = [];\n    this.doRedraw = this.doUpdate = null;\n    this.div = cm.getWrapperElement().appendChild(document.createElement(\"div\"));\n    this.div.style.cssText = \"position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none\";\n    this.computeScale();\n\n    function scheduleRedraw(delay) {\n      clearTimeout(self.doRedraw);\n      self.doRedraw = setTimeout(function() { self.redraw(); }, delay);\n    }\n\n    var self = this;\n    cm.on(\"refresh\", this.resizeHandler = function() {\n      clearTimeout(self.doUpdate);\n      self.doUpdate = setTimeout(function() {\n        if (self.computeScale()) scheduleRedraw(20);\n      }, 100);\n    });\n    cm.on(\"markerAdded\", this.resizeHandler);\n    cm.on(\"markerCleared\", this.resizeHandler);\n    if (options.listenForChanges !== false)\n      cm.on(\"changes\", this.changeHandler = function() {\n        scheduleRedraw(250);\n      });\n  }\n\n  Annotation.prototype.computeScale = function() {\n    var cm = this.cm;\n    var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /\n      cm.getScrollerElement().scrollHeight\n    if (hScale != this.hScale) {\n      this.hScale = hScale;\n      return true;\n    }\n  };\n\n  Annotation.prototype.update = function(annotations) {\n    this.annotations = annotations;\n    this.redraw();\n  };\n\n  Annotation.prototype.redraw = function(compute) {\n    if (compute !== false) this.computeScale();\n    var cm = this.cm, hScale = this.hScale;\n\n    var frag = document.createDocumentFragment(), anns = this.annotations;\n\n    var wrapping = cm.getOption(\"lineWrapping\");\n    var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;\n    var curLine = null, curLineObj = null;\n    function getY(pos, top) {\n      if (curLine != pos.line) {\n        curLine = pos.line;\n        curLineObj = cm.getLineHandle(curLine);\n      }\n      if ((curLineObj.widgets && curLineObj.widgets.length) ||\n          (wrapping && curLineObj.height > singleLineH))\n        return cm.charCoords(pos, \"local\")[top ? \"top\" : \"bottom\"];\n      var topY = cm.heightAtLine(curLineObj, \"local\");\n      return topY + (top ? 0 : curLineObj.height);\n    }\n\n    var lastLine = cm.lastLine()\n    if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {\n      var ann = anns[i];\n      if (ann.to.line > lastLine) continue;\n      var top = nextTop || getY(ann.from, true) * hScale;\n      var bottom = getY(ann.to, false) * hScale;\n      while (i < anns.length - 1) {\n        if (anns[i + 1].to.line > lastLine) break;\n        nextTop = getY(anns[i + 1].from, true) * hScale;\n        if (nextTop > bottom + .9) break;\n        ann = anns[++i];\n        bottom = getY(ann.to, false) * hScale;\n      }\n      if (bottom == top) continue;\n      var height = Math.max(bottom - top, 3);\n\n      var elt = frag.appendChild(document.createElement(\"div\"));\n      elt.style.cssText = \"position: absolute; right: 0px; width: \" + Math.max(cm.display.barWidth - 1, 2) + \"px; top: \"\n        + (top + this.buttonHeight) + \"px; height: \" + height + \"px\";\n      elt.className = this.options.className;\n      if (ann.id) {\n        elt.setAttribute(\"annotation-id\", ann.id);\n      }\n    }\n    this.div.textContent = \"\";\n    this.div.appendChild(frag);\n  };\n\n  Annotation.prototype.clear = function() {\n    this.cm.off(\"refresh\", this.resizeHandler);\n    this.cm.off(\"markerAdded\", this.resizeHandler);\n    this.cm.off(\"markerCleared\", this.resizeHandler);\n    if (this.changeHandler) this.cm.off(\"changes\", this.changeHandler);\n    this.div.parentNode.removeChild(this.div);\n  };\n});\n\n\n/* ---- extension/scroll/scrollpastend.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"scrollPastEnd\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.off(\"change\", onChange);\n      cm.off(\"refresh\", updateBottomMargin);\n      cm.display.lineSpace.parentNode.style.paddingBottom = \"\";\n      cm.state.scrollPastEndPadding = null;\n    }\n    if (val) {\n      cm.on(\"change\", onChange);\n      cm.on(\"refresh\", updateBottomMargin);\n      updateBottomMargin(cm);\n    }\n  });\n\n  function onChange(cm, change) {\n    if (CodeMirror.changeEnd(change).line == cm.lastLine())\n      updateBottomMargin(cm);\n  }\n\n  function updateBottomMargin(cm) {\n    var padding = \"\";\n    if (cm.lineCount() > 1) {\n      var totalH = cm.display.scroller.clientHeight - 30,\n          lastLineH = cm.getLineHandle(cm.lastLine()).height;\n      padding = (totalH - lastLineH) + \"px\";\n    }\n    if (cm.state.scrollPastEndPadding != padding) {\n      cm.state.scrollPastEndPadding = padding;\n      cm.display.lineSpace.parentNode.style.paddingBottom = padding;\n      cm.off(\"refresh\", updateBottomMargin);\n      cm.setSize();\n      cm.on(\"refresh\", updateBottomMargin);\n    }\n  }\n});\n\n\n/* ---- extension/scroll/simplescrollbars.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function Bar(cls, orientation, scroll) {\n    this.orientation = orientation;\n    this.scroll = scroll;\n    this.screen = this.total = this.size = 1;\n    this.pos = 0;\n\n    this.node = document.createElement(\"div\");\n    this.node.className = cls + \"-\" + orientation;\n    this.inner = this.node.appendChild(document.createElement(\"div\"));\n\n    var self = this;\n    CodeMirror.on(this.inner, \"mousedown\", function(e) {\n      if (e.which != 1) return;\n      CodeMirror.e_preventDefault(e);\n      var axis = self.orientation == \"horizontal\" ? \"pageX\" : \"pageY\";\n      var start = e[axis], startpos = self.pos;\n      function done() {\n        CodeMirror.off(document, \"mousemove\", move);\n        CodeMirror.off(document, \"mouseup\", done);\n      }\n      function move(e) {\n        if (e.which != 1) return done();\n        self.moveTo(startpos + (e[axis] - start) * (self.total / self.size));\n      }\n      CodeMirror.on(document, \"mousemove\", move);\n      CodeMirror.on(document, \"mouseup\", done);\n    });\n\n    CodeMirror.on(this.node, \"click\", function(e) {\n      CodeMirror.e_preventDefault(e);\n      var innerBox = self.inner.getBoundingClientRect(), where;\n      if (self.orientation == \"horizontal\")\n        where = e.clientX < innerBox.left ? -1 : e.clientX > innerBox.right ? 1 : 0;\n      else\n        where = e.clientY < innerBox.top ? -1 : e.clientY > innerBox.bottom ? 1 : 0;\n      self.moveTo(self.pos + where * self.screen);\n    });\n\n    function onWheel(e) {\n      var moved = CodeMirror.wheelEventPixels(e)[self.orientation == \"horizontal\" ? \"x\" : \"y\"];\n      var oldPos = self.pos;\n      self.moveTo(self.pos + moved);\n      if (self.pos != oldPos) CodeMirror.e_preventDefault(e);\n    }\n    CodeMirror.on(this.node, \"mousewheel\", onWheel);\n    CodeMirror.on(this.node, \"DOMMouseScroll\", onWheel);\n  }\n\n  Bar.prototype.setPos = function(pos, force) {\n    if (pos < 0) pos = 0;\n    if (pos > this.total - this.screen) pos = this.total - this.screen;\n    if (!force && pos == this.pos) return false;\n    this.pos = pos;\n    this.inner.style[this.orientation == \"horizontal\" ? \"left\" : \"top\"] =\n      (pos * (this.size / this.total)) + \"px\";\n    return true\n  };\n\n  Bar.prototype.moveTo = function(pos) {\n    if (this.setPos(pos)) this.scroll(pos, this.orientation);\n  }\n\n  var minButtonSize = 10;\n\n  Bar.prototype.update = function(scrollSize, clientSize, barSize) {\n    var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize\n    if (sizeChanged) {\n      this.screen = clientSize;\n      this.total = scrollSize;\n      this.size = barSize;\n    }\n\n    var buttonSize = this.screen * (this.size / this.total);\n    if (buttonSize < minButtonSize) {\n      this.size -= minButtonSize - buttonSize;\n      buttonSize = minButtonSize;\n    }\n    this.inner.style[this.orientation == \"horizontal\" ? \"width\" : \"height\"] =\n      buttonSize + \"px\";\n    this.setPos(this.pos, sizeChanged);\n  };\n\n  function SimpleScrollbars(cls, place, scroll) {\n    this.addClass = cls;\n    this.horiz = new Bar(cls, \"horizontal\", scroll);\n    place(this.horiz.node);\n    this.vert = new Bar(cls, \"vertical\", scroll);\n    place(this.vert.node);\n    this.width = null;\n  }\n\n  SimpleScrollbars.prototype.update = function(measure) {\n    if (this.width == null) {\n      var style = window.getComputedStyle ? window.getComputedStyle(this.horiz.node) : this.horiz.node.currentStyle;\n      if (style) this.width = parseInt(style.height);\n    }\n    var width = this.width || 0;\n\n    var needsH = measure.scrollWidth > measure.clientWidth + 1;\n    var needsV = measure.scrollHeight > measure.clientHeight + 1;\n    this.vert.node.style.display = needsV ? \"block\" : \"none\";\n    this.horiz.node.style.display = needsH ? \"block\" : \"none\";\n\n    if (needsV) {\n      this.vert.update(measure.scrollHeight, measure.clientHeight,\n                       measure.viewHeight - (needsH ? width : 0));\n      this.vert.node.style.bottom = needsH ? width + \"px\" : \"0\";\n    }\n    if (needsH) {\n      this.horiz.update(measure.scrollWidth, measure.clientWidth,\n                        measure.viewWidth - (needsV ? width : 0) - measure.barLeft);\n      this.horiz.node.style.right = needsV ? width + \"px\" : \"0\";\n      this.horiz.node.style.left = measure.barLeft + \"px\";\n    }\n\n    return {right: needsV ? width : 0, bottom: needsH ? width : 0};\n  };\n\n  SimpleScrollbars.prototype.setScrollTop = function(pos) {\n    this.vert.setPos(pos);\n  };\n\n  SimpleScrollbars.prototype.setScrollLeft = function(pos) {\n    this.horiz.setPos(pos);\n  };\n\n  SimpleScrollbars.prototype.clear = function() {\n    var parent = this.horiz.node.parentNode;\n    parent.removeChild(this.horiz.node);\n    parent.removeChild(this.vert.node);\n  };\n\n  CodeMirror.scrollbarModel.simple = function(place, scroll) {\n    return new SimpleScrollbars(\"CodeMirror-simplescroll\", place, scroll);\n  };\n  CodeMirror.scrollbarModel.overlay = function(place, scroll) {\n    return new SimpleScrollbars(\"CodeMirror-overlayscroll\", place, scroll);\n  };\n});\n\n\n/* ---- extension/search/jump-to-line.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Defines jumpToLine command. Uses dialog.js if present.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../dialog/dialog\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../dialog/dialog\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function dialog(cm, text, shortText, deflt, f) {\n    if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});\n    else f(prompt(shortText, deflt));\n  }\n\n  function getJumpDialog(cm) {\n    return cm.phrase(\"Jump to line:\") + ' <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/> <span style=\"color: #888\" class=\"CodeMirror-search-hint\">' + cm.phrase(\"(Use line:column or scroll% syntax)\") + '</span>';\n  }\n\n  function interpretLine(cm, string) {\n    var num = Number(string)\n    if (/^[-+]/.test(string)) return cm.getCursor().line + num\n    else return num - 1\n  }\n\n  CodeMirror.commands.jumpToLine = function(cm) {\n    var cur = cm.getCursor();\n    dialog(cm, getJumpDialog(cm), cm.phrase(\"Jump to line:\"), (cur.line + 1) + \":\" + cur.ch, function(posStr) {\n      if (!posStr) return;\n\n      var match;\n      if (match = /^\\s*([\\+\\-]?\\d+)\\s*\\:\\s*(\\d+)\\s*$/.exec(posStr)) {\n        cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))\n      } else if (match = /^\\s*([\\+\\-]?\\d+(\\.\\d+)?)\\%\\s*/.exec(posStr)) {\n        var line = Math.round(cm.lineCount() * Number(match[1]) / 100);\n        if (/^[-+]/.test(match[1])) line = cur.line + line + 1;\n        cm.setCursor(line - 1, cur.ch);\n      } else if (match = /^\\s*\\:?\\s*([\\+\\-]?\\d+)\\s*/.exec(posStr)) {\n        cm.setCursor(interpretLine(cm, match[1]), cur.ch);\n      }\n    });\n  };\n\n  CodeMirror.keyMap[\"default\"][\"Alt-G\"] = \"jumpToLine\";\n});\n\n\n/* ---- extension/search/match-highlighter.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Highlighting text that matches the selection\n//\n// Defines an option highlightSelectionMatches, which, when enabled,\n// will style strings that match the selection throughout the\n// document.\n//\n// The option can be set to true to simply enable it, or to a\n// {minChars, style, wordsOnly, showToken, delay} object to explicitly\n// configure it. minChars is the minimum amount of characters that should be\n// selected for the behavior to occur, and style is the token style to\n// apply to the matches. This will be prefixed by \"cm-\" to create an\n// actual CSS class name. If wordsOnly is enabled, the matches will be\n// highlighted only if the selected text is a word. showToken, when enabled,\n// will cause the current token to be highlighted when nothing is selected.\n// delay is used to specify how much time to wait, in milliseconds, before\n// highlighting the matches. If annotateScrollbar is enabled, the occurences\n// will be highlighted on the scrollbar via the matchesonscrollbar addon.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./matchesonscrollbar\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./matchesonscrollbar\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var defaults = {\n    style: \"matchhighlight\",\n    minChars: 2,\n    delay: 100,\n    wordsOnly: false,\n    annotateScrollbar: false,\n    showToken: false,\n    trim: true\n  }\n\n  function State(options) {\n    this.options = {}\n    for (var name in defaults)\n      this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]\n    this.overlay = this.timeout = null;\n    this.matchesonscroll = null;\n    this.active = false;\n  }\n\n  CodeMirror.defineOption(\"highlightSelectionMatches\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      removeOverlay(cm);\n      clearTimeout(cm.state.matchHighlighter.timeout);\n      cm.state.matchHighlighter = null;\n      cm.off(\"cursorActivity\", cursorActivity);\n      cm.off(\"focus\", onFocus)\n    }\n    if (val) {\n      var state = cm.state.matchHighlighter = new State(val);\n      if (cm.hasFocus()) {\n        state.active = true\n        highlightMatches(cm)\n      } else {\n        cm.on(\"focus\", onFocus)\n      }\n      cm.on(\"cursorActivity\", cursorActivity);\n    }\n  });\n\n  function cursorActivity(cm) {\n    var state = cm.state.matchHighlighter;\n    if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)\n  }\n\n  function onFocus(cm) {\n    var state = cm.state.matchHighlighter\n    if (!state.active) {\n      state.active = true\n      scheduleHighlight(cm, state)\n    }\n  }\n\n  function scheduleHighlight(cm, state) {\n    clearTimeout(state.timeout);\n    state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);\n  }\n\n  function addOverlay(cm, query, hasBoundary, style) {\n    var state = cm.state.matchHighlighter;\n    cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));\n    if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {\n      var searchFor = hasBoundary ? new RegExp((/\\w/.test(query.charAt(0)) ? \"\\\\b\" : \"\") +\n                                               query.replace(/[\\\\\\[.+*?(){|^$]/g, \"\\\\$&\") +\n                                               (/\\w/.test(query.charAt(query.length - 1)) ? \"\\\\b\" : \"\")) : query;\n      state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,\n        {className: \"CodeMirror-selection-highlight-scrollbar\"});\n    }\n  }\n\n  function removeOverlay(cm) {\n    var state = cm.state.matchHighlighter;\n    if (state.overlay) {\n      cm.removeOverlay(state.overlay);\n      state.overlay = null;\n      if (state.matchesonscroll) {\n        state.matchesonscroll.clear();\n        state.matchesonscroll = null;\n      }\n    }\n  }\n\n  function highlightMatches(cm) {\n    cm.operation(function() {\n      var state = cm.state.matchHighlighter;\n      removeOverlay(cm);\n      if (!cm.somethingSelected() && state.options.showToken) {\n        var re = state.options.showToken === true ? /[\\w$]/ : state.options.showToken;\n        var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;\n        while (start && re.test(line.charAt(start - 1))) --start;\n        while (end < line.length && re.test(line.charAt(end))) ++end;\n        if (start < end)\n          addOverlay(cm, line.slice(start, end), re, state.options.style);\n        return;\n      }\n      var from = cm.getCursor(\"from\"), to = cm.getCursor(\"to\");\n      if (from.line != to.line) return;\n      if (state.options.wordsOnly && !isWord(cm, from, to)) return;\n      var selection = cm.getRange(from, to)\n      if (state.options.trim) selection = selection.replace(/^\\s+|\\s+$/g, \"\")\n      if (selection.length >= state.options.minChars)\n        addOverlay(cm, selection, false, state.options.style);\n    });\n  }\n\n  function isWord(cm, from, to) {\n    var str = cm.getRange(from, to);\n    if (str.match(/^\\w+$/) !== null) {\n        if (from.ch > 0) {\n            var pos = {line: from.line, ch: from.ch - 1};\n            var chr = cm.getRange(pos, from);\n            if (chr.match(/\\W/) === null) return false;\n        }\n        if (to.ch < cm.getLine(from.line).length) {\n            var pos = {line: to.line, ch: to.ch + 1};\n            var chr = cm.getRange(to, pos);\n            if (chr.match(/\\W/) === null) return false;\n        }\n        return true;\n    } else return false;\n  }\n\n  function boundariesAround(stream, re) {\n    return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&\n      (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));\n  }\n\n  function makeOverlay(query, hasBoundary, style) {\n    return {token: function(stream) {\n      if (stream.match(query) &&\n          (!hasBoundary || boundariesAround(stream, hasBoundary)))\n        return style;\n      stream.next();\n      stream.skipTo(query.charAt(0)) || stream.skipToEnd();\n    }};\n  }\n});\n\n\n/* ---- extension/search/matchesonscrollbar.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./searchcursor\"), require(\"../scroll/annotatescrollbar\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./searchcursor\", \"../scroll/annotatescrollbar\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineExtension(\"showMatchesOnScrollbar\", function(query, caseFold, options) {\n    if (typeof options == \"string\") options = {className: options};\n    if (!options) options = {};\n    return new SearchAnnotation(this, query, caseFold, options);\n  });\n\n  function SearchAnnotation(cm, query, caseFold, options) {\n    this.cm = cm;\n    this.options = options;\n    var annotateOptions = {listenForChanges: false};\n    for (var prop in options) annotateOptions[prop] = options[prop];\n    if (!annotateOptions.className) annotateOptions.className = \"CodeMirror-search-match\";\n    this.annotation = cm.annotateScrollbar(annotateOptions);\n    this.query = query;\n    this.caseFold = caseFold;\n    this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};\n    this.matches = [];\n    this.update = null;\n\n    this.findMatches();\n    this.annotation.update(this.matches);\n\n    var self = this;\n    cm.on(\"change\", this.changeHandler = function(_cm, change) { self.onChange(change); });\n  }\n\n  var MAX_MATCHES = 1000;\n\n  SearchAnnotation.prototype.findMatches = function() {\n    if (!this.gap) return;\n    for (var i = 0; i < this.matches.length; i++) {\n      var match = this.matches[i];\n      if (match.from.line >= this.gap.to) break;\n      if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);\n    }\n    var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline});\n    var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;\n    while (cursor.findNext()) {\n      var match = {from: cursor.from(), to: cursor.to()};\n      if (match.from.line >= this.gap.to) break;\n      this.matches.splice(i++, 0, match);\n      if (this.matches.length > maxMatches) break;\n    }\n    this.gap = null;\n  };\n\n  function offsetLine(line, changeStart, sizeChange) {\n    if (line <= changeStart) return line;\n    return Math.max(changeStart, line + sizeChange);\n  }\n\n  SearchAnnotation.prototype.onChange = function(change) {\n    var startLine = change.from.line;\n    var endLine = CodeMirror.changeEnd(change).line;\n    var sizeChange = endLine - change.to.line;\n    if (this.gap) {\n      this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);\n      this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);\n    } else {\n      this.gap = {from: change.from.line, to: endLine + 1};\n    }\n\n    if (sizeChange) for (var i = 0; i < this.matches.length; i++) {\n      var match = this.matches[i];\n      var newFrom = offsetLine(match.from.line, startLine, sizeChange);\n      if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);\n      var newTo = offsetLine(match.to.line, startLine, sizeChange);\n      if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);\n    }\n    clearTimeout(this.update);\n    var self = this;\n    this.update = setTimeout(function() { self.updateAfterChange(); }, 250);\n  };\n\n  SearchAnnotation.prototype.updateAfterChange = function() {\n    this.findMatches();\n    this.annotation.update(this.matches);\n  };\n\n  SearchAnnotation.prototype.clear = function() {\n    this.cm.off(\"change\", this.changeHandler);\n    this.annotation.clear();\n  };\n});\n\n\n/* ---- extension/search/search.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Define search commands. Depends on dialog.js or another\n// implementation of the openDialog method.\n\n// Replace works a little oddly -- it will do the replace on the next\n// Ctrl-G (or whatever is bound to findNext) press. You prevent a\n// replace by making sure the match is no longer selected when hitting\n// Ctrl-G.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./searchcursor\"), require(\"../dialog/dialog\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./searchcursor\", \"../dialog/dialog\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function searchOverlay(query, caseInsensitive) {\n    if (typeof query == \"string\")\n      query = new RegExp(query.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\"), caseInsensitive ? \"gi\" : \"g\");\n    else if (!query.global)\n      query = new RegExp(query.source, query.ignoreCase ? \"gi\" : \"g\");\n\n    return {token: function(stream) {\n      query.lastIndex = stream.pos;\n      var match = query.exec(stream.string);\n      if (match && match.index == stream.pos) {\n        stream.pos += match[0].length || 1;\n        return \"searching\";\n      } else if (match) {\n        stream.pos = match.index;\n      } else {\n        stream.skipToEnd();\n      }\n    }};\n  }\n\n  function SearchState() {\n    this.posFrom = this.posTo = this.lastQuery = this.query = null;\n    this.overlay = null;\n  }\n\n  function getSearchState(cm) {\n    return cm.state.search || (cm.state.search = new SearchState());\n  }\n\n  function queryCaseInsensitive(query) {\n    return typeof query == \"string\" && query == query.toLowerCase();\n  }\n\n  function getSearchCursor(cm, query, pos) {\n    // Heuristic: if the query string is all lowercase, do a case insensitive search.\n    return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});\n  }\n\n  function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {\n    cm.openDialog(text, onEnter, {\n      value: deflt,\n      selectValueOnOpen: true,\n      closeOnEnter: false,\n      onClose: function() { clearSearch(cm); },\n      onKeyDown: onKeyDown\n    });\n  }\n\n  function dialog(cm, text, shortText, deflt, f) {\n    if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});\n    else f(prompt(shortText, deflt));\n  }\n\n  function confirmDialog(cm, text, shortText, fs) {\n    if (cm.openConfirm) cm.openConfirm(text, fs);\n    else if (confirm(shortText)) fs[0]();\n  }\n\n  function parseString(string) {\n    return string.replace(/\\\\([nrt\\\\])/g, function(match, ch) {\n      if (ch == \"n\") return \"\\n\"\n      if (ch == \"r\") return \"\\r\"\n      if (ch == \"t\") return \"\\t\"\n      if (ch == \"\\\\\") return \"\\\\\"\n      return match\n    })\n  }\n\n  function parseQuery(query) {\n    var isRE = query.match(/^\\/(.*)\\/([a-z]*)$/);\n    if (isRE) {\n      try { query = new RegExp(isRE[1], isRE[2].indexOf(\"i\") == -1 ? \"\" : \"i\"); }\n      catch(e) {} // Not a regular expression after all, do a string search\n    } else {\n      query = parseString(query)\n    }\n    if (typeof query == \"string\" ? query == \"\" : query.test(\"\"))\n      query = /x^/;\n    return query;\n  }\n\n  function startSearch(cm, state, query) {\n    state.queryText = query;\n    state.query = parseQuery(query);\n    cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));\n    state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));\n    cm.addOverlay(state.overlay);\n    if (cm.showMatchesOnScrollbar) {\n      if (state.annotate) { state.annotate.clear(); state.annotate = null; }\n      state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));\n    }\n  }\n\n  function doSearch(cm, rev, persistent, immediate) {\n    var state = getSearchState(cm);\n    if (state.query) return findNext(cm, rev);\n    var q = cm.getSelection() || state.lastQuery;\n    if (q instanceof RegExp && q.source == \"x^\") q = null\n    if (persistent && cm.openDialog) {\n      var hiding = null\n      var searchNext = function(query, event) {\n        CodeMirror.e_stop(event);\n        if (!query) return;\n        if (query != state.queryText) {\n          startSearch(cm, state, query);\n          state.posFrom = state.posTo = cm.getCursor();\n        }\n        if (hiding) hiding.style.opacity = 1\n        findNext(cm, event.shiftKey, function(_, to) {\n          var dialog\n          if (to.line < 3 && document.querySelector &&\n              (dialog = cm.display.wrapper.querySelector(\".CodeMirror-dialog\")) &&\n              dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, \"window\").top)\n            (hiding = dialog).style.opacity = .4\n        })\n      };\n      persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) {\n        var keyName = CodeMirror.keyName(event)\n        var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption(\"keyMap\")][keyName]\n        if (cmd == \"findNext\" || cmd == \"findPrev\" ||\n          cmd == \"findPersistentNext\" || cmd == \"findPersistentPrev\") {\n          CodeMirror.e_stop(event);\n          startSearch(cm, getSearchState(cm), query);\n          cm.execCommand(cmd);\n        } else if (cmd == \"find\" || cmd == \"findPersistent\") {\n          CodeMirror.e_stop(event);\n          searchNext(query, event);\n        }\n      });\n      if (immediate && q) {\n        startSearch(cm, state, q);\n        findNext(cm, rev);\n      }\n    } else {\n      dialog(cm, getQueryDialog(cm), \"Search for:\", q, function(query) {\n        if (query && !state.query) cm.operation(function() {\n          startSearch(cm, state, query);\n          state.posFrom = state.posTo = cm.getCursor();\n          findNext(cm, rev);\n        });\n      });\n    }\n  }\n\n  function findNext(cm, rev, callback) {cm.operation(function() {\n    var state = getSearchState(cm);\n    var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);\n    if (!cursor.find(rev)) {\n      cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));\n      if (!cursor.find(rev)) return;\n    }\n    cm.setSelection(cursor.from(), cursor.to());\n    cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);\n    state.posFrom = cursor.from(); state.posTo = cursor.to();\n    if (callback) callback(cursor.from(), cursor.to())\n  });}\n\n  function clearSearch(cm) {cm.operation(function() {\n    var state = getSearchState(cm);\n    state.lastQuery = state.query;\n    if (!state.query) return;\n    state.query = state.queryText = null;\n    cm.removeOverlay(state.overlay);\n    if (state.annotate) { state.annotate.clear(); state.annotate = null; }\n  });}\n\n\n  function getQueryDialog(cm)  {\n    return '<span class=\"CodeMirror-search-label\">' + cm.phrase(\"Search:\") + '</span> <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/> <span style=\"color: #888\" class=\"CodeMirror-search-hint\">' + cm.phrase(\"(Use /re/ syntax for regexp search)\") + '</span>';\n  }\n  function getReplaceQueryDialog(cm) {\n    return ' <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/> <span style=\"color: #888\" class=\"CodeMirror-search-hint\">' + cm.phrase(\"(Use /re/ syntax for regexp search)\") + '</span>';\n  }\n  function getReplacementQueryDialog(cm) {\n    return '<span class=\"CodeMirror-search-label\">' + cm.phrase(\"With:\") + '</span> <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/>';\n  }\n  function getDoReplaceConfirm(cm) {\n    return '<span class=\"CodeMirror-search-label\">' + cm.phrase(\"Replace?\") + '</span> <button>' + cm.phrase(\"Yes\") + '</button> <button>' + cm.phrase(\"No\") + '</button> <button>' + cm.phrase(\"All\") + '</button> <button>' + cm.phrase(\"Stop\") + '</button> ';\n  }\n\n  function replaceAll(cm, query, text) {\n    cm.operation(function() {\n      for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {\n        if (typeof query != \"string\") {\n          var match = cm.getRange(cursor.from(), cursor.to()).match(query);\n          cursor.replace(text.replace(/\\$(\\d)/g, function(_, i) {return match[i];}));\n        } else cursor.replace(text);\n      }\n    });\n  }\n\n  function replace(cm, all) {\n    if (cm.getOption(\"readOnly\")) return;\n    var query = cm.getSelection() || getSearchState(cm).lastQuery;\n    var dialogText = '<span class=\"CodeMirror-search-label\">' + (all ? cm.phrase(\"Replace all:\") : cm.phrase(\"Replace:\")) + '</span>';\n    dialog(cm, dialogText + getReplaceQueryDialog(cm), dialogText, query, function(query) {\n      if (!query) return;\n      query = parseQuery(query);\n      dialog(cm, getReplacementQueryDialog(cm), cm.phrase(\"Replace with:\"), \"\", function(text) {\n        text = parseString(text)\n        if (all) {\n          replaceAll(cm, query, text)\n        } else {\n          clearSearch(cm);\n          var cursor = getSearchCursor(cm, query, cm.getCursor(\"from\"));\n          var advance = function() {\n            var start = cursor.from(), match;\n            if (!(match = cursor.findNext())) {\n              cursor = getSearchCursor(cm, query);\n              if (!(match = cursor.findNext()) ||\n                  (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;\n            }\n            cm.setSelection(cursor.from(), cursor.to());\n            cm.scrollIntoView({from: cursor.from(), to: cursor.to()});\n            confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase(\"Replace?\"),\n                          [function() {doReplace(match);}, advance,\n                           function() {replaceAll(cm, query, text)}]);\n          };\n          var doReplace = function(match) {\n            cursor.replace(typeof query == \"string\" ? text :\n                           text.replace(/\\$(\\d)/g, function(_, i) {return match[i];}));\n            advance();\n          };\n          advance();\n        }\n      });\n    });\n  }\n\n  CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};\n  CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};\n  CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};\n  CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};\n  CodeMirror.commands.findNext = doSearch;\n  CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};\n  CodeMirror.commands.clearSearch = clearSearch;\n  CodeMirror.commands.replace = replace;\n  CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};\n});\n\n\n/* ---- extension/search/searchcursor.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"))\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod)\n  else // Plain browser env\n    mod(CodeMirror)\n})(function(CodeMirror) {\n  \"use strict\"\n  var Pos = CodeMirror.Pos\n\n  function regexpFlags(regexp) {\n    var flags = regexp.flags\n    return flags != null ? flags : (regexp.ignoreCase ? \"i\" : \"\")\n      + (regexp.global ? \"g\" : \"\")\n      + (regexp.multiline ? \"m\" : \"\")\n  }\n\n  function ensureFlags(regexp, flags) {\n    var current = regexpFlags(regexp), target = current\n    for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)\n      target += flags.charAt(i)\n    return current == target ? regexp : new RegExp(regexp.source, target)\n  }\n\n  function maybeMultiline(regexp) {\n    return /\\\\s|\\\\n|\\n|\\\\W|\\\\D|\\[\\^/.test(regexp.source)\n  }\n\n  function searchRegexpForward(doc, regexp, start) {\n    regexp = ensureFlags(regexp, \"g\")\n    for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {\n      regexp.lastIndex = ch\n      var string = doc.getLine(line), match = regexp.exec(string)\n      if (match)\n        return {from: Pos(line, match.index),\n                to: Pos(line, match.index + match[0].length),\n                match: match}\n    }\n  }\n\n  function searchRegexpForwardMultiline(doc, regexp, start) {\n    if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)\n\n    regexp = ensureFlags(regexp, \"gm\")\n    var string, chunk = 1\n    for (var line = start.line, last = doc.lastLine(); line <= last;) {\n      // This grows the search buffer in exponentially-sized chunks\n      // between matches, so that nearby matches are fast and don't\n      // require concatenating the whole document (in case we're\n      // searching for something that has tons of matches), but at the\n      // same time, the amount of retries is limited.\n      for (var i = 0; i < chunk; i++) {\n        if (line > last) break\n        var curLine = doc.getLine(line++)\n        string = string == null ? curLine : string + \"\\n\" + curLine\n      }\n      chunk = chunk * 2\n      regexp.lastIndex = start.ch\n      var match = regexp.exec(string)\n      if (match) {\n        var before = string.slice(0, match.index).split(\"\\n\"), inside = match[0].split(\"\\n\")\n        var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length\n        return {from: Pos(startLine, startCh),\n                to: Pos(startLine + inside.length - 1,\n                        inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),\n                match: match}\n      }\n    }\n  }\n\n  function lastMatchIn(string, regexp, endMargin) {\n    var match, from = 0\n    while (from <= string.length) {\n      regexp.lastIndex = from\n      var newMatch = regexp.exec(string)\n      if (!newMatch) break\n      var end = newMatch.index + newMatch[0].length\n      if (end > string.length - endMargin) break\n      if (!match || end > match.index + match[0].length)\n        match = newMatch\n      from = newMatch.index + 1\n    }\n    return match\n  }\n\n  function searchRegexpBackward(doc, regexp, start) {\n    regexp = ensureFlags(regexp, \"g\")\n    for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {\n      var string = doc.getLine(line)\n      var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)\n      if (match)\n        return {from: Pos(line, match.index),\n                to: Pos(line, match.index + match[0].length),\n                match: match}\n    }\n  }\n\n  function searchRegexpBackwardMultiline(doc, regexp, start) {\n    if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)\n    regexp = ensureFlags(regexp, \"gm\")\n    var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch\n    for (var line = start.line, first = doc.firstLine(); line >= first;) {\n      for (var i = 0; i < chunkSize && line >= first; i++) {\n        var curLine = doc.getLine(line--)\n        string = string == null ? curLine : curLine + \"\\n\" + string\n      }\n      chunkSize *= 2\n\n      var match = lastMatchIn(string, regexp, endMargin)\n      if (match) {\n        var before = string.slice(0, match.index).split(\"\\n\"), inside = match[0].split(\"\\n\")\n        var startLine = line + before.length, startCh = before[before.length - 1].length\n        return {from: Pos(startLine, startCh),\n                to: Pos(startLine + inside.length - 1,\n                        inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),\n                match: match}\n      }\n    }\n  }\n\n  var doFold, noFold\n  if (String.prototype.normalize) {\n    doFold = function(str) { return str.normalize(\"NFD\").toLowerCase() }\n    noFold = function(str) { return str.normalize(\"NFD\") }\n  } else {\n    doFold = function(str) { return str.toLowerCase() }\n    noFold = function(str) { return str }\n  }\n\n  // Maps a position in a case-folded line back to a position in the original line\n  // (compensating for codepoints increasing in number during folding)\n  function adjustPos(orig, folded, pos, foldFunc) {\n    if (orig.length == folded.length) return pos\n    for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {\n      if (min == max) return min\n      var mid = (min + max) >> 1\n      var len = foldFunc(orig.slice(0, mid)).length\n      if (len == pos) return mid\n      else if (len > pos) max = mid\n      else min = mid + 1\n    }\n  }\n\n  function searchStringForward(doc, query, start, caseFold) {\n    // Empty string would match anything and never progress, so we\n    // define it to match nothing instead.\n    if (!query.length) return null\n    var fold = caseFold ? doFold : noFold\n    var lines = fold(query).split(/\\r|\\n\\r?/)\n\n    search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {\n      var orig = doc.getLine(line).slice(ch), string = fold(orig)\n      if (lines.length == 1) {\n        var found = string.indexOf(lines[0])\n        if (found == -1) continue search\n        var start = adjustPos(orig, string, found, fold) + ch\n        return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),\n                to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}\n      } else {\n        var cutFrom = string.length - lines[0].length\n        if (string.slice(cutFrom) != lines[0]) continue search\n        for (var i = 1; i < lines.length - 1; i++)\n          if (fold(doc.getLine(line + i)) != lines[i]) continue search\n        var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]\n        if (endString.slice(0, lastLine.length) != lastLine) continue search\n        return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),\n                to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}\n      }\n    }\n  }\n\n  function searchStringBackward(doc, query, start, caseFold) {\n    if (!query.length) return null\n    var fold = caseFold ? doFold : noFold\n    var lines = fold(query).split(/\\r|\\n\\r?/)\n\n    search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {\n      var orig = doc.getLine(line)\n      if (ch > -1) orig = orig.slice(0, ch)\n      var string = fold(orig)\n      if (lines.length == 1) {\n        var found = string.lastIndexOf(lines[0])\n        if (found == -1) continue search\n        return {from: Pos(line, adjustPos(orig, string, found, fold)),\n                to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}\n      } else {\n        var lastLine = lines[lines.length - 1]\n        if (string.slice(0, lastLine.length) != lastLine) continue search\n        for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)\n          if (fold(doc.getLine(start + i)) != lines[i]) continue search\n        var top = doc.getLine(line + 1 - lines.length), topString = fold(top)\n        if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search\n        return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),\n                to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}\n      }\n    }\n  }\n\n  function SearchCursor(doc, query, pos, options) {\n    this.atOccurrence = false\n    this.doc = doc\n    pos = pos ? doc.clipPos(pos) : Pos(0, 0)\n    this.pos = {from: pos, to: pos}\n\n    var caseFold\n    if (typeof options == \"object\") {\n      caseFold = options.caseFold\n    } else { // Backwards compat for when caseFold was the 4th argument\n      caseFold = options\n      options = null\n    }\n\n    if (typeof query == \"string\") {\n      if (caseFold == null) caseFold = false\n      this.matches = function(reverse, pos) {\n        return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)\n      }\n    } else {\n      query = ensureFlags(query, \"gm\")\n      if (!options || options.multiline !== false)\n        this.matches = function(reverse, pos) {\n          return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)\n        }\n      else\n        this.matches = function(reverse, pos) {\n          return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)\n        }\n    }\n  }\n\n  SearchCursor.prototype = {\n    findNext: function() {return this.find(false)},\n    findPrevious: function() {return this.find(true)},\n\n    find: function(reverse) {\n      var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))\n\n      // Implements weird auto-growing behavior on null-matches for\n      // backwards-compatibility with the vim code (unfortunately)\n      while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {\n        if (reverse) {\n          if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)\n          else if (result.from.line == this.doc.firstLine()) result = null\n          else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))\n        } else {\n          if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)\n          else if (result.to.line == this.doc.lastLine()) result = null\n          else result = this.matches(reverse, Pos(result.to.line + 1, 0))\n        }\n      }\n\n      if (result) {\n        this.pos = result\n        this.atOccurrence = true\n        return this.pos.match || true\n      } else {\n        var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)\n        this.pos = {from: end, to: end}\n        return this.atOccurrence = false\n      }\n    },\n\n    from: function() {if (this.atOccurrence) return this.pos.from},\n    to: function() {if (this.atOccurrence) return this.pos.to},\n\n    replace: function(newText, origin) {\n      if (!this.atOccurrence) return\n      var lines = CodeMirror.splitLines(newText)\n      this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)\n      this.pos.to = Pos(this.pos.from.line + lines.length - 1,\n                        lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))\n    }\n  }\n\n  CodeMirror.defineExtension(\"getSearchCursor\", function(query, pos, caseFold) {\n    return new SearchCursor(this.doc, query, pos, caseFold)\n  })\n  CodeMirror.defineDocExtension(\"getSearchCursor\", function(query, pos, caseFold) {\n    return new SearchCursor(this, query, pos, caseFold)\n  })\n\n  CodeMirror.defineExtension(\"selectMatches\", function(query, caseFold) {\n    var ranges = []\n    var cur = this.getSearchCursor(query, this.getCursor(\"from\"), caseFold)\n    while (cur.findNext()) {\n      if (CodeMirror.cmpPos(cur.to(), this.getCursor(\"to\")) > 0) break\n      ranges.push({anchor: cur.from(), head: cur.to()})\n    }\n    if (ranges.length)\n      this.setSelections(ranges, 0)\n  })\n});\n\n\n/* ---- extension/selection/active-line.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n  var WRAP_CLASS = \"CodeMirror-activeline\";\n  var BACK_CLASS = \"CodeMirror-activeline-background\";\n  var GUTT_CLASS = \"CodeMirror-activeline-gutter\";\n\n  CodeMirror.defineOption(\"styleActiveLine\", false, function(cm, val, old) {\n    var prev = old == CodeMirror.Init ? false : old;\n    if (val == prev) return\n    if (prev) {\n      cm.off(\"beforeSelectionChange\", selectionChange);\n      clearActiveLines(cm);\n      delete cm.state.activeLines;\n    }\n    if (val) {\n      cm.state.activeLines = [];\n      updateActiveLines(cm, cm.listSelections());\n      cm.on(\"beforeSelectionChange\", selectionChange);\n    }\n  });\n\n  function clearActiveLines(cm) {\n    for (var i = 0; i < cm.state.activeLines.length; i++) {\n      cm.removeLineClass(cm.state.activeLines[i], \"wrap\", WRAP_CLASS);\n      cm.removeLineClass(cm.state.activeLines[i], \"background\", BACK_CLASS);\n      cm.removeLineClass(cm.state.activeLines[i], \"gutter\", GUTT_CLASS);\n    }\n  }\n\n  function sameArray(a, b) {\n    if (a.length != b.length) return false;\n    for (var i = 0; i < a.length; i++)\n      if (a[i] != b[i]) return false;\n    return true;\n  }\n\n  function updateActiveLines(cm, ranges) {\n    var active = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      var option = cm.getOption(\"styleActiveLine\");\n      if (typeof option == \"object\" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())\n        continue\n      var line = cm.getLineHandleVisualStart(range.head.line);\n      if (active[active.length - 1] != line) active.push(line);\n    }\n    if (sameArray(cm.state.activeLines, active)) return;\n    cm.operation(function() {\n      clearActiveLines(cm);\n      for (var i = 0; i < active.length; i++) {\n        cm.addLineClass(active[i], \"wrap\", WRAP_CLASS);\n        cm.addLineClass(active[i], \"background\", BACK_CLASS);\n        cm.addLineClass(active[i], \"gutter\", GUTT_CLASS);\n      }\n      cm.state.activeLines = active;\n    });\n  }\n\n  function selectionChange(cm, sel) {\n    updateActiveLines(cm, sel.ranges);\n  }\n});\n\n\n/* ---- extension/selection/mark-selection.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Because sometimes you need to mark the selected *text*.\n//\n// Adds an option 'styleSelectedText' which, when enabled, gives\n// selected text the CSS class given as option value, or\n// \"CodeMirror-selectedtext\" when the value is not a string.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"styleSelectedText\", false, function(cm, val, old) {\n    var prev = old && old != CodeMirror.Init;\n    if (val && !prev) {\n      cm.state.markedSelection = [];\n      cm.state.markedSelectionStyle = typeof val == \"string\" ? val : \"CodeMirror-selectedtext\";\n      reset(cm);\n      cm.on(\"cursorActivity\", onCursorActivity);\n      cm.on(\"change\", onChange);\n    } else if (!val && prev) {\n      cm.off(\"cursorActivity\", onCursorActivity);\n      cm.off(\"change\", onChange);\n      clear(cm);\n      cm.state.markedSelection = cm.state.markedSelectionStyle = null;\n    }\n  });\n\n  function onCursorActivity(cm) {\n    if (cm.state.markedSelection)\n      cm.operation(function() { update(cm); });\n  }\n\n  function onChange(cm) {\n    if (cm.state.markedSelection && cm.state.markedSelection.length)\n      cm.operation(function() { clear(cm); });\n  }\n\n  var CHUNK_SIZE = 8;\n  var Pos = CodeMirror.Pos;\n  var cmp = CodeMirror.cmpPos;\n\n  function coverRange(cm, from, to, addAt) {\n    if (cmp(from, to) == 0) return;\n    var array = cm.state.markedSelection;\n    var cls = cm.state.markedSelectionStyle;\n    for (var line = from.line;;) {\n      var start = line == from.line ? from : Pos(line, 0);\n      var endLine = line + CHUNK_SIZE, atEnd = endLine >= to.line;\n      var end = atEnd ? to : Pos(endLine, 0);\n      var mark = cm.markText(start, end, {className: cls});\n      if (addAt == null) array.push(mark);\n      else array.splice(addAt++, 0, mark);\n      if (atEnd) break;\n      line = endLine;\n    }\n  }\n\n  function clear(cm) {\n    var array = cm.state.markedSelection;\n    for (var i = 0; i < array.length; ++i) array[i].clear();\n    array.length = 0;\n  }\n\n  function reset(cm) {\n    clear(cm);\n    var ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++)\n      coverRange(cm, ranges[i].from(), ranges[i].to());\n  }\n\n  function update(cm) {\n    if (!cm.somethingSelected()) return clear(cm);\n    if (cm.listSelections().length > 1) return reset(cm);\n\n    var from = cm.getCursor(\"start\"), to = cm.getCursor(\"end\");\n\n    var array = cm.state.markedSelection;\n    if (!array.length) return coverRange(cm, from, to);\n\n    var coverStart = array[0].find(), coverEnd = array[array.length - 1].find();\n    if (!coverStart || !coverEnd || to.line - from.line <= CHUNK_SIZE ||\n        cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0)\n      return reset(cm);\n\n    while (cmp(from, coverStart.from) > 0) {\n      array.shift().clear();\n      coverStart = array[0].find();\n    }\n    if (cmp(from, coverStart.from) < 0) {\n      if (coverStart.to.line - from.line < CHUNK_SIZE) {\n        array.shift().clear();\n        coverRange(cm, from, coverStart.to, 0);\n      } else {\n        coverRange(cm, from, coverStart.from, 0);\n      }\n    }\n\n    while (cmp(to, coverEnd.to) < 0) {\n      array.pop().clear();\n      coverEnd = array[array.length - 1].find();\n    }\n    if (cmp(to, coverEnd.to) > 0) {\n      if (to.line - coverEnd.from.line < CHUNK_SIZE) {\n        array.pop().clear();\n        coverRange(cm, coverEnd.from, to);\n      } else {\n        coverRange(cm, coverEnd.to, to);\n      }\n    }\n  }\n});\n\n\n/* ---- extension/selection/selection-pointer.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"selectionPointer\", false, function(cm, val) {\n    var data = cm.state.selectionPointer;\n    if (data) {\n      CodeMirror.off(cm.getWrapperElement(), \"mousemove\", data.mousemove);\n      CodeMirror.off(cm.getWrapperElement(), \"mouseout\", data.mouseout);\n      CodeMirror.off(window, \"scroll\", data.windowScroll);\n      cm.off(\"cursorActivity\", reset);\n      cm.off(\"scroll\", reset);\n      cm.state.selectionPointer = null;\n      cm.display.lineDiv.style.cursor = \"\";\n    }\n    if (val) {\n      data = cm.state.selectionPointer = {\n        value: typeof val == \"string\" ? val : \"default\",\n        mousemove: function(event) { mousemove(cm, event); },\n        mouseout: function(event) { mouseout(cm, event); },\n        windowScroll: function() { reset(cm); },\n        rects: null,\n        mouseX: null, mouseY: null,\n        willUpdate: false\n      };\n      CodeMirror.on(cm.getWrapperElement(), \"mousemove\", data.mousemove);\n      CodeMirror.on(cm.getWrapperElement(), \"mouseout\", data.mouseout);\n      CodeMirror.on(window, \"scroll\", data.windowScroll);\n      cm.on(\"cursorActivity\", reset);\n      cm.on(\"scroll\", reset);\n    }\n  });\n\n  function mousemove(cm, event) {\n    var data = cm.state.selectionPointer;\n    if (event.buttons == null ? event.which : event.buttons) {\n      data.mouseX = data.mouseY = null;\n    } else {\n      data.mouseX = event.clientX;\n      data.mouseY = event.clientY;\n    }\n    scheduleUpdate(cm);\n  }\n\n  function mouseout(cm, event) {\n    if (!cm.getWrapperElement().contains(event.relatedTarget)) {\n      var data = cm.state.selectionPointer;\n      data.mouseX = data.mouseY = null;\n      scheduleUpdate(cm);\n    }\n  }\n\n  function reset(cm) {\n    cm.state.selectionPointer.rects = null;\n    scheduleUpdate(cm);\n  }\n\n  function scheduleUpdate(cm) {\n    if (!cm.state.selectionPointer.willUpdate) {\n      cm.state.selectionPointer.willUpdate = true;\n      setTimeout(function() {\n        update(cm);\n        cm.state.selectionPointer.willUpdate = false;\n      }, 50);\n    }\n  }\n\n  function update(cm) {\n    var data = cm.state.selectionPointer;\n    if (!data) return;\n    if (data.rects == null && data.mouseX != null) {\n      data.rects = [];\n      if (cm.somethingSelected()) {\n        for (var sel = cm.display.selectionDiv.firstChild; sel; sel = sel.nextSibling)\n          data.rects.push(sel.getBoundingClientRect());\n      }\n    }\n    var inside = false;\n    if (data.mouseX != null) for (var i = 0; i < data.rects.length; i++) {\n      var rect = data.rects[i];\n      if (rect.left <= data.mouseX && rect.right >= data.mouseX &&\n          rect.top <= data.mouseY && rect.bottom >= data.mouseY)\n        inside = true;\n    }\n    var cursor = inside ? data.value : \"\";\n    if (cm.display.lineDiv.style.cursor != cursor)\n      cm.display.lineDiv.style.cursor = cursor;\n  }\n});\n\n\n/* ---- mode/coffeescript.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n/**\n * Link to the project's GitHub page:\n * https://github.com/pickhardt/coffeescript-codemirror-mode\n */\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"coffeescript\", function(conf, parserConf) {\n  var ERRORCLASS = \"error\";\n\n  function wordRegexp(words) {\n    return new RegExp(\"^((\" + words.join(\")|(\") + \"))\\\\b\");\n  }\n\n  var operators = /^(?:->|=>|\\+[+=]?|-[\\-=]?|\\*[\\*=]?|\\/[\\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\\|=?|\\^=?|\\~|!|\\?|(or|and|\\|\\||&&|\\?)=)/;\n  var delimiters = /^(?:[()\\[\\]{},:`=;]|\\.\\.?\\.?)/;\n  var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;\n  var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;\n\n  var wordOperators = wordRegexp([\"and\", \"or\", \"not\",\n                                  \"is\", \"isnt\", \"in\",\n                                  \"instanceof\", \"typeof\"]);\n  var indentKeywords = [\"for\", \"while\", \"loop\", \"if\", \"unless\", \"else\",\n                        \"switch\", \"try\", \"catch\", \"finally\", \"class\"];\n  var commonKeywords = [\"break\", \"by\", \"continue\", \"debugger\", \"delete\",\n                        \"do\", \"in\", \"of\", \"new\", \"return\", \"then\",\n                        \"this\", \"@\", \"throw\", \"when\", \"until\", \"extends\"];\n\n  var keywords = wordRegexp(indentKeywords.concat(commonKeywords));\n\n  indentKeywords = wordRegexp(indentKeywords);\n\n\n  var stringPrefixes = /^('{3}|\\\"{3}|['\\\"])/;\n  var regexPrefixes = /^(\\/{3}|\\/)/;\n  var commonConstants = [\"Infinity\", \"NaN\", \"undefined\", \"null\", \"true\", \"false\", \"on\", \"off\", \"yes\", \"no\"];\n  var constants = wordRegexp(commonConstants);\n\n  // Tokenizers\n  function tokenBase(stream, state) {\n    // Handle scope changes\n    if (stream.sol()) {\n      if (state.scope.align === null) state.scope.align = false;\n      var scopeOffset = state.scope.offset;\n      if (stream.eatSpace()) {\n        var lineOffset = stream.indentation();\n        if (lineOffset > scopeOffset && state.scope.type == \"coffee\") {\n          return \"indent\";\n        } else if (lineOffset < scopeOffset) {\n          return \"dedent\";\n        }\n        return null;\n      } else {\n        if (scopeOffset > 0) {\n          dedent(stream, state);\n        }\n      }\n    }\n    if (stream.eatSpace()) {\n      return null;\n    }\n\n    var ch = stream.peek();\n\n    // Handle docco title comment (single line)\n    if (stream.match(\"####\")) {\n      stream.skipToEnd();\n      return \"comment\";\n    }\n\n    // Handle multi line comments\n    if (stream.match(\"###\")) {\n      state.tokenize = longComment;\n      return state.tokenize(stream, state);\n    }\n\n    // Single line comment\n    if (ch === \"#\") {\n      stream.skipToEnd();\n      return \"comment\";\n    }\n\n    // Handle number literals\n    if (stream.match(/^-?[0-9\\.]/, false)) {\n      var floatLiteral = false;\n      // Floats\n      if (stream.match(/^-?\\d*\\.\\d+(e[\\+\\-]?\\d+)?/i)) {\n        floatLiteral = true;\n      }\n      if (stream.match(/^-?\\d+\\.\\d*/)) {\n        floatLiteral = true;\n      }\n      if (stream.match(/^-?\\.\\d+/)) {\n        floatLiteral = true;\n      }\n\n      if (floatLiteral) {\n        // prevent from getting extra . on 1..\n        if (stream.peek() == \".\"){\n          stream.backUp(1);\n        }\n        return \"number\";\n      }\n      // Integers\n      var intLiteral = false;\n      // Hex\n      if (stream.match(/^-?0x[0-9a-f]+/i)) {\n        intLiteral = true;\n      }\n      // Decimal\n      if (stream.match(/^-?[1-9]\\d*(e[\\+\\-]?\\d+)?/)) {\n        intLiteral = true;\n      }\n      // Zero by itself with no other piece of number.\n      if (stream.match(/^-?0(?![\\dx])/i)) {\n        intLiteral = true;\n      }\n      if (intLiteral) {\n        return \"number\";\n      }\n    }\n\n    // Handle strings\n    if (stream.match(stringPrefixes)) {\n      state.tokenize = tokenFactory(stream.current(), false, \"string\");\n      return state.tokenize(stream, state);\n    }\n    // Handle regex literals\n    if (stream.match(regexPrefixes)) {\n      if (stream.current() != \"/\" || stream.match(/^.*\\//, false)) { // prevent highlight of division\n        state.tokenize = tokenFactory(stream.current(), true, \"string-2\");\n        return state.tokenize(stream, state);\n      } else {\n        stream.backUp(1);\n      }\n    }\n\n\n\n    // Handle operators and delimiters\n    if (stream.match(operators) || stream.match(wordOperators)) {\n      return \"operator\";\n    }\n    if (stream.match(delimiters)) {\n      return \"punctuation\";\n    }\n\n    if (stream.match(constants)) {\n      return \"atom\";\n    }\n\n    if (stream.match(atProp) || state.prop && stream.match(identifiers)) {\n      return \"property\";\n    }\n\n    if (stream.match(keywords)) {\n      return \"keyword\";\n    }\n\n    if (stream.match(identifiers)) {\n      return \"variable\";\n    }\n\n    // Handle non-detected items\n    stream.next();\n    return ERRORCLASS;\n  }\n\n  function tokenFactory(delimiter, singleline, outclass) {\n    return function(stream, state) {\n      while (!stream.eol()) {\n        stream.eatWhile(/[^'\"\\/\\\\]/);\n        if (stream.eat(\"\\\\\")) {\n          stream.next();\n          if (singleline && stream.eol()) {\n            return outclass;\n          }\n        } else if (stream.match(delimiter)) {\n          state.tokenize = tokenBase;\n          return outclass;\n        } else {\n          stream.eat(/['\"\\/]/);\n        }\n      }\n      if (singleline) {\n        if (parserConf.singleLineStringErrors) {\n          outclass = ERRORCLASS;\n        } else {\n          state.tokenize = tokenBase;\n        }\n      }\n      return outclass;\n    };\n  }\n\n  function longComment(stream, state) {\n    while (!stream.eol()) {\n      stream.eatWhile(/[^#]/);\n      if (stream.match(\"###\")) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      stream.eatWhile(\"#\");\n    }\n    return \"comment\";\n  }\n\n  function indent(stream, state, type) {\n    type = type || \"coffee\";\n    var offset = 0, align = false, alignOffset = null;\n    for (var scope = state.scope; scope; scope = scope.prev) {\n      if (scope.type === \"coffee\" || scope.type == \"}\") {\n        offset = scope.offset + conf.indentUnit;\n        break;\n      }\n    }\n    if (type !== \"coffee\") {\n      align = null;\n      alignOffset = stream.column() + stream.current().length;\n    } else if (state.scope.align) {\n      state.scope.align = false;\n    }\n    state.scope = {\n      offset: offset,\n      type: type,\n      prev: state.scope,\n      align: align,\n      alignOffset: alignOffset\n    };\n  }\n\n  function dedent(stream, state) {\n    if (!state.scope.prev) return;\n    if (state.scope.type === \"coffee\") {\n      var _indent = stream.indentation();\n      var matched = false;\n      for (var scope = state.scope; scope; scope = scope.prev) {\n        if (_indent === scope.offset) {\n          matched = true;\n          break;\n        }\n      }\n      if (!matched) {\n        return true;\n      }\n      while (state.scope.prev && state.scope.offset !== _indent) {\n        state.scope = state.scope.prev;\n      }\n      return false;\n    } else {\n      state.scope = state.scope.prev;\n      return false;\n    }\n  }\n\n  function tokenLexer(stream, state) {\n    var style = state.tokenize(stream, state);\n    var current = stream.current();\n\n    // Handle scope changes.\n    if (current === \"return\") {\n      state.dedent = true;\n    }\n    if (((current === \"->\" || current === \"=>\") && stream.eol())\n        || style === \"indent\") {\n      indent(stream, state);\n    }\n    var delimiter_index = \"[({\".indexOf(current);\n    if (delimiter_index !== -1) {\n      indent(stream, state, \"])}\".slice(delimiter_index, delimiter_index+1));\n    }\n    if (indentKeywords.exec(current)){\n      indent(stream, state);\n    }\n    if (current == \"then\"){\n      dedent(stream, state);\n    }\n\n\n    if (style === \"dedent\") {\n      if (dedent(stream, state)) {\n        return ERRORCLASS;\n      }\n    }\n    delimiter_index = \"])}\".indexOf(current);\n    if (delimiter_index !== -1) {\n      while (state.scope.type == \"coffee\" && state.scope.prev)\n        state.scope = state.scope.prev;\n      if (state.scope.type == current)\n        state.scope = state.scope.prev;\n    }\n    if (state.dedent && stream.eol()) {\n      if (state.scope.type == \"coffee\" && state.scope.prev)\n        state.scope = state.scope.prev;\n      state.dedent = false;\n    }\n\n    return style;\n  }\n\n  var external = {\n    startState: function(basecolumn) {\n      return {\n        tokenize: tokenBase,\n        scope: {offset:basecolumn || 0, type:\"coffee\", prev: null, align: false},\n        prop: false,\n        dedent: 0\n      };\n    },\n\n    token: function(stream, state) {\n      var fillAlign = state.scope.align === null && state.scope;\n      if (fillAlign && stream.sol()) fillAlign.align = false;\n\n      var style = tokenLexer(stream, state);\n      if (style && style != \"comment\") {\n        if (fillAlign) fillAlign.align = true;\n        state.prop = style == \"punctuation\" && stream.current() == \".\"\n      }\n\n      return style;\n    },\n\n    indent: function(state, text) {\n      if (state.tokenize != tokenBase) return 0;\n      var scope = state.scope;\n      var closer = text && \"])}\".indexOf(text.charAt(0)) > -1;\n      if (closer) while (scope.type == \"coffee\" && scope.prev) scope = scope.prev;\n      var closes = closer && scope.type === text.charAt(0);\n      if (scope.align)\n        return scope.alignOffset - (closes ? 1 : 0);\n      else\n        return (closes ? scope.prev : scope).offset;\n    },\n\n    lineComment: \"#\",\n    fold: \"indent\"\n  };\n  return external;\n});\n\n// IANA registered media type\n// https://www.iana.org/assignments/media-types/\nCodeMirror.defineMIME(\"application/vnd.coffeescript\", \"coffeescript\");\n\nCodeMirror.defineMIME(\"text/x-coffeescript\", \"coffeescript\");\nCodeMirror.defineMIME(\"text/coffeescript\", \"coffeescript\");\n\n});\n\n\n/* ---- mode/css.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"css\", function(config, parserConfig) {\n  var inline = parserConfig.inline\n  if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode(\"text/css\");\n\n  var indentUnit = config.indentUnit,\n      tokenHooks = parserConfig.tokenHooks,\n      documentTypes = parserConfig.documentTypes || {},\n      mediaTypes = parserConfig.mediaTypes || {},\n      mediaFeatures = parserConfig.mediaFeatures || {},\n      mediaValueKeywords = parserConfig.mediaValueKeywords || {},\n      propertyKeywords = parserConfig.propertyKeywords || {},\n      nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},\n      fontProperties = parserConfig.fontProperties || {},\n      counterDescriptors = parserConfig.counterDescriptors || {},\n      colorKeywords = parserConfig.colorKeywords || {},\n      valueKeywords = parserConfig.valueKeywords || {},\n      allowNested = parserConfig.allowNested,\n      lineComment = parserConfig.lineComment,\n      supportsAtComponent = parserConfig.supportsAtComponent === true;\n\n  var type, override;\n  function ret(style, tp) { type = tp; return style; }\n\n  // Tokenizers\n\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (tokenHooks[ch]) {\n      var result = tokenHooks[ch](stream, state);\n      if (result !== false) return result;\n    }\n    if (ch == \"@\") {\n      stream.eatWhile(/[\\w\\\\\\-]/);\n      return ret(\"def\", stream.current());\n    } else if (ch == \"=\" || (ch == \"~\" || ch == \"|\") && stream.eat(\"=\")) {\n      return ret(null, \"compare\");\n    } else if (ch == \"\\\"\" || ch == \"'\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    } else if (ch == \"#\") {\n      stream.eatWhile(/[\\w\\\\\\-]/);\n      return ret(\"atom\", \"hash\");\n    } else if (ch == \"!\") {\n      stream.match(/^\\s*\\w*/);\n      return ret(\"keyword\", \"important\");\n    } else if (/\\d/.test(ch) || ch == \".\" && stream.eat(/\\d/)) {\n      stream.eatWhile(/[\\w.%]/);\n      return ret(\"number\", \"unit\");\n    } else if (ch === \"-\") {\n      if (/[\\d.]/.test(stream.peek())) {\n        stream.eatWhile(/[\\w.%]/);\n        return ret(\"number\", \"unit\");\n      } else if (stream.match(/^-[\\w\\\\\\-]*/)) {\n        stream.eatWhile(/[\\w\\\\\\-]/);\n        if (stream.match(/^\\s*:/, false))\n          return ret(\"variable-2\", \"variable-definition\");\n        return ret(\"variable-2\", \"variable\");\n      } else if (stream.match(/^\\w+-/)) {\n        return ret(\"meta\", \"meta\");\n      }\n    } else if (/[,+>*\\/]/.test(ch)) {\n      return ret(null, \"select-op\");\n    } else if (ch == \".\" && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {\n      return ret(\"qualifier\", \"qualifier\");\n    } else if (/[:;{}\\[\\]\\(\\)]/.test(ch)) {\n      return ret(null, ch);\n    } else if (stream.match(/[\\w-.]+(?=\\()/)) {\n      if (/^(url(-prefix)?|domain|regexp)$/.test(stream.current().toLowerCase())) {\n        state.tokenize = tokenParenthesized;\n      }\n      return ret(\"variable callee\", \"variable\");\n    } else if (/[\\w\\\\\\-]/.test(ch)) {\n      stream.eatWhile(/[\\w\\\\\\-]/);\n      return ret(\"property\", \"word\");\n    } else {\n      return ret(null, null);\n    }\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, ch;\n      while ((ch = stream.next()) != null) {\n        if (ch == quote && !escaped) {\n          if (quote == \")\") stream.backUp(1);\n          break;\n        }\n        escaped = !escaped && ch == \"\\\\\";\n      }\n      if (ch == quote || !escaped && quote != \")\") state.tokenize = null;\n      return ret(\"string\", \"string\");\n    };\n  }\n\n  function tokenParenthesized(stream, state) {\n    stream.next(); // Must be '('\n    if (!stream.match(/\\s*[\\\"\\')]/, false))\n      state.tokenize = tokenString(\")\");\n    else\n      state.tokenize = null;\n    return ret(null, \"(\");\n  }\n\n  // Context management\n\n  function Context(type, indent, prev) {\n    this.type = type;\n    this.indent = indent;\n    this.prev = prev;\n  }\n\n  function pushContext(state, stream, type, indent) {\n    state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);\n    return type;\n  }\n\n  function popContext(state) {\n    if (state.context.prev)\n      state.context = state.context.prev;\n    return state.context.type;\n  }\n\n  function pass(type, stream, state) {\n    return states[state.context.type](type, stream, state);\n  }\n  function popAndPass(type, stream, state, n) {\n    for (var i = n || 1; i > 0; i--)\n      state.context = state.context.prev;\n    return pass(type, stream, state);\n  }\n\n  // Parser\n\n  function wordAsValue(stream) {\n    var word = stream.current().toLowerCase();\n    if (valueKeywords.hasOwnProperty(word))\n      override = \"atom\";\n    else if (colorKeywords.hasOwnProperty(word))\n      override = \"keyword\";\n    else\n      override = \"variable\";\n  }\n\n  var states = {};\n\n  states.top = function(type, stream, state) {\n    if (type == \"{\") {\n      return pushContext(state, stream, \"block\");\n    } else if (type == \"}\" && state.context.prev) {\n      return popContext(state);\n    } else if (supportsAtComponent && /@component/i.test(type)) {\n      return pushContext(state, stream, \"atComponentBlock\");\n    } else if (/^@(-moz-)?document$/i.test(type)) {\n      return pushContext(state, stream, \"documentTypes\");\n    } else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {\n      return pushContext(state, stream, \"atBlock\");\n    } else if (/^@(font-face|counter-style)/i.test(type)) {\n      state.stateArg = type;\n      return \"restricted_atBlock_before\";\n    } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {\n      return \"keyframes\";\n    } else if (type && type.charAt(0) == \"@\") {\n      return pushContext(state, stream, \"at\");\n    } else if (type == \"hash\") {\n      override = \"builtin\";\n    } else if (type == \"word\") {\n      override = \"tag\";\n    } else if (type == \"variable-definition\") {\n      return \"maybeprop\";\n    } else if (type == \"interpolation\") {\n      return pushContext(state, stream, \"interpolation\");\n    } else if (type == \":\") {\n      return \"pseudo\";\n    } else if (allowNested && type == \"(\") {\n      return pushContext(state, stream, \"parens\");\n    }\n    return state.context.type;\n  };\n\n  states.block = function(type, stream, state) {\n    if (type == \"word\") {\n      var word = stream.current().toLowerCase();\n      if (propertyKeywords.hasOwnProperty(word)) {\n        override = \"property\";\n        return \"maybeprop\";\n      } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {\n        override = \"string-2\";\n        return \"maybeprop\";\n      } else if (allowNested) {\n        override = stream.match(/^\\s*:(?:\\s|$)/, false) ? \"property\" : \"tag\";\n        return \"block\";\n      } else {\n        override += \" error\";\n        return \"maybeprop\";\n      }\n    } else if (type == \"meta\") {\n      return \"block\";\n    } else if (!allowNested && (type == \"hash\" || type == \"qualifier\")) {\n      override = \"error\";\n      return \"block\";\n    } else {\n      return states.top(type, stream, state);\n    }\n  };\n\n  states.maybeprop = function(type, stream, state) {\n    if (type == \":\") return pushContext(state, stream, \"prop\");\n    return pass(type, stream, state);\n  };\n\n  states.prop = function(type, stream, state) {\n    if (type == \";\") return popContext(state);\n    if (type == \"{\" && allowNested) return pushContext(state, stream, \"propBlock\");\n    if (type == \"}\" || type == \"{\") return popAndPass(type, stream, state);\n    if (type == \"(\") return pushContext(state, stream, \"parens\");\n\n    if (type == \"hash\" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {\n      override += \" error\";\n    } else if (type == \"word\") {\n      wordAsValue(stream);\n    } else if (type == \"interpolation\") {\n      return pushContext(state, stream, \"interpolation\");\n    }\n    return \"prop\";\n  };\n\n  states.propBlock = function(type, _stream, state) {\n    if (type == \"}\") return popContext(state);\n    if (type == \"word\") { override = \"property\"; return \"maybeprop\"; }\n    return state.context.type;\n  };\n\n  states.parens = function(type, stream, state) {\n    if (type == \"{\" || type == \"}\") return popAndPass(type, stream, state);\n    if (type == \")\") return popContext(state);\n    if (type == \"(\") return pushContext(state, stream, \"parens\");\n    if (type == \"interpolation\") return pushContext(state, stream, \"interpolation\");\n    if (type == \"word\") wordAsValue(stream);\n    return \"parens\";\n  };\n\n  states.pseudo = function(type, stream, state) {\n    if (type == \"meta\") return \"pseudo\";\n\n    if (type == \"word\") {\n      override = \"variable-3\";\n      return state.context.type;\n    }\n    return pass(type, stream, state);\n  };\n\n  states.documentTypes = function(type, stream, state) {\n    if (type == \"word\" && documentTypes.hasOwnProperty(stream.current())) {\n      override = \"tag\";\n      return state.context.type;\n    } else {\n      return states.atBlock(type, stream, state);\n    }\n  };\n\n  states.atBlock = function(type, stream, state) {\n    if (type == \"(\") return pushContext(state, stream, \"atBlock_parens\");\n    if (type == \"}\" || type == \";\") return popAndPass(type, stream, state);\n    if (type == \"{\") return popContext(state) && pushContext(state, stream, allowNested ? \"block\" : \"top\");\n\n    if (type == \"interpolation\") return pushContext(state, stream, \"interpolation\");\n\n    if (type == \"word\") {\n      var word = stream.current().toLowerCase();\n      if (word == \"only\" || word == \"not\" || word == \"and\" || word == \"or\")\n        override = \"keyword\";\n      else if (mediaTypes.hasOwnProperty(word))\n        override = \"attribute\";\n      else if (mediaFeatures.hasOwnProperty(word))\n        override = \"property\";\n      else if (mediaValueKeywords.hasOwnProperty(word))\n        override = \"keyword\";\n      else if (propertyKeywords.hasOwnProperty(word))\n        override = \"property\";\n      else if (nonStandardPropertyKeywords.hasOwnProperty(word))\n        override = \"string-2\";\n      else if (valueKeywords.hasOwnProperty(word))\n        override = \"atom\";\n      else if (colorKeywords.hasOwnProperty(word))\n        override = \"keyword\";\n      else\n        override = \"error\";\n    }\n    return state.context.type;\n  };\n\n  states.atComponentBlock = function(type, stream, state) {\n    if (type == \"}\")\n      return popAndPass(type, stream, state);\n    if (type == \"{\")\n      return popContext(state) && pushContext(state, stream, allowNested ? \"block\" : \"top\", false);\n    if (type == \"word\")\n      override = \"error\";\n    return state.context.type;\n  };\n\n  states.atBlock_parens = function(type, stream, state) {\n    if (type == \")\") return popContext(state);\n    if (type == \"{\" || type == \"}\") return popAndPass(type, stream, state, 2);\n    return states.atBlock(type, stream, state);\n  };\n\n  states.restricted_atBlock_before = function(type, stream, state) {\n    if (type == \"{\")\n      return pushContext(state, stream, \"restricted_atBlock\");\n    if (type == \"word\" && state.stateArg == \"@counter-style\") {\n      override = \"variable\";\n      return \"restricted_atBlock_before\";\n    }\n    return pass(type, stream, state);\n  };\n\n  states.restricted_atBlock = function(type, stream, state) {\n    if (type == \"}\") {\n      state.stateArg = null;\n      return popContext(state);\n    }\n    if (type == \"word\") {\n      if ((state.stateArg == \"@font-face\" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||\n          (state.stateArg == \"@counter-style\" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))\n        override = \"error\";\n      else\n        override = \"property\";\n      return \"maybeprop\";\n    }\n    return \"restricted_atBlock\";\n  };\n\n  states.keyframes = function(type, stream, state) {\n    if (type == \"word\") { override = \"variable\"; return \"keyframes\"; }\n    if (type == \"{\") return pushContext(state, stream, \"top\");\n    return pass(type, stream, state);\n  };\n\n  states.at = function(type, stream, state) {\n    if (type == \";\") return popContext(state);\n    if (type == \"{\" || type == \"}\") return popAndPass(type, stream, state);\n    if (type == \"word\") override = \"tag\";\n    else if (type == \"hash\") override = \"builtin\";\n    return \"at\";\n  };\n\n  states.interpolation = function(type, stream, state) {\n    if (type == \"}\") return popContext(state);\n    if (type == \"{\" || type == \";\") return popAndPass(type, stream, state);\n    if (type == \"word\") override = \"variable\";\n    else if (type != \"variable\" && type != \"(\" && type != \")\") override = \"error\";\n    return \"interpolation\";\n  };\n\n  return {\n    startState: function(base) {\n      return {tokenize: null,\n              state: inline ? \"block\" : \"top\",\n              stateArg: null,\n              context: new Context(inline ? \"block\" : \"top\", base || 0, null)};\n    },\n\n    token: function(stream, state) {\n      if (!state.tokenize && stream.eatSpace()) return null;\n      var style = (state.tokenize || tokenBase)(stream, state);\n      if (style && typeof style == \"object\") {\n        type = style[1];\n        style = style[0];\n      }\n      override = style;\n      if (type != \"comment\")\n        state.state = states[state.state](type, stream, state);\n      return override;\n    },\n\n    indent: function(state, textAfter) {\n      var cx = state.context, ch = textAfter && textAfter.charAt(0);\n      var indent = cx.indent;\n      if (cx.type == \"prop\" && (ch == \"}\" || ch == \")\")) cx = cx.prev;\n      if (cx.prev) {\n        if (ch == \"}\" && (cx.type == \"block\" || cx.type == \"top\" ||\n                          cx.type == \"interpolation\" || cx.type == \"restricted_atBlock\")) {\n          // Resume indentation from parent context.\n          cx = cx.prev;\n          indent = cx.indent;\n        } else if (ch == \")\" && (cx.type == \"parens\" || cx.type == \"atBlock_parens\") ||\n            ch == \"{\" && (cx.type == \"at\" || cx.type == \"atBlock\")) {\n          // Dedent relative to current context.\n          indent = Math.max(0, cx.indent - indentUnit);\n        }\n      }\n      return indent;\n    },\n\n    electricChars: \"}\",\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    blockCommentContinue: \" * \",\n    lineComment: lineComment,\n    fold: \"brace\"\n  };\n});\n\n  function keySet(array) {\n    var keys = {};\n    for (var i = 0; i < array.length; ++i) {\n      keys[array[i].toLowerCase()] = true;\n    }\n    return keys;\n  }\n\n  var documentTypes_ = [\n    \"domain\", \"regexp\", \"url\", \"url-prefix\"\n  ], documentTypes = keySet(documentTypes_);\n\n  var mediaTypes_ = [\n    \"all\", \"aural\", \"braille\", \"handheld\", \"print\", \"projection\", \"screen\",\n    \"tty\", \"tv\", \"embossed\"\n  ], mediaTypes = keySet(mediaTypes_);\n\n  var mediaFeatures_ = [\n    \"width\", \"min-width\", \"max-width\", \"height\", \"min-height\", \"max-height\",\n    \"device-width\", \"min-device-width\", \"max-device-width\", \"device-height\",\n    \"min-device-height\", \"max-device-height\", \"aspect-ratio\",\n    \"min-aspect-ratio\", \"max-aspect-ratio\", \"device-aspect-ratio\",\n    \"min-device-aspect-ratio\", \"max-device-aspect-ratio\", \"color\", \"min-color\",\n    \"max-color\", \"color-index\", \"min-color-index\", \"max-color-index\",\n    \"monochrome\", \"min-monochrome\", \"max-monochrome\", \"resolution\",\n    \"min-resolution\", \"max-resolution\", \"scan\", \"grid\", \"orientation\",\n    \"device-pixel-ratio\", \"min-device-pixel-ratio\", \"max-device-pixel-ratio\",\n    \"pointer\", \"any-pointer\", \"hover\", \"any-hover\"\n  ], mediaFeatures = keySet(mediaFeatures_);\n\n  var mediaValueKeywords_ = [\n    \"landscape\", \"portrait\", \"none\", \"coarse\", \"fine\", \"on-demand\", \"hover\",\n    \"interlace\", \"progressive\"\n  ], mediaValueKeywords = keySet(mediaValueKeywords_);\n\n  var propertyKeywords_ = [\n    \"align-content\", \"align-items\", \"align-self\", \"alignment-adjust\",\n    \"alignment-baseline\", \"anchor-point\", \"animation\", \"animation-delay\",\n    \"animation-direction\", \"animation-duration\", \"animation-fill-mode\",\n    \"animation-iteration-count\", \"animation-name\", \"animation-play-state\",\n    \"animation-timing-function\", \"appearance\", \"azimuth\", \"backdrop-filter\",\n    \"backface-visibility\", \"background\", \"background-attachment\",\n    \"background-blend-mode\", \"background-clip\", \"background-color\",\n    \"background-image\", \"background-origin\", \"background-position\",\n    \"background-position-x\", \"background-position-y\", \"background-repeat\",\n    \"background-size\", \"baseline-shift\", \"binding\", \"bleed\", \"block-size\",\n    \"bookmark-label\", \"bookmark-level\", \"bookmark-state\", \"bookmark-target\",\n    \"border\", \"border-bottom\", \"border-bottom-color\", \"border-bottom-left-radius\",\n    \"border-bottom-right-radius\", \"border-bottom-style\", \"border-bottom-width\",\n    \"border-collapse\", \"border-color\", \"border-image\", \"border-image-outset\",\n    \"border-image-repeat\", \"border-image-slice\", \"border-image-source\",\n    \"border-image-width\", \"border-left\", \"border-left-color\", \"border-left-style\",\n    \"border-left-width\", \"border-radius\", \"border-right\", \"border-right-color\",\n    \"border-right-style\", \"border-right-width\", \"border-spacing\", \"border-style\",\n    \"border-top\", \"border-top-color\", \"border-top-left-radius\",\n    \"border-top-right-radius\", \"border-top-style\", \"border-top-width\",\n    \"border-width\", \"bottom\", \"box-decoration-break\", \"box-shadow\", \"box-sizing\",\n    \"break-after\", \"break-before\", \"break-inside\", \"caption-side\", \"caret-color\",\n    \"clear\", \"clip\", \"color\", \"color-profile\", \"column-count\", \"column-fill\",\n    \"column-gap\", \"column-rule\", \"column-rule-color\", \"column-rule-style\",\n    \"column-rule-width\", \"column-span\", \"column-width\", \"columns\", \"contain\",\n    \"content\", \"counter-increment\", \"counter-reset\", \"crop\", \"cue\", \"cue-after\",\n    \"cue-before\", \"cursor\", \"direction\", \"display\", \"dominant-baseline\",\n    \"drop-initial-after-adjust\", \"drop-initial-after-align\",\n    \"drop-initial-before-adjust\", \"drop-initial-before-align\", \"drop-initial-size\",\n    \"drop-initial-value\", \"elevation\", \"empty-cells\", \"fit\", \"fit-position\",\n    \"flex\", \"flex-basis\", \"flex-direction\", \"flex-flow\", \"flex-grow\",\n    \"flex-shrink\", \"flex-wrap\", \"float\", \"float-offset\", \"flow-from\", \"flow-into\",\n    \"font\", \"font-family\", \"font-feature-settings\", \"font-kerning\",\n    \"font-language-override\", \"font-optical-sizing\", \"font-size\",\n    \"font-size-adjust\", \"font-stretch\", \"font-style\", \"font-synthesis\",\n    \"font-variant\", \"font-variant-alternates\", \"font-variant-caps\",\n    \"font-variant-east-asian\", \"font-variant-ligatures\", \"font-variant-numeric\",\n    \"font-variant-position\", \"font-variation-settings\", \"font-weight\", \"gap\",\n    \"grid\", \"grid-area\", \"grid-auto-columns\", \"grid-auto-flow\", \"grid-auto-rows\",\n    \"grid-column\", \"grid-column-end\", \"grid-column-gap\", \"grid-column-start\",\n    \"grid-gap\", \"grid-row\", \"grid-row-end\", \"grid-row-gap\", \"grid-row-start\",\n    \"grid-template\", \"grid-template-areas\", \"grid-template-columns\",\n    \"grid-template-rows\", \"hanging-punctuation\", \"height\", \"hyphens\", \"icon\",\n    \"image-orientation\", \"image-rendering\", \"image-resolution\", \"inline-box-align\",\n    \"inset\", \"inset-block\", \"inset-block-end\", \"inset-block-start\", \"inset-inline\",\n    \"inset-inline-end\", \"inset-inline-start\", \"isolation\", \"justify-content\",\n    \"justify-items\", \"justify-self\", \"left\", \"letter-spacing\", \"line-break\",\n    \"line-height\", \"line-height-step\", \"line-stacking\", \"line-stacking-ruby\",\n    \"line-stacking-shift\", \"line-stacking-strategy\", \"list-style\",\n    \"list-style-image\", \"list-style-position\", \"list-style-type\", \"margin\",\n    \"margin-bottom\", \"margin-left\", \"margin-right\", \"margin-top\", \"marks\",\n    \"marquee-direction\", \"marquee-loop\", \"marquee-play-count\", \"marquee-speed\",\n    \"marquee-style\", \"max-block-size\", \"max-height\", \"max-inline-size\",\n    \"max-width\", \"min-block-size\", \"min-height\", \"min-inline-size\", \"min-width\",\n    \"mix-blend-mode\", \"move-to\", \"nav-down\", \"nav-index\", \"nav-left\", \"nav-right\",\n    \"nav-up\", \"object-fit\", \"object-position\", \"offset\", \"offset-anchor\",\n    \"offset-distance\", \"offset-path\", \"offset-position\", \"offset-rotate\",\n    \"opacity\", \"order\", \"orphans\", \"outline\", \"outline-color\", \"outline-offset\",\n    \"outline-style\", \"outline-width\", \"overflow\", \"overflow-style\",\n    \"overflow-wrap\", \"overflow-x\", \"overflow-y\", \"padding\", \"padding-bottom\",\n    \"padding-left\", \"padding-right\", \"padding-top\", \"page\", \"page-break-after\",\n    \"page-break-before\", \"page-break-inside\", \"page-policy\", \"pause\",\n    \"pause-after\", \"pause-before\", \"perspective\", \"perspective-origin\", \"pitch\",\n    \"pitch-range\", \"place-content\", \"place-items\", \"place-self\", \"play-during\",\n    \"position\", \"presentation-level\", \"punctuation-trim\", \"quotes\",\n    \"region-break-after\", \"region-break-before\", \"region-break-inside\",\n    \"region-fragment\", \"rendering-intent\", \"resize\", \"rest\", \"rest-after\",\n    \"rest-before\", \"richness\", \"right\", \"rotate\", \"rotation\", \"rotation-point\",\n    \"row-gap\", \"ruby-align\", \"ruby-overhang\", \"ruby-position\", \"ruby-span\",\n    \"scale\", \"scroll-behavior\", \"scroll-margin\", \"scroll-margin-block\",\n    \"scroll-margin-block-end\", \"scroll-margin-block-start\", \"scroll-margin-bottom\",\n    \"scroll-margin-inline\", \"scroll-margin-inline-end\",\n    \"scroll-margin-inline-start\", \"scroll-margin-left\", \"scroll-margin-right\",\n    \"scroll-margin-top\", \"scroll-padding\", \"scroll-padding-block\",\n    \"scroll-padding-block-end\", \"scroll-padding-block-start\",\n    \"scroll-padding-bottom\", \"scroll-padding-inline\", \"scroll-padding-inline-end\",\n    \"scroll-padding-inline-start\", \"scroll-padding-left\", \"scroll-padding-right\",\n    \"scroll-padding-top\", \"scroll-snap-align\", \"scroll-snap-type\",\n    \"shape-image-threshold\", \"shape-inside\", \"shape-margin\", \"shape-outside\",\n    \"size\", \"speak\", \"speak-as\", \"speak-header\", \"speak-numeral\",\n    \"speak-punctuation\", \"speech-rate\", \"stress\", \"string-set\", \"tab-size\",\n    \"table-layout\", \"target\", \"target-name\", \"target-new\", \"target-position\",\n    \"text-align\", \"text-align-last\", \"text-combine-upright\", \"text-decoration\",\n    \"text-decoration-color\", \"text-decoration-line\", \"text-decoration-skip\",\n    \"text-decoration-skip-ink\", \"text-decoration-style\", \"text-emphasis\",\n    \"text-emphasis-color\", \"text-emphasis-position\", \"text-emphasis-style\",\n    \"text-height\", \"text-indent\", \"text-justify\", \"text-orientation\",\n    \"text-outline\", \"text-overflow\", \"text-rendering\", \"text-shadow\",\n    \"text-size-adjust\", \"text-space-collapse\", \"text-transform\",\n    \"text-underline-position\", \"text-wrap\", \"top\", \"transform\", \"transform-origin\",\n    \"transform-style\", \"transition\", \"transition-delay\", \"transition-duration\",\n    \"transition-property\", \"transition-timing-function\", \"translate\",\n    \"unicode-bidi\", \"user-select\", \"vertical-align\", \"visibility\", \"voice-balance\",\n    \"voice-duration\", \"voice-family\", \"voice-pitch\", \"voice-range\", \"voice-rate\",\n    \"voice-stress\", \"voice-volume\", \"volume\", \"white-space\", \"widows\", \"width\",\n    \"will-change\", \"word-break\", \"word-spacing\", \"word-wrap\", \"writing-mode\", \"z-index\",\n    // SVG-specific\n    \"clip-path\", \"clip-rule\", \"mask\", \"enable-background\", \"filter\", \"flood-color\",\n    \"flood-opacity\", \"lighting-color\", \"stop-color\", \"stop-opacity\", \"pointer-events\",\n    \"color-interpolation\", \"color-interpolation-filters\",\n    \"color-rendering\", \"fill\", \"fill-opacity\", \"fill-rule\", \"image-rendering\",\n    \"marker\", \"marker-end\", \"marker-mid\", \"marker-start\", \"shape-rendering\", \"stroke\",\n    \"stroke-dasharray\", \"stroke-dashoffset\", \"stroke-linecap\", \"stroke-linejoin\",\n    \"stroke-miterlimit\", \"stroke-opacity\", \"stroke-width\", \"text-rendering\",\n    \"baseline-shift\", \"dominant-baseline\", \"glyph-orientation-horizontal\",\n    \"glyph-orientation-vertical\", \"text-anchor\", \"writing-mode\"\n  ], propertyKeywords = keySet(propertyKeywords_);\n\n  var nonStandardPropertyKeywords_ = [\n    \"border-block\", \"border-block-color\", \"border-block-end\",\n    \"border-block-end-color\", \"border-block-end-style\", \"border-block-end-width\",\n    \"border-block-start\", \"border-block-start-color\", \"border-block-start-style\",\n    \"border-block-start-width\", \"border-block-style\", \"border-block-width\",\n    \"border-inline\", \"border-inline-color\", \"border-inline-end\",\n    \"border-inline-end-color\", \"border-inline-end-style\",\n    \"border-inline-end-width\", \"border-inline-start\", \"border-inline-start-color\",\n    \"border-inline-start-style\", \"border-inline-start-width\",\n    \"border-inline-style\", \"border-inline-width\", \"margin-block\",\n    \"margin-block-end\", \"margin-block-start\", \"margin-inline\", \"margin-inline-end\",\n    \"margin-inline-start\", \"padding-block\", \"padding-block-end\",\n    \"padding-block-start\", \"padding-inline\", \"padding-inline-end\",\n    \"padding-inline-start\", \"scroll-snap-stop\", \"scrollbar-3d-light-color\",\n    \"scrollbar-arrow-color\", \"scrollbar-base-color\", \"scrollbar-dark-shadow-color\",\n    \"scrollbar-face-color\", \"scrollbar-highlight-color\", \"scrollbar-shadow-color\",\n    \"scrollbar-track-color\", \"searchfield-cancel-button\", \"searchfield-decoration\",\n    \"searchfield-results-button\", \"searchfield-results-decoration\", \"shape-inside\", \"zoom\"\n  ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);\n\n  var fontProperties_ = [\n    \"font-display\", \"font-family\", \"src\", \"unicode-range\", \"font-variant\",\n     \"font-feature-settings\", \"font-stretch\", \"font-weight\", \"font-style\"\n  ], fontProperties = keySet(fontProperties_);\n\n  var counterDescriptors_ = [\n    \"additive-symbols\", \"fallback\", \"negative\", \"pad\", \"prefix\", \"range\",\n    \"speak-as\", \"suffix\", \"symbols\", \"system\"\n  ], counterDescriptors = keySet(counterDescriptors_);\n\n  var colorKeywords_ = [\n    \"aliceblue\", \"antiquewhite\", \"aqua\", \"aquamarine\", \"azure\", \"beige\",\n    \"bisque\", \"black\", \"blanchedalmond\", \"blue\", \"blueviolet\", \"brown\",\n    \"burlywood\", \"cadetblue\", \"chartreuse\", \"chocolate\", \"coral\", \"cornflowerblue\",\n    \"cornsilk\", \"crimson\", \"cyan\", \"darkblue\", \"darkcyan\", \"darkgoldenrod\",\n    \"darkgray\", \"darkgreen\", \"darkkhaki\", \"darkmagenta\", \"darkolivegreen\",\n    \"darkorange\", \"darkorchid\", \"darkred\", \"darksalmon\", \"darkseagreen\",\n    \"darkslateblue\", \"darkslategray\", \"darkturquoise\", \"darkviolet\",\n    \"deeppink\", \"deepskyblue\", \"dimgray\", \"dodgerblue\", \"firebrick\",\n    \"floralwhite\", \"forestgreen\", \"fuchsia\", \"gainsboro\", \"ghostwhite\",\n    \"gold\", \"goldenrod\", \"gray\", \"grey\", \"green\", \"greenyellow\", \"honeydew\",\n    \"hotpink\", \"indianred\", \"indigo\", \"ivory\", \"khaki\", \"lavender\",\n    \"lavenderblush\", \"lawngreen\", \"lemonchiffon\", \"lightblue\", \"lightcoral\",\n    \"lightcyan\", \"lightgoldenrodyellow\", \"lightgray\", \"lightgreen\", \"lightpink\",\n    \"lightsalmon\", \"lightseagreen\", \"lightskyblue\", \"lightslategray\",\n    \"lightsteelblue\", \"lightyellow\", \"lime\", \"limegreen\", \"linen\", \"magenta\",\n    \"maroon\", \"mediumaquamarine\", \"mediumblue\", \"mediumorchid\", \"mediumpurple\",\n    \"mediumseagreen\", \"mediumslateblue\", \"mediumspringgreen\", \"mediumturquoise\",\n    \"mediumvioletred\", \"midnightblue\", \"mintcream\", \"mistyrose\", \"moccasin\",\n    \"navajowhite\", \"navy\", \"oldlace\", \"olive\", \"olivedrab\", \"orange\", \"orangered\",\n    \"orchid\", \"palegoldenrod\", \"palegreen\", \"paleturquoise\", \"palevioletred\",\n    \"papayawhip\", \"peachpuff\", \"peru\", \"pink\", \"plum\", \"powderblue\",\n    \"purple\", \"rebeccapurple\", \"red\", \"rosybrown\", \"royalblue\", \"saddlebrown\",\n    \"salmon\", \"sandybrown\", \"seagreen\", \"seashell\", \"sienna\", \"silver\", \"skyblue\",\n    \"slateblue\", \"slategray\", \"snow\", \"springgreen\", \"steelblue\", \"tan\",\n    \"teal\", \"thistle\", \"tomato\", \"turquoise\", \"violet\", \"wheat\", \"white\",\n    \"whitesmoke\", \"yellow\", \"yellowgreen\"\n  ], colorKeywords = keySet(colorKeywords_);\n\n  var valueKeywords_ = [\n    \"above\", \"absolute\", \"activeborder\", \"additive\", \"activecaption\", \"afar\",\n    \"after-white-space\", \"ahead\", \"alias\", \"all\", \"all-scroll\", \"alphabetic\", \"alternate\",\n    \"always\", \"amharic\", \"amharic-abegede\", \"antialiased\", \"appworkspace\",\n    \"arabic-indic\", \"armenian\", \"asterisks\", \"attr\", \"auto\", \"auto-flow\", \"avoid\", \"avoid-column\", \"avoid-page\",\n    \"avoid-region\", \"background\", \"backwards\", \"baseline\", \"below\", \"bidi-override\", \"binary\",\n    \"bengali\", \"blink\", \"block\", \"block-axis\", \"bold\", \"bolder\", \"border\", \"border-box\",\n    \"both\", \"bottom\", \"break\", \"break-all\", \"break-word\", \"bullets\", \"button\", \"button-bevel\",\n    \"buttonface\", \"buttonhighlight\", \"buttonshadow\", \"buttontext\", \"calc\", \"cambodian\",\n    \"capitalize\", \"caps-lock-indicator\", \"caption\", \"captiontext\", \"caret\",\n    \"cell\", \"center\", \"checkbox\", \"circle\", \"cjk-decimal\", \"cjk-earthly-branch\",\n    \"cjk-heavenly-stem\", \"cjk-ideographic\", \"clear\", \"clip\", \"close-quote\",\n    \"col-resize\", \"collapse\", \"color\", \"color-burn\", \"color-dodge\", \"column\", \"column-reverse\",\n    \"compact\", \"condensed\", \"contain\", \"content\", \"contents\",\n    \"content-box\", \"context-menu\", \"continuous\", \"copy\", \"counter\", \"counters\", \"cover\", \"crop\",\n    \"cross\", \"crosshair\", \"currentcolor\", \"cursive\", \"cyclic\", \"darken\", \"dashed\", \"decimal\",\n    \"decimal-leading-zero\", \"default\", \"default-button\", \"dense\", \"destination-atop\",\n    \"destination-in\", \"destination-out\", \"destination-over\", \"devanagari\", \"difference\",\n    \"disc\", \"discard\", \"disclosure-closed\", \"disclosure-open\", \"document\",\n    \"dot-dash\", \"dot-dot-dash\",\n    \"dotted\", \"double\", \"down\", \"e-resize\", \"ease\", \"ease-in\", \"ease-in-out\", \"ease-out\",\n    \"element\", \"ellipse\", \"ellipsis\", \"embed\", \"end\", \"ethiopic\", \"ethiopic-abegede\",\n    \"ethiopic-abegede-am-et\", \"ethiopic-abegede-gez\", \"ethiopic-abegede-ti-er\",\n    \"ethiopic-abegede-ti-et\", \"ethiopic-halehame-aa-er\",\n    \"ethiopic-halehame-aa-et\", \"ethiopic-halehame-am-et\",\n    \"ethiopic-halehame-gez\", \"ethiopic-halehame-om-et\",\n    \"ethiopic-halehame-sid-et\", \"ethiopic-halehame-so-et\",\n    \"ethiopic-halehame-ti-er\", \"ethiopic-halehame-ti-et\", \"ethiopic-halehame-tig\",\n    \"ethiopic-numeric\", \"ew-resize\", \"exclusion\", \"expanded\", \"extends\", \"extra-condensed\",\n    \"extra-expanded\", \"fantasy\", \"fast\", \"fill\", \"fixed\", \"flat\", \"flex\", \"flex-end\", \"flex-start\", \"footnotes\",\n    \"forwards\", \"from\", \"geometricPrecision\", \"georgian\", \"graytext\", \"grid\", \"groove\",\n    \"gujarati\", \"gurmukhi\", \"hand\", \"hangul\", \"hangul-consonant\", \"hard-light\", \"hebrew\",\n    \"help\", \"hidden\", \"hide\", \"higher\", \"highlight\", \"highlighttext\",\n    \"hiragana\", \"hiragana-iroha\", \"horizontal\", \"hsl\", \"hsla\", \"hue\", \"icon\", \"ignore\",\n    \"inactiveborder\", \"inactivecaption\", \"inactivecaptiontext\", \"infinite\",\n    \"infobackground\", \"infotext\", \"inherit\", \"initial\", \"inline\", \"inline-axis\",\n    \"inline-block\", \"inline-flex\", \"inline-grid\", \"inline-table\", \"inset\", \"inside\", \"intrinsic\", \"invert\",\n    \"italic\", \"japanese-formal\", \"japanese-informal\", \"justify\", \"kannada\",\n    \"katakana\", \"katakana-iroha\", \"keep-all\", \"khmer\",\n    \"korean-hangul-formal\", \"korean-hanja-formal\", \"korean-hanja-informal\",\n    \"landscape\", \"lao\", \"large\", \"larger\", \"left\", \"level\", \"lighter\", \"lighten\",\n    \"line-through\", \"linear\", \"linear-gradient\", \"lines\", \"list-item\", \"listbox\", \"listitem\",\n    \"local\", \"logical\", \"loud\", \"lower\", \"lower-alpha\", \"lower-armenian\",\n    \"lower-greek\", \"lower-hexadecimal\", \"lower-latin\", \"lower-norwegian\",\n    \"lower-roman\", \"lowercase\", \"ltr\", \"luminosity\", \"malayalam\", \"match\", \"matrix\", \"matrix3d\",\n    \"media-controls-background\", \"media-current-time-display\",\n    \"media-fullscreen-button\", \"media-mute-button\", \"media-play-button\",\n    \"media-return-to-realtime-button\", \"media-rewind-button\",\n    \"media-seek-back-button\", \"media-seek-forward-button\", \"media-slider\",\n    \"media-sliderthumb\", \"media-time-remaining-display\", \"media-volume-slider\",\n    \"media-volume-slider-container\", \"media-volume-sliderthumb\", \"medium\",\n    \"menu\", \"menulist\", \"menulist-button\", \"menulist-text\",\n    \"menulist-textfield\", \"menutext\", \"message-box\", \"middle\", \"min-intrinsic\",\n    \"mix\", \"mongolian\", \"monospace\", \"move\", \"multiple\", \"multiply\", \"myanmar\", \"n-resize\",\n    \"narrower\", \"ne-resize\", \"nesw-resize\", \"no-close-quote\", \"no-drop\",\n    \"no-open-quote\", \"no-repeat\", \"none\", \"normal\", \"not-allowed\", \"nowrap\",\n    \"ns-resize\", \"numbers\", \"numeric\", \"nw-resize\", \"nwse-resize\", \"oblique\", \"octal\", \"opacity\", \"open-quote\",\n    \"optimizeLegibility\", \"optimizeSpeed\", \"oriya\", \"oromo\", \"outset\",\n    \"outside\", \"outside-shape\", \"overlay\", \"overline\", \"padding\", \"padding-box\",\n    \"painted\", \"page\", \"paused\", \"persian\", \"perspective\", \"plus-darker\", \"plus-lighter\",\n    \"pointer\", \"polygon\", \"portrait\", \"pre\", \"pre-line\", \"pre-wrap\", \"preserve-3d\",\n    \"progress\", \"push-button\", \"radial-gradient\", \"radio\", \"read-only\",\n    \"read-write\", \"read-write-plaintext-only\", \"rectangle\", \"region\",\n    \"relative\", \"repeat\", \"repeating-linear-gradient\",\n    \"repeating-radial-gradient\", \"repeat-x\", \"repeat-y\", \"reset\", \"reverse\",\n    \"rgb\", \"rgba\", \"ridge\", \"right\", \"rotate\", \"rotate3d\", \"rotateX\", \"rotateY\",\n    \"rotateZ\", \"round\", \"row\", \"row-resize\", \"row-reverse\", \"rtl\", \"run-in\", \"running\",\n    \"s-resize\", \"sans-serif\", \"saturation\", \"scale\", \"scale3d\", \"scaleX\", \"scaleY\", \"scaleZ\", \"screen\",\n    \"scroll\", \"scrollbar\", \"scroll-position\", \"se-resize\", \"searchfield\",\n    \"searchfield-cancel-button\", \"searchfield-decoration\",\n    \"searchfield-results-button\", \"searchfield-results-decoration\", \"self-start\", \"self-end\",\n    \"semi-condensed\", \"semi-expanded\", \"separate\", \"serif\", \"show\", \"sidama\",\n    \"simp-chinese-formal\", \"simp-chinese-informal\", \"single\",\n    \"skew\", \"skewX\", \"skewY\", \"skip-white-space\", \"slide\", \"slider-horizontal\",\n    \"slider-vertical\", \"sliderthumb-horizontal\", \"sliderthumb-vertical\", \"slow\",\n    \"small\", \"small-caps\", \"small-caption\", \"smaller\", \"soft-light\", \"solid\", \"somali\",\n    \"source-atop\", \"source-in\", \"source-out\", \"source-over\", \"space\", \"space-around\", \"space-between\", \"space-evenly\", \"spell-out\", \"square\",\n    \"square-button\", \"start\", \"static\", \"status-bar\", \"stretch\", \"stroke\", \"sub\",\n    \"subpixel-antialiased\", \"super\", \"sw-resize\", \"symbolic\", \"symbols\", \"system-ui\", \"table\",\n    \"table-caption\", \"table-cell\", \"table-column\", \"table-column-group\",\n    \"table-footer-group\", \"table-header-group\", \"table-row\", \"table-row-group\",\n    \"tamil\",\n    \"telugu\", \"text\", \"text-bottom\", \"text-top\", \"textarea\", \"textfield\", \"thai\",\n    \"thick\", \"thin\", \"threeddarkshadow\", \"threedface\", \"threedhighlight\",\n    \"threedlightshadow\", \"threedshadow\", \"tibetan\", \"tigre\", \"tigrinya-er\",\n    \"tigrinya-er-abegede\", \"tigrinya-et\", \"tigrinya-et-abegede\", \"to\", \"top\",\n    \"trad-chinese-formal\", \"trad-chinese-informal\", \"transform\",\n    \"translate\", \"translate3d\", \"translateX\", \"translateY\", \"translateZ\",\n    \"transparent\", \"ultra-condensed\", \"ultra-expanded\", \"underline\", \"unset\", \"up\",\n    \"upper-alpha\", \"upper-armenian\", \"upper-greek\", \"upper-hexadecimal\",\n    \"upper-latin\", \"upper-norwegian\", \"upper-roman\", \"uppercase\", \"urdu\", \"url\",\n    \"var\", \"vertical\", \"vertical-text\", \"visible\", \"visibleFill\", \"visiblePainted\",\n    \"visibleStroke\", \"visual\", \"w-resize\", \"wait\", \"wave\", \"wider\",\n    \"window\", \"windowframe\", \"windowtext\", \"words\", \"wrap\", \"wrap-reverse\", \"x-large\", \"x-small\", \"xor\",\n    \"xx-large\", \"xx-small\"\n  ], valueKeywords = keySet(valueKeywords_);\n\n  var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)\n    .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)\n    .concat(valueKeywords_);\n  CodeMirror.registerHelper(\"hintWords\", \"css\", allWords);\n\n  function tokenCComment(stream, state) {\n    var maybeEnd = false, ch;\n    while ((ch = stream.next()) != null) {\n      if (maybeEnd && ch == \"/\") {\n        state.tokenize = null;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return [\"comment\", \"comment\"];\n  }\n\n  CodeMirror.defineMIME(\"text/css\", {\n    documentTypes: documentTypes,\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    mediaValueKeywords: mediaValueKeywords,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    fontProperties: fontProperties,\n    counterDescriptors: counterDescriptors,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (!stream.eat(\"*\")) return false;\n        state.tokenize = tokenCComment;\n        return tokenCComment(stream, state);\n      }\n    },\n    name: \"css\"\n  });\n\n  CodeMirror.defineMIME(\"text/x-scss\", {\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    mediaValueKeywords: mediaValueKeywords,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    fontProperties: fontProperties,\n    allowNested: true,\n    lineComment: \"//\",\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (stream.eat(\"/\")) {\n          stream.skipToEnd();\n          return [\"comment\", \"comment\"];\n        } else if (stream.eat(\"*\")) {\n          state.tokenize = tokenCComment;\n          return tokenCComment(stream, state);\n        } else {\n          return [\"operator\", \"operator\"];\n        }\n      },\n      \":\": function(stream) {\n        if (stream.match(/\\s*\\{/, false))\n          return [null, null]\n        return false;\n      },\n      \"$\": function(stream) {\n        stream.match(/^[\\w-]+/);\n        if (stream.match(/^\\s*:/, false))\n          return [\"variable-2\", \"variable-definition\"];\n        return [\"variable-2\", \"variable\"];\n      },\n      \"#\": function(stream) {\n        if (!stream.eat(\"{\")) return false;\n        return [null, \"interpolation\"];\n      }\n    },\n    name: \"css\",\n    helperType: \"scss\"\n  });\n\n  CodeMirror.defineMIME(\"text/x-less\", {\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    mediaValueKeywords: mediaValueKeywords,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    fontProperties: fontProperties,\n    allowNested: true,\n    lineComment: \"//\",\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (stream.eat(\"/\")) {\n          stream.skipToEnd();\n          return [\"comment\", \"comment\"];\n        } else if (stream.eat(\"*\")) {\n          state.tokenize = tokenCComment;\n          return tokenCComment(stream, state);\n        } else {\n          return [\"operator\", \"operator\"];\n        }\n      },\n      \"@\": function(stream) {\n        if (stream.eat(\"{\")) return [null, \"interpolation\"];\n        if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\\b/i, false)) return false;\n        stream.eatWhile(/[\\w\\\\\\-]/);\n        if (stream.match(/^\\s*:/, false))\n          return [\"variable-2\", \"variable-definition\"];\n        return [\"variable-2\", \"variable\"];\n      },\n      \"&\": function() {\n        return [\"atom\", \"atom\"];\n      }\n    },\n    name: \"css\",\n    helperType: \"less\"\n  });\n\n  CodeMirror.defineMIME(\"text/x-gss\", {\n    documentTypes: documentTypes,\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    fontProperties: fontProperties,\n    counterDescriptors: counterDescriptors,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    supportsAtComponent: true,\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (!stream.eat(\"*\")) return false;\n        state.tokenize = tokenCComment;\n        return tokenCComment(stream, state);\n      }\n    },\n    name: \"css\",\n    helperType: \"gss\"\n  });\n\n});\n\n\n/* ---- mode/go.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"go\", function(config) {\n  var indentUnit = config.indentUnit;\n\n  var keywords = {\n    \"break\":true, \"case\":true, \"chan\":true, \"const\":true, \"continue\":true,\n    \"default\":true, \"defer\":true, \"else\":true, \"fallthrough\":true, \"for\":true,\n    \"func\":true, \"go\":true, \"goto\":true, \"if\":true, \"import\":true,\n    \"interface\":true, \"map\":true, \"package\":true, \"range\":true, \"return\":true,\n    \"select\":true, \"struct\":true, \"switch\":true, \"type\":true, \"var\":true,\n    \"bool\":true, \"byte\":true, \"complex64\":true, \"complex128\":true,\n    \"float32\":true, \"float64\":true, \"int8\":true, \"int16\":true, \"int32\":true,\n    \"int64\":true, \"string\":true, \"uint8\":true, \"uint16\":true, \"uint32\":true,\n    \"uint64\":true, \"int\":true, \"uint\":true, \"uintptr\":true, \"error\": true,\n    \"rune\":true\n  };\n\n  var atoms = {\n    \"true\":true, \"false\":true, \"iota\":true, \"nil\":true, \"append\":true,\n    \"cap\":true, \"close\":true, \"complex\":true, \"copy\":true, \"delete\":true, \"imag\":true,\n    \"len\":true, \"make\":true, \"new\":true, \"panic\":true, \"print\":true,\n    \"println\":true, \"real\":true, \"recover\":true\n  };\n\n  var isOperatorChar = /[+\\-*&^%:=<>!|\\/]/;\n\n  var curPunc;\n\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (ch == '\"' || ch == \"'\" || ch == \"`\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    }\n    if (/[\\d\\.]/.test(ch)) {\n      if (ch == \".\") {\n        stream.match(/^[0-9]+([eE][\\-+]?[0-9]+)?/);\n      } else if (ch == \"0\") {\n        stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);\n      } else {\n        stream.match(/^[0-9]*\\.?[0-9]*([eE][\\-+]?[0-9]+)?/);\n      }\n      return \"number\";\n    }\n    if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch)) {\n      curPunc = ch;\n      return null;\n    }\n    if (ch == \"/\") {\n      if (stream.eat(\"*\")) {\n        state.tokenize = tokenComment;\n        return tokenComment(stream, state);\n      }\n      if (stream.eat(\"/\")) {\n        stream.skipToEnd();\n        return \"comment\";\n      }\n    }\n    if (isOperatorChar.test(ch)) {\n      stream.eatWhile(isOperatorChar);\n      return \"operator\";\n    }\n    stream.eatWhile(/[\\w\\$_\\xa1-\\uffff]/);\n    var cur = stream.current();\n    if (keywords.propertyIsEnumerable(cur)) {\n      if (cur == \"case\" || cur == \"default\") curPunc = \"case\";\n      return \"keyword\";\n    }\n    if (atoms.propertyIsEnumerable(cur)) return \"atom\";\n    return \"variable\";\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, next, end = false;\n      while ((next = stream.next()) != null) {\n        if (next == quote && !escaped) {end = true; break;}\n        escaped = !escaped && quote != \"`\" && next == \"\\\\\";\n      }\n      if (end || !(escaped || quote == \"`\"))\n        state.tokenize = tokenBase;\n      return \"string\";\n    };\n  }\n\n  function tokenComment(stream, state) {\n    var maybeEnd = false, ch;\n    while (ch = stream.next()) {\n      if (ch == \"/\" && maybeEnd) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return \"comment\";\n  }\n\n  function Context(indented, column, type, align, prev) {\n    this.indented = indented;\n    this.column = column;\n    this.type = type;\n    this.align = align;\n    this.prev = prev;\n  }\n  function pushContext(state, col, type) {\n    return state.context = new Context(state.indented, col, type, null, state.context);\n  }\n  function popContext(state) {\n    if (!state.context.prev) return;\n    var t = state.context.type;\n    if (t == \")\" || t == \"]\" || t == \"}\")\n      state.indented = state.context.indented;\n    return state.context = state.context.prev;\n  }\n\n  // Interface\n\n  return {\n    startState: function(basecolumn) {\n      return {\n        tokenize: null,\n        context: new Context((basecolumn || 0) - indentUnit, 0, \"top\", false),\n        indented: 0,\n        startOfLine: true\n      };\n    },\n\n    token: function(stream, state) {\n      var ctx = state.context;\n      if (stream.sol()) {\n        if (ctx.align == null) ctx.align = false;\n        state.indented = stream.indentation();\n        state.startOfLine = true;\n        if (ctx.type == \"case\") ctx.type = \"}\";\n      }\n      if (stream.eatSpace()) return null;\n      curPunc = null;\n      var style = (state.tokenize || tokenBase)(stream, state);\n      if (style == \"comment\") return style;\n      if (ctx.align == null) ctx.align = true;\n\n      if (curPunc == \"{\") pushContext(state, stream.column(), \"}\");\n      else if (curPunc == \"[\") pushContext(state, stream.column(), \"]\");\n      else if (curPunc == \"(\") pushContext(state, stream.column(), \")\");\n      else if (curPunc == \"case\") ctx.type = \"case\";\n      else if (curPunc == \"}\" && ctx.type == \"}\") popContext(state);\n      else if (curPunc == ctx.type) popContext(state);\n      state.startOfLine = false;\n      return style;\n    },\n\n    indent: function(state, textAfter) {\n      if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;\n      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);\n      if (ctx.type == \"case\" && /^(?:case|default)\\b/.test(textAfter)) {\n        state.context.type = \"}\";\n        return ctx.indented;\n      }\n      var closing = firstChar == ctx.type;\n      if (ctx.align) return ctx.column + (closing ? 0 : 1);\n      else return ctx.indented + (closing ? 0 : indentUnit);\n    },\n\n    electricChars: \"{}):\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n    fold: \"brace\",\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    lineComment: \"//\"\n  };\n});\n\nCodeMirror.defineMIME(\"text/x-go\", \"go\");\n\n});\n\n\n/* ---- mode/htmlembedded.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../htmlmixed/htmlmixed\"),\n        require(\"../../addon/mode/multiplex\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../htmlmixed/htmlmixed\",\n            \"../../addon/mode/multiplex\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineMode(\"htmlembedded\", function(config, parserConfig) {\n    var closeComment = parserConfig.closeComment || \"--%>\"\n    return CodeMirror.multiplexingMode(CodeMirror.getMode(config, \"htmlmixed\"), {\n      open: parserConfig.openComment || \"<%--\",\n      close: closeComment,\n      delimStyle: \"comment\",\n      mode: {token: function(stream) {\n        stream.skipTo(closeComment) || stream.skipToEnd()\n        return \"comment\"\n      }}\n    }, {\n      open: parserConfig.open || parserConfig.scriptStartRegex || \"<%\",\n      close: parserConfig.close || parserConfig.scriptEndRegex || \"%>\",\n      mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec)\n    });\n  }, \"htmlmixed\");\n\n  CodeMirror.defineMIME(\"application/x-ejs\", {name: \"htmlembedded\", scriptingModeSpec:\"javascript\"});\n  CodeMirror.defineMIME(\"application/x-aspx\", {name: \"htmlembedded\", scriptingModeSpec:\"text/x-csharp\"});\n  CodeMirror.defineMIME(\"application/x-jsp\", {name: \"htmlembedded\", scriptingModeSpec:\"text/x-java\"});\n  CodeMirror.defineMIME(\"application/x-erb\", {name: \"htmlembedded\", scriptingModeSpec:\"ruby\"});\n});\n\n\n/* ---- mode/htmlmixed.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../xml/xml\"), require(\"../javascript/javascript\"), require(\"../css/css\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../xml/xml\", \"../javascript/javascript\", \"../css/css\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var defaultTags = {\n    script: [\n      [\"lang\", /(javascript|babel)/i, \"javascript\"],\n      [\"type\", /^(?:text|application)\\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, \"javascript\"],\n      [\"type\", /./, \"text/plain\"],\n      [null, null, \"javascript\"]\n    ],\n    style:  [\n      [\"lang\", /^css$/i, \"css\"],\n      [\"type\", /^(text\\/)?(x-)?(stylesheet|css)$/i, \"css\"],\n      [\"type\", /./, \"text/plain\"],\n      [null, null, \"css\"]\n    ]\n  };\n\n  function maybeBackup(stream, pat, style) {\n    var cur = stream.current(), close = cur.search(pat);\n    if (close > -1) {\n      stream.backUp(cur.length - close);\n    } else if (cur.match(/<\\/?$/)) {\n      stream.backUp(cur.length);\n      if (!stream.match(pat, false)) stream.match(cur);\n    }\n    return style;\n  }\n\n  var attrRegexpCache = {};\n  function getAttrRegexp(attr) {\n    var regexp = attrRegexpCache[attr];\n    if (regexp) return regexp;\n    return attrRegexpCache[attr] = new RegExp(\"\\\\s+\" + attr + \"\\\\s*=\\\\s*('|\\\")?([^'\\\"]+)('|\\\")?\\\\s*\");\n  }\n\n  function getAttrValue(text, attr) {\n    var match = text.match(getAttrRegexp(attr))\n    return match ? /^\\s*(.*?)\\s*$/.exec(match[2])[1] : \"\"\n  }\n\n  function getTagRegexp(tagName, anchored) {\n    return new RegExp((anchored ? \"^\" : \"\") + \"<\\/\\s*\" + tagName + \"\\s*>\", \"i\");\n  }\n\n  function addTags(from, to) {\n    for (var tag in from) {\n      var dest = to[tag] || (to[tag] = []);\n      var source = from[tag];\n      for (var i = source.length - 1; i >= 0; i--)\n        dest.unshift(source[i])\n    }\n  }\n\n  function findMatchingMode(tagInfo, tagText) {\n    for (var i = 0; i < tagInfo.length; i++) {\n      var spec = tagInfo[i];\n      if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];\n    }\n  }\n\n  CodeMirror.defineMode(\"htmlmixed\", function (config, parserConfig) {\n    var htmlMode = CodeMirror.getMode(config, {\n      name: \"xml\",\n      htmlMode: true,\n      multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,\n      multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag\n    });\n\n    var tags = {};\n    var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;\n    addTags(defaultTags, tags);\n    if (configTags) addTags(configTags, tags);\n    if (configScript) for (var i = configScript.length - 1; i >= 0; i--)\n      tags.script.unshift([\"type\", configScript[i].matches, configScript[i].mode])\n\n    function html(stream, state) {\n      var style = htmlMode.token(stream, state.htmlState), tag = /\\btag\\b/.test(style), tagName\n      if (tag && !/[<>\\s\\/]/.test(stream.current()) &&\n          (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&\n          tags.hasOwnProperty(tagName)) {\n        state.inTag = tagName + \" \"\n      } else if (state.inTag && tag && />$/.test(stream.current())) {\n        var inTag = /^([\\S]+) (.*)/.exec(state.inTag)\n        state.inTag = null\n        var modeSpec = stream.current() == \">\" && findMatchingMode(tags[inTag[1]], inTag[2])\n        var mode = CodeMirror.getMode(config, modeSpec)\n        var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);\n        state.token = function (stream, state) {\n          if (stream.match(endTagA, false)) {\n            state.token = html;\n            state.localState = state.localMode = null;\n            return null;\n          }\n          return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));\n        };\n        state.localMode = mode;\n        state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, \"\", \"\"));\n      } else if (state.inTag) {\n        state.inTag += stream.current()\n        if (stream.eol()) state.inTag += \" \"\n      }\n      return style;\n    };\n\n    return {\n      startState: function () {\n        var state = CodeMirror.startState(htmlMode);\n        return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};\n      },\n\n      copyState: function (state) {\n        var local;\n        if (state.localState) {\n          local = CodeMirror.copyState(state.localMode, state.localState);\n        }\n        return {token: state.token, inTag: state.inTag,\n                localMode: state.localMode, localState: local,\n                htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};\n      },\n\n      token: function (stream, state) {\n        return state.token(stream, state);\n      },\n\n      indent: function (state, textAfter, line) {\n        if (!state.localMode || /^\\s*<\\//.test(textAfter))\n          return htmlMode.indent(state.htmlState, textAfter, line);\n        else if (state.localMode.indent)\n          return state.localMode.indent(state.localState, textAfter, line);\n        else\n          return CodeMirror.Pass;\n      },\n\n      innerMode: function (state) {\n        return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};\n      }\n    };\n  }, \"xml\", \"javascript\", \"css\");\n\n  CodeMirror.defineMIME(\"text/html\", \"htmlmixed\");\n});\n\n\n/* ---- mode/javascript.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"javascript\", function(config, parserConfig) {\n  var indentUnit = config.indentUnit;\n  var statementIndent = parserConfig.statementIndent;\n  var jsonldMode = parserConfig.jsonld;\n  var jsonMode = parserConfig.json || jsonldMode;\n  var isTS = parserConfig.typescript;\n  var wordRE = parserConfig.wordCharacters || /[\\w$\\xa1-\\uffff]/;\n\n  // Tokenizer\n\n  var keywords = function(){\n    function kw(type) {return {type: type, style: \"keyword\"};}\n    var A = kw(\"keyword a\"), B = kw(\"keyword b\"), C = kw(\"keyword c\"), D = kw(\"keyword d\");\n    var operator = kw(\"operator\"), atom = {type: \"atom\", style: \"atom\"};\n\n    return {\n      \"if\": kw(\"if\"), \"while\": A, \"with\": A, \"else\": B, \"do\": B, \"try\": B, \"finally\": B,\n      \"return\": D, \"break\": D, \"continue\": D, \"new\": kw(\"new\"), \"delete\": C, \"void\": C, \"throw\": C,\n      \"debugger\": kw(\"debugger\"), \"var\": kw(\"var\"), \"const\": kw(\"var\"), \"let\": kw(\"var\"),\n      \"function\": kw(\"function\"), \"catch\": kw(\"catch\"),\n      \"for\": kw(\"for\"), \"switch\": kw(\"switch\"), \"case\": kw(\"case\"), \"default\": kw(\"default\"),\n      \"in\": operator, \"typeof\": operator, \"instanceof\": operator,\n      \"true\": atom, \"false\": atom, \"null\": atom, \"undefined\": atom, \"NaN\": atom, \"Infinity\": atom,\n      \"this\": kw(\"this\"), \"class\": kw(\"class\"), \"super\": kw(\"atom\"),\n      \"yield\": C, \"export\": kw(\"export\"), \"import\": kw(\"import\"), \"extends\": C,\n      \"await\": C\n    };\n  }();\n\n  var isOperatorChar = /[+\\-*&%=<>!?|~^@]/;\n  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)\"/;\n\n  function readRegexp(stream) {\n    var escaped = false, next, inSet = false;\n    while ((next = stream.next()) != null) {\n      if (!escaped) {\n        if (next == \"/\" && !inSet) return;\n        if (next == \"[\") inSet = true;\n        else if (inSet && next == \"]\") inSet = false;\n      }\n      escaped = !escaped && next == \"\\\\\";\n    }\n  }\n\n  // Used as scratch variables to communicate multiple values without\n  // consing up tons of objects.\n  var type, content;\n  function ret(tp, style, cont) {\n    type = tp; content = cont;\n    return style;\n  }\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (ch == '\"' || ch == \"'\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    } else if (ch == \".\" && stream.match(/^\\d[\\d_]*(?:[eE][+\\-]?[\\d_]+)?/)) {\n      return ret(\"number\", \"number\");\n    } else if (ch == \".\" && stream.match(\"..\")) {\n      return ret(\"spread\", \"meta\");\n    } else if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch)) {\n      return ret(ch);\n    } else if (ch == \"=\" && stream.eat(\">\")) {\n      return ret(\"=>\", \"operator\");\n    } else if (ch == \"0\" && stream.match(/^(?:x[\\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {\n      return ret(\"number\", \"number\");\n    } else if (/\\d/.test(ch)) {\n      stream.match(/^[\\d_]*(?:n|(?:\\.[\\d_]*)?(?:[eE][+\\-]?[\\d_]+)?)?/);\n      return ret(\"number\", \"number\");\n    } else if (ch == \"/\") {\n      if (stream.eat(\"*\")) {\n        state.tokenize = tokenComment;\n        return tokenComment(stream, state);\n      } else if (stream.eat(\"/\")) {\n        stream.skipToEnd();\n        return ret(\"comment\", \"comment\");\n      } else if (expressionAllowed(stream, state, 1)) {\n        readRegexp(stream);\n        stream.match(/^\\b(([gimyus])(?![gimyus]*\\2))+\\b/);\n        return ret(\"regexp\", \"string-2\");\n      } else {\n        stream.eat(\"=\");\n        return ret(\"operator\", \"operator\", stream.current());\n      }\n    } else if (ch == \"`\") {\n      state.tokenize = tokenQuasi;\n      return tokenQuasi(stream, state);\n    } else if (ch == \"#\" && stream.peek() == \"!\") {\n      stream.skipToEnd();\n      return ret(\"meta\", \"meta\");\n    } else if (ch == \"#\" && stream.eatWhile(wordRE)) {\n      return ret(\"variable\", \"property\")\n    } else if (ch == \"<\" && stream.match(\"!--\") ||\n               (ch == \"-\" && stream.match(\"->\") && !/\\S/.test(stream.string.slice(0, stream.start)))) {\n      stream.skipToEnd()\n      return ret(\"comment\", \"comment\")\n    } else if (isOperatorChar.test(ch)) {\n      if (ch != \">\" || !state.lexical || state.lexical.type != \">\") {\n        if (stream.eat(\"=\")) {\n          if (ch == \"!\" || ch == \"=\") stream.eat(\"=\")\n        } else if (/[<>*+\\-]/.test(ch)) {\n          stream.eat(ch)\n          if (ch == \">\") stream.eat(ch)\n        }\n      }\n      if (ch == \"?\" && stream.eat(\".\")) return ret(\".\")\n      return ret(\"operator\", \"operator\", stream.current());\n    } else if (wordRE.test(ch)) {\n      stream.eatWhile(wordRE);\n      var word = stream.current()\n      if (state.lastType != \".\") {\n        if (keywords.propertyIsEnumerable(word)) {\n          var kw = keywords[word]\n          return ret(kw.type, kw.style, word)\n        }\n        if (word == \"async\" && stream.match(/^(\\s|\\/\\*.*?\\*\\/)*[\\[\\(\\w]/, false))\n          return ret(\"async\", \"keyword\", word)\n      }\n      return ret(\"variable\", \"variable\", word)\n    }\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, next;\n      if (jsonldMode && stream.peek() == \"@\" && stream.match(isJsonldKeyword)){\n        state.tokenize = tokenBase;\n        return ret(\"jsonld-keyword\", \"meta\");\n      }\n      while ((next = stream.next()) != null) {\n        if (next == quote && !escaped) break;\n        escaped = !escaped && next == \"\\\\\";\n      }\n      if (!escaped) state.tokenize = tokenBase;\n      return ret(\"string\", \"string\");\n    };\n  }\n\n  function tokenComment(stream, state) {\n    var maybeEnd = false, ch;\n    while (ch = stream.next()) {\n      if (ch == \"/\" && maybeEnd) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return ret(\"comment\", \"comment\");\n  }\n\n  function tokenQuasi(stream, state) {\n    var escaped = false, next;\n    while ((next = stream.next()) != null) {\n      if (!escaped && (next == \"`\" || next == \"$\" && stream.eat(\"{\"))) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      escaped = !escaped && next == \"\\\\\";\n    }\n    return ret(\"quasi\", \"string-2\", stream.current());\n  }\n\n  var brackets = \"([{}])\";\n  // This is a crude lookahead trick to try and notice that we're\n  // parsing the argument patterns for a fat-arrow function before we\n  // actually hit the arrow token. It only works if the arrow is on\n  // the same line as the arguments and there's no strange noise\n  // (comments) in between. Fallback is to only notice when we hit the\n  // arrow, and not declare the arguments as locals for the arrow\n  // body.\n  function findFatArrow(stream, state) {\n    if (state.fatArrowAt) state.fatArrowAt = null;\n    var arrow = stream.string.indexOf(\"=>\", stream.start);\n    if (arrow < 0) return;\n\n    if (isTS) { // Try to skip TypeScript return type declarations after the arguments\n      var m = /:\\s*(?:\\w+(?:<[^>]*>|\\[\\])?|\\{[^}]*\\})\\s*$/.exec(stream.string.slice(stream.start, arrow))\n      if (m) arrow = m.index\n    }\n\n    var depth = 0, sawSomething = false;\n    for (var pos = arrow - 1; pos >= 0; --pos) {\n      var ch = stream.string.charAt(pos);\n      var bracket = brackets.indexOf(ch);\n      if (bracket >= 0 && bracket < 3) {\n        if (!depth) { ++pos; break; }\n        if (--depth == 0) { if (ch == \"(\") sawSomething = true; break; }\n      } else if (bracket >= 3 && bracket < 6) {\n        ++depth;\n      } else if (wordRE.test(ch)) {\n        sawSomething = true;\n      } else if (/[\"'\\/`]/.test(ch)) {\n        for (;; --pos) {\n          if (pos == 0) return\n          var next = stream.string.charAt(pos - 1)\n          if (next == ch && stream.string.charAt(pos - 2) != \"\\\\\") { pos--; break }\n        }\n      } else if (sawSomething && !depth) {\n        ++pos;\n        break;\n      }\n    }\n    if (sawSomething && !depth) state.fatArrowAt = pos;\n  }\n\n  // Parser\n\n  var atomicTypes = {\"atom\": true, \"number\": true, \"variable\": true, \"string\": true, \"regexp\": true, \"this\": true, \"jsonld-keyword\": true};\n\n  function JSLexical(indented, column, type, align, prev, info) {\n    this.indented = indented;\n    this.column = column;\n    this.type = type;\n    this.prev = prev;\n    this.info = info;\n    if (align != null) this.align = align;\n  }\n\n  function inScope(state, varname) {\n    for (var v = state.localVars; v; v = v.next)\n      if (v.name == varname) return true;\n    for (var cx = state.context; cx; cx = cx.prev) {\n      for (var v = cx.vars; v; v = v.next)\n        if (v.name == varname) return true;\n    }\n  }\n\n  function parseJS(state, style, type, content, stream) {\n    var cc = state.cc;\n    // Communicate our context to the combinators.\n    // (Less wasteful than consing up a hundred closures on every call.)\n    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;\n\n    if (!state.lexical.hasOwnProperty(\"align\"))\n      state.lexical.align = true;\n\n    while(true) {\n      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;\n      if (combinator(type, content)) {\n        while(cc.length && cc[cc.length - 1].lex)\n          cc.pop()();\n        if (cx.marked) return cx.marked;\n        if (type == \"variable\" && inScope(state, content)) return \"variable-2\";\n        return style;\n      }\n    }\n  }\n\n  // Combinator utils\n\n  var cx = {state: null, column: null, marked: null, cc: null};\n  function pass() {\n    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);\n  }\n  function cont() {\n    pass.apply(null, arguments);\n    return true;\n  }\n  function inList(name, list) {\n    for (var v = list; v; v = v.next) if (v.name == name) return true\n    return false;\n  }\n  function register(varname) {\n    var state = cx.state;\n    cx.marked = \"def\";\n    if (state.context) {\n      if (state.lexical.info == \"var\" && state.context && state.context.block) {\n        // FIXME function decls are also not block scoped\n        var newContext = registerVarScoped(varname, state.context)\n        if (newContext != null) {\n          state.context = newContext\n          return\n        }\n      } else if (!inList(varname, state.localVars)) {\n        state.localVars = new Var(varname, state.localVars)\n        return\n      }\n    }\n    // Fall through means this is global\n    if (parserConfig.globalVars && !inList(varname, state.globalVars))\n      state.globalVars = new Var(varname, state.globalVars)\n  }\n  function registerVarScoped(varname, context) {\n    if (!context) {\n      return null\n    } else if (context.block) {\n      var inner = registerVarScoped(varname, context.prev)\n      if (!inner) return null\n      if (inner == context.prev) return context\n      return new Context(inner, context.vars, true)\n    } else if (inList(varname, context.vars)) {\n      return context\n    } else {\n      return new Context(context.prev, new Var(varname, context.vars), false)\n    }\n  }\n\n  function isModifier(name) {\n    return name == \"public\" || name == \"private\" || name == \"protected\" || name == \"abstract\" || name == \"readonly\"\n  }\n\n  // Combinators\n\n  function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }\n  function Var(name, next) { this.name = name; this.next = next }\n\n  var defaultVars = new Var(\"this\", new Var(\"arguments\", null))\n  function pushcontext() {\n    cx.state.context = new Context(cx.state.context, cx.state.localVars, false)\n    cx.state.localVars = defaultVars\n  }\n  function pushblockcontext() {\n    cx.state.context = new Context(cx.state.context, cx.state.localVars, true)\n    cx.state.localVars = null\n  }\n  function popcontext() {\n    cx.state.localVars = cx.state.context.vars\n    cx.state.context = cx.state.context.prev\n  }\n  popcontext.lex = true\n  function pushlex(type, info) {\n    var result = function() {\n      var state = cx.state, indent = state.indented;\n      if (state.lexical.type == \"stat\") indent = state.lexical.indented;\n      else for (var outer = state.lexical; outer && outer.type == \")\" && outer.align; outer = outer.prev)\n        indent = outer.indented;\n      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);\n    };\n    result.lex = true;\n    return result;\n  }\n  function poplex() {\n    var state = cx.state;\n    if (state.lexical.prev) {\n      if (state.lexical.type == \")\")\n        state.indented = state.lexical.indented;\n      state.lexical = state.lexical.prev;\n    }\n  }\n  poplex.lex = true;\n\n  function expect(wanted) {\n    function exp(type) {\n      if (type == wanted) return cont();\n      else if (wanted == \";\" || type == \"}\" || type == \")\" || type == \"]\") return pass();\n      else return cont(exp);\n    };\n    return exp;\n  }\n\n  function statement(type, value) {\n    if (type == \"var\") return cont(pushlex(\"vardef\", value), vardef, expect(\";\"), poplex);\n    if (type == \"keyword a\") return cont(pushlex(\"form\"), parenExpr, statement, poplex);\n    if (type == \"keyword b\") return cont(pushlex(\"form\"), statement, poplex);\n    if (type == \"keyword d\") return cx.stream.match(/^\\s*$/, false) ? cont() : cont(pushlex(\"stat\"), maybeexpression, expect(\";\"), poplex);\n    if (type == \"debugger\") return cont(expect(\";\"));\n    if (type == \"{\") return cont(pushlex(\"}\"), pushblockcontext, block, poplex, popcontext);\n    if (type == \";\") return cont();\n    if (type == \"if\") {\n      if (cx.state.lexical.info == \"else\" && cx.state.cc[cx.state.cc.length - 1] == poplex)\n        cx.state.cc.pop()();\n      return cont(pushlex(\"form\"), parenExpr, statement, poplex, maybeelse);\n    }\n    if (type == \"function\") return cont(functiondef);\n    if (type == \"for\") return cont(pushlex(\"form\"), forspec, statement, poplex);\n    if (type == \"class\" || (isTS && value == \"interface\")) {\n      cx.marked = \"keyword\"\n      return cont(pushlex(\"form\", type == \"class\" ? type : value), className, poplex)\n    }\n    if (type == \"variable\") {\n      if (isTS && value == \"declare\") {\n        cx.marked = \"keyword\"\n        return cont(statement)\n      } else if (isTS && (value == \"module\" || value == \"enum\" || value == \"type\") && cx.stream.match(/^\\s*\\w/, false)) {\n        cx.marked = \"keyword\"\n        if (value == \"enum\") return cont(enumdef);\n        else if (value == \"type\") return cont(typename, expect(\"operator\"), typeexpr, expect(\";\"));\n        else return cont(pushlex(\"form\"), pattern, expect(\"{\"), pushlex(\"}\"), block, poplex, poplex)\n      } else if (isTS && value == \"namespace\") {\n        cx.marked = \"keyword\"\n        return cont(pushlex(\"form\"), expression, statement, poplex)\n      } else if (isTS && value == \"abstract\") {\n        cx.marked = \"keyword\"\n        return cont(statement)\n      } else {\n        return cont(pushlex(\"stat\"), maybelabel);\n      }\n    }\n    if (type == \"switch\") return cont(pushlex(\"form\"), parenExpr, expect(\"{\"), pushlex(\"}\", \"switch\"), pushblockcontext,\n                                      block, poplex, poplex, popcontext);\n    if (type == \"case\") return cont(expression, expect(\":\"));\n    if (type == \"default\") return cont(expect(\":\"));\n    if (type == \"catch\") return cont(pushlex(\"form\"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);\n    if (type == \"export\") return cont(pushlex(\"stat\"), afterExport, poplex);\n    if (type == \"import\") return cont(pushlex(\"stat\"), afterImport, poplex);\n    if (type == \"async\") return cont(statement)\n    if (value == \"@\") return cont(expression, statement)\n    return pass(pushlex(\"stat\"), expression, expect(\";\"), poplex);\n  }\n  function maybeCatchBinding(type) {\n    if (type == \"(\") return cont(funarg, expect(\")\"))\n  }\n  function expression(type, value) {\n    return expressionInner(type, value, false);\n  }\n  function expressionNoComma(type, value) {\n    return expressionInner(type, value, true);\n  }\n  function parenExpr(type) {\n    if (type != \"(\") return pass()\n    return cont(pushlex(\")\"), maybeexpression, expect(\")\"), poplex)\n  }\n  function expressionInner(type, value, noComma) {\n    if (cx.state.fatArrowAt == cx.stream.start) {\n      var body = noComma ? arrowBodyNoComma : arrowBody;\n      if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, expect(\"=>\"), body, popcontext);\n      else if (type == \"variable\") return pass(pushcontext, pattern, expect(\"=>\"), body, popcontext);\n    }\n\n    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;\n    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);\n    if (type == \"function\") return cont(functiondef, maybeop);\n    if (type == \"class\" || (isTS && value == \"interface\")) { cx.marked = \"keyword\"; return cont(pushlex(\"form\"), classExpression, poplex); }\n    if (type == \"keyword c\" || type == \"async\") return cont(noComma ? expressionNoComma : expression);\n    if (type == \"(\") return cont(pushlex(\")\"), maybeexpression, expect(\")\"), poplex, maybeop);\n    if (type == \"operator\" || type == \"spread\") return cont(noComma ? expressionNoComma : expression);\n    if (type == \"[\") return cont(pushlex(\"]\"), arrayLiteral, poplex, maybeop);\n    if (type == \"{\") return contCommasep(objprop, \"}\", null, maybeop);\n    if (type == \"quasi\") return pass(quasi, maybeop);\n    if (type == \"new\") return cont(maybeTarget(noComma));\n    if (type == \"import\") return cont(expression);\n    return cont();\n  }\n  function maybeexpression(type) {\n    if (type.match(/[;\\}\\)\\],]/)) return pass();\n    return pass(expression);\n  }\n\n  function maybeoperatorComma(type, value) {\n    if (type == \",\") return cont(maybeexpression);\n    return maybeoperatorNoComma(type, value, false);\n  }\n  function maybeoperatorNoComma(type, value, noComma) {\n    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;\n    var expr = noComma == false ? expression : expressionNoComma;\n    if (type == \"=>\") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);\n    if (type == \"operator\") {\n      if (/\\+\\+|--/.test(value) || isTS && value == \"!\") return cont(me);\n      if (isTS && value == \"<\" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\\s*\\(/, false))\n        return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, me);\n      if (value == \"?\") return cont(expression, expect(\":\"), expr);\n      return cont(expr);\n    }\n    if (type == \"quasi\") { return pass(quasi, me); }\n    if (type == \";\") return;\n    if (type == \"(\") return contCommasep(expressionNoComma, \")\", \"call\", me);\n    if (type == \".\") return cont(property, me);\n    if (type == \"[\") return cont(pushlex(\"]\"), maybeexpression, expect(\"]\"), poplex, me);\n    if (isTS && value == \"as\") { cx.marked = \"keyword\"; return cont(typeexpr, me) }\n    if (type == \"regexp\") {\n      cx.state.lastType = cx.marked = \"operator\"\n      cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)\n      return cont(expr)\n    }\n  }\n  function quasi(type, value) {\n    if (type != \"quasi\") return pass();\n    if (value.slice(value.length - 2) != \"${\") return cont(quasi);\n    return cont(expression, continueQuasi);\n  }\n  function continueQuasi(type) {\n    if (type == \"}\") {\n      cx.marked = \"string-2\";\n      cx.state.tokenize = tokenQuasi;\n      return cont(quasi);\n    }\n  }\n  function arrowBody(type) {\n    findFatArrow(cx.stream, cx.state);\n    return pass(type == \"{\" ? statement : expression);\n  }\n  function arrowBodyNoComma(type) {\n    findFatArrow(cx.stream, cx.state);\n    return pass(type == \"{\" ? statement : expressionNoComma);\n  }\n  function maybeTarget(noComma) {\n    return function(type) {\n      if (type == \".\") return cont(noComma ? targetNoComma : target);\n      else if (type == \"variable\" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)\n      else return pass(noComma ? expressionNoComma : expression);\n    };\n  }\n  function target(_, value) {\n    if (value == \"target\") { cx.marked = \"keyword\"; return cont(maybeoperatorComma); }\n  }\n  function targetNoComma(_, value) {\n    if (value == \"target\") { cx.marked = \"keyword\"; return cont(maybeoperatorNoComma); }\n  }\n  function maybelabel(type) {\n    if (type == \":\") return cont(poplex, statement);\n    return pass(maybeoperatorComma, expect(\";\"), poplex);\n  }\n  function property(type) {\n    if (type == \"variable\") {cx.marked = \"property\"; return cont();}\n  }\n  function objprop(type, value) {\n    if (type == \"async\") {\n      cx.marked = \"property\";\n      return cont(objprop);\n    } else if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\";\n      if (value == \"get\" || value == \"set\") return cont(getterSetter);\n      var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params\n      if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\\s*:\\s*/, false)))\n        cx.state.fatArrowAt = cx.stream.pos + m[0].length\n      return cont(afterprop);\n    } else if (type == \"number\" || type == \"string\") {\n      cx.marked = jsonldMode ? \"property\" : (cx.style + \" property\");\n      return cont(afterprop);\n    } else if (type == \"jsonld-keyword\") {\n      return cont(afterprop);\n    } else if (isTS && isModifier(value)) {\n      cx.marked = \"keyword\"\n      return cont(objprop)\n    } else if (type == \"[\") {\n      return cont(expression, maybetype, expect(\"]\"), afterprop);\n    } else if (type == \"spread\") {\n      return cont(expressionNoComma, afterprop);\n    } else if (value == \"*\") {\n      cx.marked = \"keyword\";\n      return cont(objprop);\n    } else if (type == \":\") {\n      return pass(afterprop)\n    }\n  }\n  function getterSetter(type) {\n    if (type != \"variable\") return pass(afterprop);\n    cx.marked = \"property\";\n    return cont(functiondef);\n  }\n  function afterprop(type) {\n    if (type == \":\") return cont(expressionNoComma);\n    if (type == \"(\") return pass(functiondef);\n  }\n  function commasep(what, end, sep) {\n    function proceed(type, value) {\n      if (sep ? sep.indexOf(type) > -1 : type == \",\") {\n        var lex = cx.state.lexical;\n        if (lex.info == \"call\") lex.pos = (lex.pos || 0) + 1;\n        return cont(function(type, value) {\n          if (type == end || value == end) return pass()\n          return pass(what)\n        }, proceed);\n      }\n      if (type == end || value == end) return cont();\n      if (sep && sep.indexOf(\";\") > -1) return pass(what)\n      return cont(expect(end));\n    }\n    return function(type, value) {\n      if (type == end || value == end) return cont();\n      return pass(what, proceed);\n    };\n  }\n  function contCommasep(what, end, info) {\n    for (var i = 3; i < arguments.length; i++)\n      cx.cc.push(arguments[i]);\n    return cont(pushlex(end, info), commasep(what, end), poplex);\n  }\n  function block(type) {\n    if (type == \"}\") return cont();\n    return pass(statement, block);\n  }\n  function maybetype(type, value) {\n    if (isTS) {\n      if (type == \":\") return cont(typeexpr);\n      if (value == \"?\") return cont(maybetype);\n    }\n  }\n  function maybetypeOrIn(type, value) {\n    if (isTS && (type == \":\" || value == \"in\")) return cont(typeexpr)\n  }\n  function mayberettype(type) {\n    if (isTS && type == \":\") {\n      if (cx.stream.match(/^\\s*\\w+\\s+is\\b/, false)) return cont(expression, isKW, typeexpr)\n      else return cont(typeexpr)\n    }\n  }\n  function isKW(_, value) {\n    if (value == \"is\") {\n      cx.marked = \"keyword\"\n      return cont()\n    }\n  }\n  function typeexpr(type, value) {\n    if (value == \"keyof\" || value == \"typeof\" || value == \"infer\") {\n      cx.marked = \"keyword\"\n      return cont(value == \"typeof\" ? expressionNoComma : typeexpr)\n    }\n    if (type == \"variable\" || value == \"void\") {\n      cx.marked = \"type\"\n      return cont(afterType)\n    }\n    if (value == \"|\" || value == \"&\") return cont(typeexpr)\n    if (type == \"string\" || type == \"number\" || type == \"atom\") return cont(afterType);\n    if (type == \"[\") return cont(pushlex(\"]\"), commasep(typeexpr, \"]\", \",\"), poplex, afterType)\n    if (type == \"{\") return cont(pushlex(\"}\"), commasep(typeprop, \"}\", \",;\"), poplex, afterType)\n    if (type == \"(\") return cont(commasep(typearg, \")\"), maybeReturnType, afterType)\n    if (type == \"<\") return cont(commasep(typeexpr, \">\"), typeexpr)\n  }\n  function maybeReturnType(type) {\n    if (type == \"=>\") return cont(typeexpr)\n  }\n  function typeprop(type, value) {\n    if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\"\n      return cont(typeprop)\n    } else if (value == \"?\" || type == \"number\" || type == \"string\") {\n      return cont(typeprop)\n    } else if (type == \":\") {\n      return cont(typeexpr)\n    } else if (type == \"[\") {\n      return cont(expect(\"variable\"), maybetypeOrIn, expect(\"]\"), typeprop)\n    } else if (type == \"(\") {\n      return pass(functiondecl, typeprop)\n    }\n  }\n  function typearg(type, value) {\n    if (type == \"variable\" && cx.stream.match(/^\\s*[?:]/, false) || value == \"?\") return cont(typearg)\n    if (type == \":\") return cont(typeexpr)\n    if (type == \"spread\") return cont(typearg)\n    return pass(typeexpr)\n  }\n  function afterType(type, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, afterType)\n    if (value == \"|\" || type == \".\" || value == \"&\") return cont(typeexpr)\n    if (type == \"[\") return cont(typeexpr, expect(\"]\"), afterType)\n    if (value == \"extends\" || value == \"implements\") { cx.marked = \"keyword\"; return cont(typeexpr) }\n    if (value == \"?\") return cont(typeexpr, expect(\":\"), typeexpr)\n  }\n  function maybeTypeArgs(_, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, afterType)\n  }\n  function typeparam() {\n    return pass(typeexpr, maybeTypeDefault)\n  }\n  function maybeTypeDefault(_, value) {\n    if (value == \"=\") return cont(typeexpr)\n  }\n  function vardef(_, value) {\n    if (value == \"enum\") {cx.marked = \"keyword\"; return cont(enumdef)}\n    return pass(pattern, maybetype, maybeAssign, vardefCont);\n  }\n  function pattern(type, value) {\n    if (isTS && isModifier(value)) { cx.marked = \"keyword\"; return cont(pattern) }\n    if (type == \"variable\") { register(value); return cont(); }\n    if (type == \"spread\") return cont(pattern);\n    if (type == \"[\") return contCommasep(eltpattern, \"]\");\n    if (type == \"{\") return contCommasep(proppattern, \"}\");\n  }\n  function proppattern(type, value) {\n    if (type == \"variable\" && !cx.stream.match(/^\\s*:/, false)) {\n      register(value);\n      return cont(maybeAssign);\n    }\n    if (type == \"variable\") cx.marked = \"property\";\n    if (type == \"spread\") return cont(pattern);\n    if (type == \"}\") return pass();\n    if (type == \"[\") return cont(expression, expect(']'), expect(':'), proppattern);\n    return cont(expect(\":\"), pattern, maybeAssign);\n  }\n  function eltpattern() {\n    return pass(pattern, maybeAssign)\n  }\n  function maybeAssign(_type, value) {\n    if (value == \"=\") return cont(expressionNoComma);\n  }\n  function vardefCont(type) {\n    if (type == \",\") return cont(vardef);\n  }\n  function maybeelse(type, value) {\n    if (type == \"keyword b\" && value == \"else\") return cont(pushlex(\"form\", \"else\"), statement, poplex);\n  }\n  function forspec(type, value) {\n    if (value == \"await\") return cont(forspec);\n    if (type == \"(\") return cont(pushlex(\")\"), forspec1, poplex);\n  }\n  function forspec1(type) {\n    if (type == \"var\") return cont(vardef, forspec2);\n    if (type == \"variable\") return cont(forspec2);\n    return pass(forspec2)\n  }\n  function forspec2(type, value) {\n    if (type == \")\") return cont()\n    if (type == \";\") return cont(forspec2)\n    if (value == \"in\" || value == \"of\") { cx.marked = \"keyword\"; return cont(expression, forspec2) }\n    return pass(expression, forspec2)\n  }\n  function functiondef(type, value) {\n    if (value == \"*\") {cx.marked = \"keyword\"; return cont(functiondef);}\n    if (type == \"variable\") {register(value); return cont(functiondef);}\n    if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, mayberettype, statement, popcontext);\n    if (isTS && value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, functiondef)\n  }\n  function functiondecl(type, value) {\n    if (value == \"*\") {cx.marked = \"keyword\"; return cont(functiondecl);}\n    if (type == \"variable\") {register(value); return cont(functiondecl);}\n    if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, mayberettype, popcontext);\n    if (isTS && value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, functiondecl)\n  }\n  function typename(type, value) {\n    if (type == \"keyword\" || type == \"variable\") {\n      cx.marked = \"type\"\n      return cont(typename)\n    } else if (value == \"<\") {\n      return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex)\n    }\n  }\n  function funarg(type, value) {\n    if (value == \"@\") cont(expression, funarg)\n    if (type == \"spread\") return cont(funarg);\n    if (isTS && isModifier(value)) { cx.marked = \"keyword\"; return cont(funarg); }\n    if (isTS && type == \"this\") return cont(maybetype, maybeAssign)\n    return pass(pattern, maybetype, maybeAssign);\n  }\n  function classExpression(type, value) {\n    // Class expressions may have an optional name.\n    if (type == \"variable\") return className(type, value);\n    return classNameAfter(type, value);\n  }\n  function className(type, value) {\n    if (type == \"variable\") {register(value); return cont(classNameAfter);}\n  }\n  function classNameAfter(type, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, classNameAfter)\n    if (value == \"extends\" || value == \"implements\" || (isTS && type == \",\")) {\n      if (value == \"implements\") cx.marked = \"keyword\";\n      return cont(isTS ? typeexpr : expression, classNameAfter);\n    }\n    if (type == \"{\") return cont(pushlex(\"}\"), classBody, poplex);\n  }\n  function classBody(type, value) {\n    if (type == \"async\" ||\n        (type == \"variable\" &&\n         (value == \"static\" || value == \"get\" || value == \"set\" || (isTS && isModifier(value))) &&\n         cx.stream.match(/^\\s+[\\w$\\xa1-\\uffff]/, false))) {\n      cx.marked = \"keyword\";\n      return cont(classBody);\n    }\n    if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\";\n      return cont(classfield, classBody);\n    }\n    if (type == \"number\" || type == \"string\") return cont(classfield, classBody);\n    if (type == \"[\")\n      return cont(expression, maybetype, expect(\"]\"), classfield, classBody)\n    if (value == \"*\") {\n      cx.marked = \"keyword\";\n      return cont(classBody);\n    }\n    if (isTS && type == \"(\") return pass(functiondecl, classBody)\n    if (type == \";\" || type == \",\") return cont(classBody);\n    if (type == \"}\") return cont();\n    if (value == \"@\") return cont(expression, classBody)\n  }\n  function classfield(type, value) {\n    if (value == \"?\") return cont(classfield)\n    if (type == \":\") return cont(typeexpr, maybeAssign)\n    if (value == \"=\") return cont(expressionNoComma)\n    var context = cx.state.lexical.prev, isInterface = context && context.info == \"interface\"\n    return pass(isInterface ? functiondecl : functiondef)\n  }\n  function afterExport(type, value) {\n    if (value == \"*\") { cx.marked = \"keyword\"; return cont(maybeFrom, expect(\";\")); }\n    if (value == \"default\") { cx.marked = \"keyword\"; return cont(expression, expect(\";\")); }\n    if (type == \"{\") return cont(commasep(exportField, \"}\"), maybeFrom, expect(\";\"));\n    return pass(statement);\n  }\n  function exportField(type, value) {\n    if (value == \"as\") { cx.marked = \"keyword\"; return cont(expect(\"variable\")); }\n    if (type == \"variable\") return pass(expressionNoComma, exportField);\n  }\n  function afterImport(type) {\n    if (type == \"string\") return cont();\n    if (type == \"(\") return pass(expression);\n    return pass(importSpec, maybeMoreImports, maybeFrom);\n  }\n  function importSpec(type, value) {\n    if (type == \"{\") return contCommasep(importSpec, \"}\");\n    if (type == \"variable\") register(value);\n    if (value == \"*\") cx.marked = \"keyword\";\n    return cont(maybeAs);\n  }\n  function maybeMoreImports(type) {\n    if (type == \",\") return cont(importSpec, maybeMoreImports)\n  }\n  function maybeAs(_type, value) {\n    if (value == \"as\") { cx.marked = \"keyword\"; return cont(importSpec); }\n  }\n  function maybeFrom(_type, value) {\n    if (value == \"from\") { cx.marked = \"keyword\"; return cont(expression); }\n  }\n  function arrayLiteral(type) {\n    if (type == \"]\") return cont();\n    return pass(commasep(expressionNoComma, \"]\"));\n  }\n  function enumdef() {\n    return pass(pushlex(\"form\"), pattern, expect(\"{\"), pushlex(\"}\"), commasep(enummember, \"}\"), poplex, poplex)\n  }\n  function enummember() {\n    return pass(pattern, maybeAssign);\n  }\n\n  function isContinuedStatement(state, textAfter) {\n    return state.lastType == \"operator\" || state.lastType == \",\" ||\n      isOperatorChar.test(textAfter.charAt(0)) ||\n      /[,.]/.test(textAfter.charAt(0));\n  }\n\n  function expressionAllowed(stream, state, backUp) {\n    return state.tokenize == tokenBase &&\n      /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\\[{}\\(,;:]|=>)$/.test(state.lastType) ||\n      (state.lastType == \"quasi\" && /\\{\\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))\n  }\n\n  // Interface\n\n  return {\n    startState: function(basecolumn) {\n      var state = {\n        tokenize: tokenBase,\n        lastType: \"sof\",\n        cc: [],\n        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, \"block\", false),\n        localVars: parserConfig.localVars,\n        context: parserConfig.localVars && new Context(null, null, false),\n        indented: basecolumn || 0\n      };\n      if (parserConfig.globalVars && typeof parserConfig.globalVars == \"object\")\n        state.globalVars = parserConfig.globalVars;\n      return state;\n    },\n\n    token: function(stream, state) {\n      if (stream.sol()) {\n        if (!state.lexical.hasOwnProperty(\"align\"))\n          state.lexical.align = false;\n        state.indented = stream.indentation();\n        findFatArrow(stream, state);\n      }\n      if (state.tokenize != tokenComment && stream.eatSpace()) return null;\n      var style = state.tokenize(stream, state);\n      if (type == \"comment\") return style;\n      state.lastType = type == \"operator\" && (content == \"++\" || content == \"--\") ? \"incdec\" : type;\n      return parseJS(state, style, type, content, stream);\n    },\n\n    indent: function(state, textAfter) {\n      if (state.tokenize == tokenComment) return CodeMirror.Pass;\n      if (state.tokenize != tokenBase) return 0;\n      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top\n      // Kludge to prevent 'maybelse' from blocking lexical scope pops\n      if (!/^\\s*else\\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {\n        var c = state.cc[i];\n        if (c == poplex) lexical = lexical.prev;\n        else if (c != maybeelse) break;\n      }\n      while ((lexical.type == \"stat\" || lexical.type == \"form\") &&\n             (firstChar == \"}\" || ((top = state.cc[state.cc.length - 1]) &&\n                                   (top == maybeoperatorComma || top == maybeoperatorNoComma) &&\n                                   !/^[,\\.=+\\-*:?[\\(]/.test(textAfter))))\n        lexical = lexical.prev;\n      if (statementIndent && lexical.type == \")\" && lexical.prev.type == \"stat\")\n        lexical = lexical.prev;\n      var type = lexical.type, closing = firstChar == type;\n\n      if (type == \"vardef\") return lexical.indented + (state.lastType == \"operator\" || state.lastType == \",\" ? lexical.info.length + 1 : 0);\n      else if (type == \"form\" && firstChar == \"{\") return lexical.indented;\n      else if (type == \"form\") return lexical.indented + indentUnit;\n      else if (type == \"stat\")\n        return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);\n      else if (lexical.info == \"switch\" && !closing && parserConfig.doubleIndentSwitch != false)\n        return lexical.indented + (/^(?:case|default)\\b/.test(textAfter) ? indentUnit : 2 * indentUnit);\n      else if (lexical.align) return lexical.column + (closing ? 0 : 1);\n      else return lexical.indented + (closing ? 0 : indentUnit);\n    },\n\n    electricInput: /^\\s*(?:case .*?:|default:|\\{|\\})$/,\n    blockCommentStart: jsonMode ? null : \"/*\",\n    blockCommentEnd: jsonMode ? null : \"*/\",\n    blockCommentContinue: jsonMode ? null : \" * \",\n    lineComment: jsonMode ? null : \"//\",\n    fold: \"brace\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n\n    helperType: jsonMode ? \"json\" : \"javascript\",\n    jsonldMode: jsonldMode,\n    jsonMode: jsonMode,\n\n    expressionAllowed: expressionAllowed,\n\n    skipExpression: function(state) {\n      var top = state.cc[state.cc.length - 1]\n      if (top == expression || top == expressionNoComma) state.cc.pop()\n    }\n  };\n});\n\nCodeMirror.registerHelper(\"wordChars\", \"javascript\", /[\\w$]/);\n\nCodeMirror.defineMIME(\"text/javascript\", \"javascript\");\nCodeMirror.defineMIME(\"text/ecmascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/javascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/x-javascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/ecmascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/json\", {name: \"javascript\", json: true});\nCodeMirror.defineMIME(\"application/x-json\", {name: \"javascript\", json: true});\nCodeMirror.defineMIME(\"application/ld+json\", {name: \"javascript\", jsonld: true});\nCodeMirror.defineMIME(\"text/typescript\", { name: \"javascript\", typescript: true });\nCodeMirror.defineMIME(\"application/typescript\", { name: \"javascript\", typescript: true });\n\n});\n\n\n/* ---- mode/markdown.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../xml/xml\"), require(\"../meta\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../xml/xml\", \"../meta\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"markdown\", function(cmCfg, modeCfg) {\n\n  var htmlMode = CodeMirror.getMode(cmCfg, \"text/html\");\n  var htmlModeMissing = htmlMode.name == \"null\"\n\n  function getMode(name) {\n    if (CodeMirror.findModeByName) {\n      var found = CodeMirror.findModeByName(name);\n      if (found) name = found.mime || found.mimes[0];\n    }\n    var mode = CodeMirror.getMode(cmCfg, name);\n    return mode.name == \"null\" ? null : mode;\n  }\n\n  // Should characters that affect highlighting be highlighted separate?\n  // Does not include characters that will be output (such as `1.` and `-` for lists)\n  if (modeCfg.highlightFormatting === undefined)\n    modeCfg.highlightFormatting = false;\n\n  // Maximum number of nested blockquotes. Set to 0 for infinite nesting.\n  // Excess `>` will emit `error` token.\n  if (modeCfg.maxBlockquoteDepth === undefined)\n    modeCfg.maxBlockquoteDepth = 0;\n\n  // Turn on task lists? (\"- [ ] \" and \"- [x] \")\n  if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;\n\n  // Turn on strikethrough syntax\n  if (modeCfg.strikethrough === undefined)\n    modeCfg.strikethrough = false;\n\n  if (modeCfg.emoji === undefined)\n    modeCfg.emoji = false;\n\n  if (modeCfg.fencedCodeBlockHighlighting === undefined)\n    modeCfg.fencedCodeBlockHighlighting = true;\n\n  if (modeCfg.fencedCodeBlockDefaultMode === undefined)\n    modeCfg.fencedCodeBlockDefaultMode = 'text/plain';\n\n  if (modeCfg.xml === undefined)\n    modeCfg.xml = true;\n\n  // Allow token types to be overridden by user-provided token types.\n  if (modeCfg.tokenTypeOverrides === undefined)\n    modeCfg.tokenTypeOverrides = {};\n\n  var tokenTypes = {\n    header: \"header\",\n    code: \"comment\",\n    quote: \"quote\",\n    list1: \"variable-2\",\n    list2: \"variable-3\",\n    list3: \"keyword\",\n    hr: \"hr\",\n    image: \"image\",\n    imageAltText: \"image-alt-text\",\n    imageMarker: \"image-marker\",\n    formatting: \"formatting\",\n    linkInline: \"link\",\n    linkEmail: \"link\",\n    linkText: \"link\",\n    linkHref: \"string\",\n    em: \"em\",\n    strong: \"strong\",\n    strikethrough: \"strikethrough\",\n    emoji: \"builtin\"\n  };\n\n  for (var tokenType in tokenTypes) {\n    if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {\n      tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];\n    }\n  }\n\n  var hrRE = /^([*\\-_])(?:\\s*\\1){2,}\\s*$/\n  ,   listRE = /^(?:[*\\-+]|^[0-9]+([.)]))\\s+/\n  ,   taskListRE = /^\\[(x| )\\](?=\\s)/i // Must follow listRE\n  ,   atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/\n  ,   setextHeaderRE = /^ {0,3}(?:\\={1,}|-{2,})\\s*$/\n  ,   textRE = /^[^#!\\[\\]*_\\\\<>` \"'(~:]+/\n  ,   fencedCodeRE = /^(~~~+|```+)[ \\t]*([\\w\\/+#-]*)[^\\n`]*$/\n  ,   linkDefRE = /^\\s*\\[[^\\]]+?\\]:.*$/ // naive link-definition\n  ,   punctuation = /[!\"#$%&'()*+,\\-.\\/:;<=>?@\\[\\\\\\]^_`{|}~\\xA1\\xA7\\xAB\\xB6\\xB7\\xBB\\xBF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2308-\\u230B\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E42\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA8FC\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]|\\uD800[\\uDD00-\\uDD02\\uDF9F\\uDFD0]|\\uD801\\uDD6F|\\uD802[\\uDC57\\uDD1F\\uDD3F\\uDE50-\\uDE58\\uDE7F\\uDEF0-\\uDEF6\\uDF39-\\uDF3F\\uDF99-\\uDF9C]|\\uD804[\\uDC47-\\uDC4D\\uDCBB\\uDCBC\\uDCBE-\\uDCC1\\uDD40-\\uDD43\\uDD74\\uDD75\\uDDC5-\\uDDC9\\uDDCD\\uDDDB\\uDDDD-\\uDDDF\\uDE38-\\uDE3D\\uDEA9]|\\uD805[\\uDCC6\\uDDC1-\\uDDD7\\uDE41-\\uDE43\\uDF3C-\\uDF3E]|\\uD809[\\uDC70-\\uDC74]|\\uD81A[\\uDE6E\\uDE6F\\uDEF5\\uDF37-\\uDF3B\\uDF44]|\\uD82F\\uDC9F|\\uD836[\\uDE87-\\uDE8B]/\n  ,   expandedTab = \"    \" // CommonMark specifies tab as 4 spaces\n\n  function switchInline(stream, state, f) {\n    state.f = state.inline = f;\n    return f(stream, state);\n  }\n\n  function switchBlock(stream, state, f) {\n    state.f = state.block = f;\n    return f(stream, state);\n  }\n\n  function lineIsEmpty(line) {\n    return !line || !/\\S/.test(line.string)\n  }\n\n  // Blocks\n\n  function blankLine(state) {\n    // Reset linkTitle state\n    state.linkTitle = false;\n    state.linkHref = false;\n    state.linkText = false;\n    // Reset EM state\n    state.em = false;\n    // Reset STRONG state\n    state.strong = false;\n    // Reset strikethrough state\n    state.strikethrough = false;\n    // Reset state.quote\n    state.quote = 0;\n    // Reset state.indentedCode\n    state.indentedCode = false;\n    if (state.f == htmlBlock) {\n      var exit = htmlModeMissing\n      if (!exit) {\n        var inner = CodeMirror.innerMode(htmlMode, state.htmlState)\n        exit = inner.mode.name == \"xml\" && inner.state.tagStart === null &&\n          (!inner.state.context && inner.state.tokenize.isInText)\n      }\n      if (exit) {\n        state.f = inlineNormal;\n        state.block = blockNormal;\n        state.htmlState = null;\n      }\n    }\n    // Reset state.trailingSpace\n    state.trailingSpace = 0;\n    state.trailingSpaceNewLine = false;\n    // Mark this line as blank\n    state.prevLine = state.thisLine\n    state.thisLine = {stream: null}\n    return null;\n  }\n\n  function blockNormal(stream, state) {\n    var firstTokenOnLine = stream.column() === state.indentation;\n    var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);\n    var prevLineIsIndentedCode = state.indentedCode;\n    var prevLineIsHr = state.prevLine.hr;\n    var prevLineIsList = state.list !== false;\n    var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3;\n\n    state.indentedCode = false;\n\n    var lineIndentation = state.indentation;\n    // compute once per line (on first token)\n    if (state.indentationDiff === null) {\n      state.indentationDiff = state.indentation;\n      if (prevLineIsList) {\n        state.list = null;\n        // While this list item's marker's indentation is less than the deepest\n        //  list item's content's indentation,pop the deepest list item\n        //  indentation off the stack, and update block indentation state\n        while (lineIndentation < state.listStack[state.listStack.length - 1]) {\n          state.listStack.pop();\n          if (state.listStack.length) {\n            state.indentation = state.listStack[state.listStack.length - 1];\n          // less than the first list's indent -> the line is no longer a list\n          } else {\n            state.list = false;\n          }\n        }\n        if (state.list !== false) {\n          state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1]\n        }\n      }\n    }\n\n    // not comprehensive (currently only for setext detection purposes)\n    var allowsInlineContinuation = (\n        !prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header &&\n        (!prevLineIsList || !prevLineIsIndentedCode) &&\n        !state.prevLine.fencedCodeEnd\n    );\n\n    var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) &&\n      state.indentation <= maxNonCodeIndentation && stream.match(hrRE);\n\n    var match = null;\n    if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||\n         state.prevLine.header || prevLineLineIsEmpty)) {\n      stream.skipToEnd();\n      state.indentedCode = true;\n      return tokenTypes.code;\n    } else if (stream.eatSpace()) {\n      return null;\n    } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) {\n      state.quote = 0;\n      state.header = match[1].length;\n      state.thisLine.header = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"header\";\n      state.f = state.inline;\n      return getType(state);\n    } else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) {\n      state.quote = firstTokenOnLine ? 1 : state.quote + 1;\n      if (modeCfg.highlightFormatting) state.formatting = \"quote\";\n      stream.eatSpace();\n      return getType(state);\n    } else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) {\n      var listType = match[1] ? \"ol\" : \"ul\";\n\n      state.indentation = lineIndentation + stream.current().length;\n      state.list = true;\n      state.quote = 0;\n\n      // Add this list item's content's indentation to the stack\n      state.listStack.push(state.indentation);\n      // Reset inline styles which shouldn't propagate aross list items\n      state.em = false;\n      state.strong = false;\n      state.code = false;\n      state.strikethrough = false;\n\n      if (modeCfg.taskLists && stream.match(taskListRE, false)) {\n        state.taskList = true;\n      }\n      state.f = state.inline;\n      if (modeCfg.highlightFormatting) state.formatting = [\"list\", \"list-\" + listType];\n      return getType(state);\n    } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {\n      state.quote = 0;\n      state.fencedEndRE = new RegExp(match[1] + \"+ *$\");\n      // try switching mode\n      state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2] || modeCfg.fencedCodeBlockDefaultMode );\n      if (state.localMode) state.localState = CodeMirror.startState(state.localMode);\n      state.f = state.block = local;\n      if (modeCfg.highlightFormatting) state.formatting = \"code-block\";\n      state.code = -1\n      return getType(state);\n    // SETEXT has lowest block-scope precedence after HR, so check it after\n    //  the others (code, blockquote, list...)\n    } else if (\n      // if setext set, indicates line after ---/===\n      state.setext || (\n        // line before ---/===\n        (!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false &&\n        !state.code && !isHr && !linkDefRE.test(stream.string) &&\n        (match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE))\n      )\n    ) {\n      if ( !state.setext ) {\n        state.header = match[0].charAt(0) == '=' ? 1 : 2;\n        state.setext = state.header;\n      } else {\n        state.header = state.setext;\n        // has no effect on type so we can reset it now\n        state.setext = 0;\n        stream.skipToEnd();\n        if (modeCfg.highlightFormatting) state.formatting = \"header\";\n      }\n      state.thisLine.header = true;\n      state.f = state.inline;\n      return getType(state);\n    } else if (isHr) {\n      stream.skipToEnd();\n      state.hr = true;\n      state.thisLine.hr = true;\n      return tokenTypes.hr;\n    } else if (stream.peek() === '[') {\n      return switchInline(stream, state, footnoteLink);\n    }\n\n    return switchInline(stream, state, state.inline);\n  }\n\n  function htmlBlock(stream, state) {\n    var style = htmlMode.token(stream, state.htmlState);\n    if (!htmlModeMissing) {\n      var inner = CodeMirror.innerMode(htmlMode, state.htmlState)\n      if ((inner.mode.name == \"xml\" && inner.state.tagStart === null &&\n           (!inner.state.context && inner.state.tokenize.isInText)) ||\n          (state.md_inside && stream.current().indexOf(\">\") > -1)) {\n        state.f = inlineNormal;\n        state.block = blockNormal;\n        state.htmlState = null;\n      }\n    }\n    return style;\n  }\n\n  function local(stream, state) {\n    var currListInd = state.listStack[state.listStack.length - 1] || 0;\n    var hasExitedList = state.indentation < currListInd;\n    var maxFencedEndInd = currListInd + 3;\n    if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) {\n      if (modeCfg.highlightFormatting) state.formatting = \"code-block\";\n      var returnType;\n      if (!hasExitedList) returnType = getType(state)\n      state.localMode = state.localState = null;\n      state.block = blockNormal;\n      state.f = inlineNormal;\n      state.fencedEndRE = null;\n      state.code = 0\n      state.thisLine.fencedCodeEnd = true;\n      if (hasExitedList) return switchBlock(stream, state, state.block);\n      return returnType;\n    } else if (state.localMode) {\n      return state.localMode.token(stream, state.localState);\n    } else {\n      stream.skipToEnd();\n      return tokenTypes.code;\n    }\n  }\n\n  // Inline\n  function getType(state) {\n    var styles = [];\n\n    if (state.formatting) {\n      styles.push(tokenTypes.formatting);\n\n      if (typeof state.formatting === \"string\") state.formatting = [state.formatting];\n\n      for (var i = 0; i < state.formatting.length; i++) {\n        styles.push(tokenTypes.formatting + \"-\" + state.formatting[i]);\n\n        if (state.formatting[i] === \"header\") {\n          styles.push(tokenTypes.formatting + \"-\" + state.formatting[i] + \"-\" + state.header);\n        }\n\n        // Add `formatting-quote` and `formatting-quote-#` for blockquotes\n        // Add `error` instead if the maximum blockquote nesting depth is passed\n        if (state.formatting[i] === \"quote\") {\n          if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {\n            styles.push(tokenTypes.formatting + \"-\" + state.formatting[i] + \"-\" + state.quote);\n          } else {\n            styles.push(\"error\");\n          }\n        }\n      }\n    }\n\n    if (state.taskOpen) {\n      styles.push(\"meta\");\n      return styles.length ? styles.join(' ') : null;\n    }\n    if (state.taskClosed) {\n      styles.push(\"property\");\n      return styles.length ? styles.join(' ') : null;\n    }\n\n    if (state.linkHref) {\n      styles.push(tokenTypes.linkHref, \"url\");\n    } else { // Only apply inline styles to non-url text\n      if (state.strong) { styles.push(tokenTypes.strong); }\n      if (state.em) { styles.push(tokenTypes.em); }\n      if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }\n      if (state.emoji) { styles.push(tokenTypes.emoji); }\n      if (state.linkText) { styles.push(tokenTypes.linkText); }\n      if (state.code) { styles.push(tokenTypes.code); }\n      if (state.image) { styles.push(tokenTypes.image); }\n      if (state.imageAltText) { styles.push(tokenTypes.imageAltText, \"link\"); }\n      if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }\n    }\n\n    if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + \"-\" + state.header); }\n\n    if (state.quote) {\n      styles.push(tokenTypes.quote);\n\n      // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth\n      if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {\n        styles.push(tokenTypes.quote + \"-\" + state.quote);\n      } else {\n        styles.push(tokenTypes.quote + \"-\" + modeCfg.maxBlockquoteDepth);\n      }\n    }\n\n    if (state.list !== false) {\n      var listMod = (state.listStack.length - 1) % 3;\n      if (!listMod) {\n        styles.push(tokenTypes.list1);\n      } else if (listMod === 1) {\n        styles.push(tokenTypes.list2);\n      } else {\n        styles.push(tokenTypes.list3);\n      }\n    }\n\n    if (state.trailingSpaceNewLine) {\n      styles.push(\"trailing-space-new-line\");\n    } else if (state.trailingSpace) {\n      styles.push(\"trailing-space-\" + (state.trailingSpace % 2 ? \"a\" : \"b\"));\n    }\n\n    return styles.length ? styles.join(' ') : null;\n  }\n\n  function handleText(stream, state) {\n    if (stream.match(textRE, true)) {\n      return getType(state);\n    }\n    return undefined;\n  }\n\n  function inlineNormal(stream, state) {\n    var style = state.text(stream, state);\n    if (typeof style !== 'undefined')\n      return style;\n\n    if (state.list) { // List marker (*, +, -, 1., etc)\n      state.list = null;\n      return getType(state);\n    }\n\n    if (state.taskList) {\n      var taskOpen = stream.match(taskListRE, true)[1] === \" \";\n      if (taskOpen) state.taskOpen = true;\n      else state.taskClosed = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"task\";\n      state.taskList = false;\n      return getType(state);\n    }\n\n    state.taskOpen = false;\n    state.taskClosed = false;\n\n    if (state.header && stream.match(/^#+$/, true)) {\n      if (modeCfg.highlightFormatting) state.formatting = \"header\";\n      return getType(state);\n    }\n\n    var ch = stream.next();\n\n    // Matches link titles present on next line\n    if (state.linkTitle) {\n      state.linkTitle = false;\n      var matchCh = ch;\n      if (ch === '(') {\n        matchCh = ')';\n      }\n      matchCh = (matchCh+'').replace(/([.?*+^\\[\\]\\\\(){}|-])/g, \"\\\\$1\");\n      var regex = '^\\\\s*(?:[^' + matchCh + '\\\\\\\\]+|\\\\\\\\\\\\\\\\|\\\\\\\\.)' + matchCh;\n      if (stream.match(new RegExp(regex), true)) {\n        return tokenTypes.linkHref;\n      }\n    }\n\n    // If this block is changed, it may need to be updated in GFM mode\n    if (ch === '`') {\n      var previousFormatting = state.formatting;\n      if (modeCfg.highlightFormatting) state.formatting = \"code\";\n      stream.eatWhile('`');\n      var count = stream.current().length\n      if (state.code == 0 && (!state.quote || count == 1)) {\n        state.code = count\n        return getType(state)\n      } else if (count == state.code) { // Must be exact\n        var t = getType(state)\n        state.code = 0\n        return t\n      } else {\n        state.formatting = previousFormatting\n        return getType(state)\n      }\n    } else if (state.code) {\n      return getType(state);\n    }\n\n    if (ch === '\\\\') {\n      stream.next();\n      if (modeCfg.highlightFormatting) {\n        var type = getType(state);\n        var formattingEscape = tokenTypes.formatting + \"-escape\";\n        return type ? type + \" \" + formattingEscape : formattingEscape;\n      }\n    }\n\n    if (ch === '!' && stream.match(/\\[[^\\]]*\\] ?(?:\\(|\\[)/, false)) {\n      state.imageMarker = true;\n      state.image = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"image\";\n      return getType(state);\n    }\n\n    if (ch === '[' && state.imageMarker && stream.match(/[^\\]]*\\](\\(.*?\\)| ?\\[.*?\\])/, false)) {\n      state.imageMarker = false;\n      state.imageAltText = true\n      if (modeCfg.highlightFormatting) state.formatting = \"image\";\n      return getType(state);\n    }\n\n    if (ch === ']' && state.imageAltText) {\n      if (modeCfg.highlightFormatting) state.formatting = \"image\";\n      var type = getType(state);\n      state.imageAltText = false;\n      state.image = false;\n      state.inline = state.f = linkHref;\n      return type;\n    }\n\n    if (ch === '[' && !state.image) {\n      if (state.linkText && stream.match(/^.*?\\]/)) return getType(state)\n      state.linkText = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      return getType(state);\n    }\n\n    if (ch === ']' && state.linkText) {\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      state.linkText = false;\n      state.inline = state.f = stream.match(/\\(.*?\\)| ?\\[.*?\\]/, false) ? linkHref : inlineNormal\n      return type;\n    }\n\n    if (ch === '<' && stream.match(/^(https?|ftps?):\\/\\/(?:[^\\\\>]|\\\\.)+>/, false)) {\n      state.f = state.inline = linkInline;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      if (type){\n        type += \" \";\n      } else {\n        type = \"\";\n      }\n      return type + tokenTypes.linkInline;\n    }\n\n    if (ch === '<' && stream.match(/^[^> \\\\]+@(?:[^\\\\>]|\\\\.)+>/, false)) {\n      state.f = state.inline = linkInline;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      if (type){\n        type += \" \";\n      } else {\n        type = \"\";\n      }\n      return type + tokenTypes.linkEmail;\n    }\n\n    if (modeCfg.xml && ch === '<' && stream.match(/^(!--|\\?|!\\[CDATA\\[|[a-z][a-z0-9-]*(?:\\s+[a-z_:.\\-]+(?:\\s*=\\s*[^>]+)?)*\\s*(?:>|$))/i, false)) {\n      var end = stream.string.indexOf(\">\", stream.pos);\n      if (end != -1) {\n        var atts = stream.string.substring(stream.start, end);\n        if (/markdown\\s*=\\s*('|\"){0,1}1('|\"){0,1}/.test(atts)) state.md_inside = true;\n      }\n      stream.backUp(1);\n      state.htmlState = CodeMirror.startState(htmlMode);\n      return switchBlock(stream, state, htmlBlock);\n    }\n\n    if (modeCfg.xml && ch === '<' && stream.match(/^\\/\\w*?>/)) {\n      state.md_inside = false;\n      return \"tag\";\n    } else if (ch === \"*\" || ch === \"_\") {\n      var len = 1, before = stream.pos == 1 ? \" \" : stream.string.charAt(stream.pos - 2)\n      while (len < 3 && stream.eat(ch)) len++\n      var after = stream.peek() || \" \"\n      // See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis\n      var leftFlanking = !/\\s/.test(after) && (!punctuation.test(after) || /\\s/.test(before) || punctuation.test(before))\n      var rightFlanking = !/\\s/.test(before) && (!punctuation.test(before) || /\\s/.test(after) || punctuation.test(after))\n      var setEm = null, setStrong = null\n      if (len % 2) { // Em\n        if (!state.em && leftFlanking && (ch === \"*\" || !rightFlanking || punctuation.test(before)))\n          setEm = true\n        else if (state.em == ch && rightFlanking && (ch === \"*\" || !leftFlanking || punctuation.test(after)))\n          setEm = false\n      }\n      if (len > 1) { // Strong\n        if (!state.strong && leftFlanking && (ch === \"*\" || !rightFlanking || punctuation.test(before)))\n          setStrong = true\n        else if (state.strong == ch && rightFlanking && (ch === \"*\" || !leftFlanking || punctuation.test(after)))\n          setStrong = false\n      }\n      if (setStrong != null || setEm != null) {\n        if (modeCfg.highlightFormatting) state.formatting = setEm == null ? \"strong\" : setStrong == null ? \"em\" : \"strong em\"\n        if (setEm === true) state.em = ch\n        if (setStrong === true) state.strong = ch\n        var t = getType(state)\n        if (setEm === false) state.em = false\n        if (setStrong === false) state.strong = false\n        return t\n      }\n    } else if (ch === ' ') {\n      if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces\n        if (stream.peek() === ' ') { // Surrounded by spaces, ignore\n          return getType(state);\n        } else { // Not surrounded by spaces, back up pointer\n          stream.backUp(1);\n        }\n      }\n    }\n\n    if (modeCfg.strikethrough) {\n      if (ch === '~' && stream.eatWhile(ch)) {\n        if (state.strikethrough) {// Remove strikethrough\n          if (modeCfg.highlightFormatting) state.formatting = \"strikethrough\";\n          var t = getType(state);\n          state.strikethrough = false;\n          return t;\n        } else if (stream.match(/^[^\\s]/, false)) {// Add strikethrough\n          state.strikethrough = true;\n          if (modeCfg.highlightFormatting) state.formatting = \"strikethrough\";\n          return getType(state);\n        }\n      } else if (ch === ' ') {\n        if (stream.match(/^~~/, true)) { // Probably surrounded by space\n          if (stream.peek() === ' ') { // Surrounded by spaces, ignore\n            return getType(state);\n          } else { // Not surrounded by spaces, back up pointer\n            stream.backUp(2);\n          }\n        }\n      }\n    }\n\n    if (modeCfg.emoji && ch === \":\" && stream.match(/^(?:[a-z_\\d+][a-z_\\d+-]*|\\-[a-z_\\d+][a-z_\\d+-]*):/)) {\n      state.emoji = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"emoji\";\n      var retType = getType(state);\n      state.emoji = false;\n      return retType;\n    }\n\n    if (ch === ' ') {\n      if (stream.match(/^ +$/, false)) {\n        state.trailingSpace++;\n      } else if (state.trailingSpace) {\n        state.trailingSpaceNewLine = true;\n      }\n    }\n\n    return getType(state);\n  }\n\n  function linkInline(stream, state) {\n    var ch = stream.next();\n\n    if (ch === \">\") {\n      state.f = state.inline = inlineNormal;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      if (type){\n        type += \" \";\n      } else {\n        type = \"\";\n      }\n      return type + tokenTypes.linkInline;\n    }\n\n    stream.match(/^[^>]+/, true);\n\n    return tokenTypes.linkInline;\n  }\n\n  function linkHref(stream, state) {\n    // Check if space, and return NULL if so (to avoid marking the space)\n    if(stream.eatSpace()){\n      return null;\n    }\n    var ch = stream.next();\n    if (ch === '(' || ch === '[') {\n      state.f = state.inline = getLinkHrefInside(ch === \"(\" ? \")\" : \"]\");\n      if (modeCfg.highlightFormatting) state.formatting = \"link-string\";\n      state.linkHref = true;\n      return getType(state);\n    }\n    return 'error';\n  }\n\n  var linkRE = {\n    \")\": /^(?:[^\\\\\\(\\)]|\\\\.|\\((?:[^\\\\\\(\\)]|\\\\.)*\\))*?(?=\\))/,\n    \"]\": /^(?:[^\\\\\\[\\]]|\\\\.|\\[(?:[^\\\\\\[\\]]|\\\\.)*\\])*?(?=\\])/\n  }\n\n  function getLinkHrefInside(endChar) {\n    return function(stream, state) {\n      var ch = stream.next();\n\n      if (ch === endChar) {\n        state.f = state.inline = inlineNormal;\n        if (modeCfg.highlightFormatting) state.formatting = \"link-string\";\n        var returnState = getType(state);\n        state.linkHref = false;\n        return returnState;\n      }\n\n      stream.match(linkRE[endChar])\n      state.linkHref = true;\n      return getType(state);\n    };\n  }\n\n  function footnoteLink(stream, state) {\n    if (stream.match(/^([^\\]\\\\]|\\\\.)*\\]:/, false)) {\n      state.f = footnoteLinkInside;\n      stream.next(); // Consume [\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      state.linkText = true;\n      return getType(state);\n    }\n    return switchInline(stream, state, inlineNormal);\n  }\n\n  function footnoteLinkInside(stream, state) {\n    if (stream.match(/^\\]:/, true)) {\n      state.f = state.inline = footnoteUrl;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var returnType = getType(state);\n      state.linkText = false;\n      return returnType;\n    }\n\n    stream.match(/^([^\\]\\\\]|\\\\.)+/, true);\n\n    return tokenTypes.linkText;\n  }\n\n  function footnoteUrl(stream, state) {\n    // Check if space, and return NULL if so (to avoid marking the space)\n    if(stream.eatSpace()){\n      return null;\n    }\n    // Match URL\n    stream.match(/^[^\\s]+/, true);\n    // Check for link title\n    if (stream.peek() === undefined) { // End of line, set flag to check next line\n      state.linkTitle = true;\n    } else { // More content on line, check if link title\n      stream.match(/^(?:\\s+(?:\"(?:[^\"\\\\]|\\\\\\\\|\\\\.)+\"|'(?:[^'\\\\]|\\\\\\\\|\\\\.)+'|\\((?:[^)\\\\]|\\\\\\\\|\\\\.)+\\)))?/, true);\n    }\n    state.f = state.inline = inlineNormal;\n    return tokenTypes.linkHref + \" url\";\n  }\n\n  var mode = {\n    startState: function() {\n      return {\n        f: blockNormal,\n\n        prevLine: {stream: null},\n        thisLine: {stream: null},\n\n        block: blockNormal,\n        htmlState: null,\n        indentation: 0,\n\n        inline: inlineNormal,\n        text: handleText,\n\n        formatting: false,\n        linkText: false,\n        linkHref: false,\n        linkTitle: false,\n        code: 0,\n        em: false,\n        strong: false,\n        header: 0,\n        setext: 0,\n        hr: false,\n        taskList: false,\n        list: false,\n        listStack: [],\n        quote: 0,\n        trailingSpace: 0,\n        trailingSpaceNewLine: false,\n        strikethrough: false,\n        emoji: false,\n        fencedEndRE: null\n      };\n    },\n\n    copyState: function(s) {\n      return {\n        f: s.f,\n\n        prevLine: s.prevLine,\n        thisLine: s.thisLine,\n\n        block: s.block,\n        htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),\n        indentation: s.indentation,\n\n        localMode: s.localMode,\n        localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,\n\n        inline: s.inline,\n        text: s.text,\n        formatting: false,\n        linkText: s.linkText,\n        linkTitle: s.linkTitle,\n        linkHref: s.linkHref,\n        code: s.code,\n        em: s.em,\n        strong: s.strong,\n        strikethrough: s.strikethrough,\n        emoji: s.emoji,\n        header: s.header,\n        setext: s.setext,\n        hr: s.hr,\n        taskList: s.taskList,\n        list: s.list,\n        listStack: s.listStack.slice(0),\n        quote: s.quote,\n        indentedCode: s.indentedCode,\n        trailingSpace: s.trailingSpace,\n        trailingSpaceNewLine: s.trailingSpaceNewLine,\n        md_inside: s.md_inside,\n        fencedEndRE: s.fencedEndRE\n      };\n    },\n\n    token: function(stream, state) {\n\n      // Reset state.formatting\n      state.formatting = false;\n\n      if (stream != state.thisLine.stream) {\n        state.header = 0;\n        state.hr = false;\n\n        if (stream.match(/^\\s*$/, true)) {\n          blankLine(state);\n          return null;\n        }\n\n        state.prevLine = state.thisLine\n        state.thisLine = {stream: stream}\n\n        // Reset state.taskList\n        state.taskList = false;\n\n        // Reset state.trailingSpace\n        state.trailingSpace = 0;\n        state.trailingSpaceNewLine = false;\n\n        if (!state.localState) {\n          state.f = state.block;\n          if (state.f != htmlBlock) {\n            var indentation = stream.match(/^\\s*/, true)[0].replace(/\\t/g, expandedTab).length;\n            state.indentation = indentation;\n            state.indentationDiff = null;\n            if (indentation > 0) return null;\n          }\n        }\n      }\n      return state.f(stream, state);\n    },\n\n    innerMode: function(state) {\n      if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};\n      if (state.localState) return {state: state.localState, mode: state.localMode};\n      return {state: state, mode: mode};\n    },\n\n    indent: function(state, textAfter, line) {\n      if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line)\n      if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line)\n      return CodeMirror.Pass\n    },\n\n    blankLine: blankLine,\n\n    getType: getType,\n\n    blockCommentStart: \"<!--\",\n    blockCommentEnd: \"-->\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n    fold: \"markdown\"\n  };\n  return mode;\n}, \"xml\");\n\nCodeMirror.defineMIME(\"text/markdown\", \"markdown\");\n\nCodeMirror.defineMIME(\"text/x-markdown\", \"markdown\");\n\n});\n\n\n/* ---- mode/python.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function wordRegexp(words) {\n    return new RegExp(\"^((\" + words.join(\")|(\") + \"))\\\\b\");\n  }\n\n  var wordOperators = wordRegexp([\"and\", \"or\", \"not\", \"is\"]);\n  var commonKeywords = [\"as\", \"assert\", \"break\", \"class\", \"continue\",\n                        \"def\", \"del\", \"elif\", \"else\", \"except\", \"finally\",\n                        \"for\", \"from\", \"global\", \"if\", \"import\",\n                        \"lambda\", \"pass\", \"raise\", \"return\",\n                        \"try\", \"while\", \"with\", \"yield\", \"in\"];\n  var commonBuiltins = [\"abs\", \"all\", \"any\", \"bin\", \"bool\", \"bytearray\", \"callable\", \"chr\",\n                        \"classmethod\", \"compile\", \"complex\", \"delattr\", \"dict\", \"dir\", \"divmod\",\n                        \"enumerate\", \"eval\", \"filter\", \"float\", \"format\", \"frozenset\",\n                        \"getattr\", \"globals\", \"hasattr\", \"hash\", \"help\", \"hex\", \"id\",\n                        \"input\", \"int\", \"isinstance\", \"issubclass\", \"iter\", \"len\",\n                        \"list\", \"locals\", \"map\", \"max\", \"memoryview\", \"min\", \"next\",\n                        \"object\", \"oct\", \"open\", \"ord\", \"pow\", \"property\", \"range\",\n                        \"repr\", \"reversed\", \"round\", \"set\", \"setattr\", \"slice\",\n                        \"sorted\", \"staticmethod\", \"str\", \"sum\", \"super\", \"tuple\",\n                        \"type\", \"vars\", \"zip\", \"__import__\", \"NotImplemented\",\n                        \"Ellipsis\", \"__debug__\"];\n  CodeMirror.registerHelper(\"hintWords\", \"python\", commonKeywords.concat(commonBuiltins));\n\n  function top(state) {\n    return state.scopes[state.scopes.length - 1];\n  }\n\n  CodeMirror.defineMode(\"python\", function(conf, parserConf) {\n    var ERRORCLASS = \"error\";\n\n    var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\\(\\)\\[\\]\\{\\}@,:`=;\\.\\\\]/;\n    //               (Backwards-compatibility with old, cumbersome config system)\n    var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters,\n                     parserConf.operators || /^([-+*/%\\/&|^]=?|[<>=]+|\\/\\/=?|\\*\\*=?|!=|[~!@]|\\.\\.\\.)/]\n    for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1)\n\n    var hangingIndent = parserConf.hangingIndent || conf.indentUnit;\n\n    var myKeywords = commonKeywords, myBuiltins = commonBuiltins;\n    if (parserConf.extra_keywords != undefined)\n      myKeywords = myKeywords.concat(parserConf.extra_keywords);\n\n    if (parserConf.extra_builtins != undefined)\n      myBuiltins = myBuiltins.concat(parserConf.extra_builtins);\n\n    var py3 = !(parserConf.version && Number(parserConf.version) < 3)\n    if (py3) {\n      // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator\n      var identifiers = parserConf.identifiers|| /^[_A-Za-z\\u00A1-\\uFFFF][_A-Za-z0-9\\u00A1-\\uFFFF]*/;\n      myKeywords = myKeywords.concat([\"nonlocal\", \"False\", \"True\", \"None\", \"async\", \"await\"]);\n      myBuiltins = myBuiltins.concat([\"ascii\", \"bytes\", \"exec\", \"print\"]);\n      var stringPrefixes = new RegExp(\"^(([rbuf]|(br)|(fr))?('{3}|\\\"{3}|['\\\"]))\", \"i\");\n    } else {\n      var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/;\n      myKeywords = myKeywords.concat([\"exec\", \"print\"]);\n      myBuiltins = myBuiltins.concat([\"apply\", \"basestring\", \"buffer\", \"cmp\", \"coerce\", \"execfile\",\n                                      \"file\", \"intern\", \"long\", \"raw_input\", \"reduce\", \"reload\",\n                                      \"unichr\", \"unicode\", \"xrange\", \"False\", \"True\", \"None\"]);\n      var stringPrefixes = new RegExp(\"^(([rubf]|(ur)|(br))?('{3}|\\\"{3}|['\\\"]))\", \"i\");\n    }\n    var keywords = wordRegexp(myKeywords);\n    var builtins = wordRegexp(myBuiltins);\n\n    // tokenizers\n    function tokenBase(stream, state) {\n      var sol = stream.sol() && state.lastToken != \"\\\\\"\n      if (sol) state.indent = stream.indentation()\n      // Handle scope changes\n      if (sol && top(state).type == \"py\") {\n        var scopeOffset = top(state).offset;\n        if (stream.eatSpace()) {\n          var lineOffset = stream.indentation();\n          if (lineOffset > scopeOffset)\n            pushPyScope(state);\n          else if (lineOffset < scopeOffset && dedent(stream, state) && stream.peek() != \"#\")\n            state.errorToken = true;\n          return null;\n        } else {\n          var style = tokenBaseInner(stream, state);\n          if (scopeOffset > 0 && dedent(stream, state))\n            style += \" \" + ERRORCLASS;\n          return style;\n        }\n      }\n      return tokenBaseInner(stream, state);\n    }\n\n    function tokenBaseInner(stream, state, inFormat) {\n      if (stream.eatSpace()) return null;\n\n      // Handle Comments\n      if (!inFormat && stream.match(/^#.*/)) return \"comment\";\n\n      // Handle Number Literals\n      if (stream.match(/^[0-9\\.]/, false)) {\n        var floatLiteral = false;\n        // Floats\n        if (stream.match(/^[\\d_]*\\.\\d+(e[\\+\\-]?\\d+)?/i)) { floatLiteral = true; }\n        if (stream.match(/^[\\d_]+\\.\\d*/)) { floatLiteral = true; }\n        if (stream.match(/^\\.\\d+/)) { floatLiteral = true; }\n        if (floatLiteral) {\n          // Float literals may be \"imaginary\"\n          stream.eat(/J/i);\n          return \"number\";\n        }\n        // Integers\n        var intLiteral = false;\n        // Hex\n        if (stream.match(/^0x[0-9a-f_]+/i)) intLiteral = true;\n        // Binary\n        if (stream.match(/^0b[01_]+/i)) intLiteral = true;\n        // Octal\n        if (stream.match(/^0o[0-7_]+/i)) intLiteral = true;\n        // Decimal\n        if (stream.match(/^[1-9][\\d_]*(e[\\+\\-]?[\\d_]+)?/)) {\n          // Decimal literals may be \"imaginary\"\n          stream.eat(/J/i);\n          // TODO - Can you have imaginary longs?\n          intLiteral = true;\n        }\n        // Zero by itself with no other piece of number.\n        if (stream.match(/^0(?![\\dx])/i)) intLiteral = true;\n        if (intLiteral) {\n          // Integer literals may be \"long\"\n          stream.eat(/L/i);\n          return \"number\";\n        }\n      }\n\n      // Handle Strings\n      if (stream.match(stringPrefixes)) {\n        var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1;\n        if (!isFmtString) {\n          state.tokenize = tokenStringFactory(stream.current(), state.tokenize);\n          return state.tokenize(stream, state);\n        } else {\n          state.tokenize = formatStringFactory(stream.current(), state.tokenize);\n          return state.tokenize(stream, state);\n        }\n      }\n\n      for (var i = 0; i < operators.length; i++)\n        if (stream.match(operators[i])) return \"operator\"\n\n      if (stream.match(delimiters)) return \"punctuation\";\n\n      if (state.lastToken == \".\" && stream.match(identifiers))\n        return \"property\";\n\n      if (stream.match(keywords) || stream.match(wordOperators))\n        return \"keyword\";\n\n      if (stream.match(builtins))\n        return \"builtin\";\n\n      if (stream.match(/^(self|cls)\\b/))\n        return \"variable-2\";\n\n      if (stream.match(identifiers)) {\n        if (state.lastToken == \"def\" || state.lastToken == \"class\")\n          return \"def\";\n        return \"variable\";\n      }\n\n      // Handle non-detected items\n      stream.next();\n      return inFormat ? null :ERRORCLASS;\n    }\n\n    function formatStringFactory(delimiter, tokenOuter) {\n      while (\"rubf\".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)\n        delimiter = delimiter.substr(1);\n\n      var singleline = delimiter.length == 1;\n      var OUTCLASS = \"string\";\n\n      function tokenNestedExpr(depth) {\n        return function(stream, state) {\n          var inner = tokenBaseInner(stream, state, true)\n          if (inner == \"punctuation\") {\n            if (stream.current() == \"{\") {\n              state.tokenize = tokenNestedExpr(depth + 1)\n            } else if (stream.current() == \"}\") {\n              if (depth > 1) state.tokenize = tokenNestedExpr(depth - 1)\n              else state.tokenize = tokenString\n            }\n          }\n          return inner\n        }\n      }\n\n      function tokenString(stream, state) {\n        while (!stream.eol()) {\n          stream.eatWhile(/[^'\"\\{\\}\\\\]/);\n          if (stream.eat(\"\\\\\")) {\n            stream.next();\n            if (singleline && stream.eol())\n              return OUTCLASS;\n          } else if (stream.match(delimiter)) {\n            state.tokenize = tokenOuter;\n            return OUTCLASS;\n          } else if (stream.match('{{')) {\n            // ignore {{ in f-str\n            return OUTCLASS;\n          } else if (stream.match('{', false)) {\n            // switch to nested mode\n            state.tokenize = tokenNestedExpr(0)\n            if (stream.current()) return OUTCLASS;\n            else return state.tokenize(stream, state)\n          } else if (stream.match('}}')) {\n            return OUTCLASS;\n          } else if (stream.match('}')) {\n            // single } in f-string is an error\n            return ERRORCLASS;\n          } else {\n            stream.eat(/['\"]/);\n          }\n        }\n        if (singleline) {\n          if (parserConf.singleLineStringErrors)\n            return ERRORCLASS;\n          else\n            state.tokenize = tokenOuter;\n        }\n        return OUTCLASS;\n      }\n      tokenString.isString = true;\n      return tokenString;\n    }\n\n    function tokenStringFactory(delimiter, tokenOuter) {\n      while (\"rubf\".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)\n        delimiter = delimiter.substr(1);\n\n      var singleline = delimiter.length == 1;\n      var OUTCLASS = \"string\";\n\n      function tokenString(stream, state) {\n        while (!stream.eol()) {\n          stream.eatWhile(/[^'\"\\\\]/);\n          if (stream.eat(\"\\\\\")) {\n            stream.next();\n            if (singleline && stream.eol())\n              return OUTCLASS;\n          } else if (stream.match(delimiter)) {\n            state.tokenize = tokenOuter;\n            return OUTCLASS;\n          } else {\n            stream.eat(/['\"]/);\n          }\n        }\n        if (singleline) {\n          if (parserConf.singleLineStringErrors)\n            return ERRORCLASS;\n          else\n            state.tokenize = tokenOuter;\n        }\n        return OUTCLASS;\n      }\n      tokenString.isString = true;\n      return tokenString;\n    }\n\n    function pushPyScope(state) {\n      while (top(state).type != \"py\") state.scopes.pop()\n      state.scopes.push({offset: top(state).offset + conf.indentUnit,\n                         type: \"py\",\n                         align: null})\n    }\n\n    function pushBracketScope(stream, state, type) {\n      var align = stream.match(/^([\\s\\[\\{\\(]|#.*)*$/, false) ? null : stream.column() + 1\n      state.scopes.push({offset: state.indent + hangingIndent,\n                         type: type,\n                         align: align})\n    }\n\n    function dedent(stream, state) {\n      var indented = stream.indentation();\n      while (state.scopes.length > 1 && top(state).offset > indented) {\n        if (top(state).type != \"py\") return true;\n        state.scopes.pop();\n      }\n      return top(state).offset != indented;\n    }\n\n    function tokenLexer(stream, state) {\n      if (stream.sol()) state.beginningOfLine = true;\n\n      var style = state.tokenize(stream, state);\n      var current = stream.current();\n\n      // Handle decorators\n      if (state.beginningOfLine && current == \"@\")\n        return stream.match(identifiers, false) ? \"meta\" : py3 ? \"operator\" : ERRORCLASS;\n\n      if (/\\S/.test(current)) state.beginningOfLine = false;\n\n      if ((style == \"variable\" || style == \"builtin\")\n          && state.lastToken == \"meta\")\n        style = \"meta\";\n\n      // Handle scope changes.\n      if (current == \"pass\" || current == \"return\")\n        state.dedent += 1;\n\n      if (current == \"lambda\") state.lambda = true;\n      if (current == \":\" && !state.lambda && top(state).type == \"py\")\n        pushPyScope(state);\n\n      if (current.length == 1 && !/string|comment/.test(style)) {\n        var delimiter_index = \"[({\".indexOf(current);\n        if (delimiter_index != -1)\n          pushBracketScope(stream, state, \"])}\".slice(delimiter_index, delimiter_index+1));\n\n        delimiter_index = \"])}\".indexOf(current);\n        if (delimiter_index != -1) {\n          if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent\n          else return ERRORCLASS;\n        }\n      }\n      if (state.dedent > 0 && stream.eol() && top(state).type == \"py\") {\n        if (state.scopes.length > 1) state.scopes.pop();\n        state.dedent -= 1;\n      }\n\n      return style;\n    }\n\n    var external = {\n      startState: function(basecolumn) {\n        return {\n          tokenize: tokenBase,\n          scopes: [{offset: basecolumn || 0, type: \"py\", align: null}],\n          indent: basecolumn || 0,\n          lastToken: null,\n          lambda: false,\n          dedent: 0\n        };\n      },\n\n      token: function(stream, state) {\n        var addErr = state.errorToken;\n        if (addErr) state.errorToken = false;\n        var style = tokenLexer(stream, state);\n\n        if (style && style != \"comment\")\n          state.lastToken = (style == \"keyword\" || style == \"punctuation\") ? stream.current() : style;\n        if (style == \"punctuation\") style = null;\n\n        if (stream.eol() && state.lambda)\n          state.lambda = false;\n        return addErr ? style + \" \" + ERRORCLASS : style;\n      },\n\n      indent: function(state, textAfter) {\n        if (state.tokenize != tokenBase)\n          return state.tokenize.isString ? CodeMirror.Pass : 0;\n\n        var scope = top(state), closing = scope.type == textAfter.charAt(0)\n        if (scope.align != null)\n          return scope.align - (closing ? 1 : 0)\n        else\n          return scope.offset - (closing ? hangingIndent : 0)\n      },\n\n      electricInput: /^\\s*[\\}\\]\\)]$/,\n      closeBrackets: {triples: \"'\\\"\"},\n      lineComment: \"#\",\n      fold: \"indent\"\n    };\n    return external;\n  });\n\n  CodeMirror.defineMIME(\"text/x-python\", \"python\");\n\n  var words = function(str) { return str.split(\" \"); };\n\n  CodeMirror.defineMIME(\"text/x-cython\", {\n    name: \"python\",\n    extra_keywords: words(\"by cdef cimport cpdef ctypedef enum except \"+\n                          \"extern gil include nogil property public \"+\n                          \"readonly struct union DEF IF ELIF ELSE\")\n  });\n\n});\n\n\n/* ---- mode/rust.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../../addon/mode/simple\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../../addon/mode/simple\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineSimpleMode(\"rust\",{\n  start: [\n    // string and byte string\n    {regex: /b?\"/, token: \"string\", next: \"string\"},\n    // raw string and raw byte string\n    {regex: /b?r\"/, token: \"string\", next: \"string_raw\"},\n    {regex: /b?r#+\"/, token: \"string\", next: \"string_raw_hash\"},\n    // character\n    {regex: /'(?:[^'\\\\]|\\\\(?:[nrt0'\"]|x[\\da-fA-F]{2}|u\\{[\\da-fA-F]{6}\\}))'/, token: \"string-2\"},\n    // byte\n    {regex: /b'(?:[^']|\\\\(?:['\\\\nrt0]|x[\\da-fA-F]{2}))'/, token: \"string-2\"},\n\n    {regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/,\n     token: \"number\"},\n    {regex: /(let(?:\\s+mut)?|fn|enum|mod|struct|type|union)(\\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: [\"keyword\", null, \"def\"]},\n    {regex: /(?:abstract|alignof|as|async|await|box|break|continue|const|crate|do|dyn|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\\b/, token: \"keyword\"},\n    {regex: /\\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\\b/, token: \"atom\"},\n    {regex: /\\b(?:true|false|Some|None|Ok|Err)\\b/, token: \"builtin\"},\n    {regex: /\\b(fn)(\\s+)([a-zA-Z_][a-zA-Z0-9_]*)/,\n     token: [\"keyword\", null ,\"def\"]},\n    {regex: /#!?\\[.*\\]/, token: \"meta\"},\n    {regex: /\\/\\/.*/, token: \"comment\"},\n    {regex: /\\/\\*/, token: \"comment\", next: \"comment\"},\n    {regex: /[-+\\/*=<>!]+/, token: \"operator\"},\n    {regex: /[a-zA-Z_]\\w*!/,token: \"variable-3\"},\n    {regex: /[a-zA-Z_]\\w*/, token: \"variable\"},\n    {regex: /[\\{\\[\\(]/, indent: true},\n    {regex: /[\\}\\]\\)]/, dedent: true}\n  ],\n  string: [\n    {regex: /\"/, token: \"string\", next: \"start\"},\n    {regex: /(?:[^\\\\\"]|\\\\(?:.|$))*/, token: \"string\"}\n  ],\n  string_raw: [\n    {regex: /\"/, token: \"string\", next: \"start\"},\n    {regex: /[^\"]*/, token: \"string\"}\n  ],\n  string_raw_hash: [\n    {regex: /\"#+/, token: \"string\", next: \"start\"},\n    {regex: /(?:[^\"]|\"(?!#))*/, token: \"string\"}\n  ],\n  comment: [\n    {regex: /.*?\\*\\//, token: \"comment\", next: \"start\"},\n    {regex: /.*/, token: \"comment\"}\n  ],\n  meta: {\n    dontIndentStates: [\"comment\"],\n    electricInput: /^\\s*\\}$/,\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    lineComment: \"//\",\n    fold: \"brace\"\n  }\n});\n\n\nCodeMirror.defineMIME(\"text/x-rustsrc\", \"rust\");\nCodeMirror.defineMIME(\"text/rust\", \"rust\");\n});\n\n\n/* ---- mode/xml.js ---- */\n\n\n// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nvar htmlConfig = {\n  autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,\n                    'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,\n                    'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,\n                    'track': true, 'wbr': true, 'menuitem': true},\n  implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,\n                     'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,\n                     'th': true, 'tr': true},\n  contextGrabbers: {\n    'dd': {'dd': true, 'dt': true},\n    'dt': {'dd': true, 'dt': true},\n    'li': {'li': true},\n    'option': {'option': true, 'optgroup': true},\n    'optgroup': {'optgroup': true},\n    'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,\n          'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,\n          'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,\n          'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,\n          'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},\n    'rp': {'rp': true, 'rt': true},\n    'rt': {'rp': true, 'rt': true},\n    'tbody': {'tbody': true, 'tfoot': true},\n    'td': {'td': true, 'th': true},\n    'tfoot': {'tbody': true},\n    'th': {'td': true, 'th': true},\n    'thead': {'tbody': true, 'tfoot': true},\n    'tr': {'tr': true}\n  },\n  doNotIndent: {\"pre\": true},\n  allowUnquoted: true,\n  allowMissing: true,\n  caseFold: true\n}\n\nvar xmlConfig = {\n  autoSelfClosers: {},\n  implicitlyClosed: {},\n  contextGrabbers: {},\n  doNotIndent: {},\n  allowUnquoted: false,\n  allowMissing: false,\n  allowMissingTagName: false,\n  caseFold: false\n}\n\nCodeMirror.defineMode(\"xml\", function(editorConf, config_) {\n  var indentUnit = editorConf.indentUnit\n  var config = {}\n  var defaults = config_.htmlMode ? htmlConfig : xmlConfig\n  for (var prop in defaults) config[prop] = defaults[prop]\n  for (var prop in config_) config[prop] = config_[prop]\n\n  // Return variables for tokenizers\n  var type, setStyle;\n\n  function inText(stream, state) {\n    function chain(parser) {\n      state.tokenize = parser;\n      return parser(stream, state);\n    }\n\n    var ch = stream.next();\n    if (ch == \"<\") {\n      if (stream.eat(\"!\")) {\n        if (stream.eat(\"[\")) {\n          if (stream.match(\"CDATA[\")) return chain(inBlock(\"atom\", \"]]>\"));\n          else return null;\n        } else if (stream.match(\"--\")) {\n          return chain(inBlock(\"comment\", \"-->\"));\n        } else if (stream.match(\"DOCTYPE\", true, true)) {\n          stream.eatWhile(/[\\w\\._\\-]/);\n          return chain(doctype(1));\n        } else {\n          return null;\n        }\n      } else if (stream.eat(\"?\")) {\n        stream.eatWhile(/[\\w\\._\\-]/);\n        state.tokenize = inBlock(\"meta\", \"?>\");\n        return \"meta\";\n      } else {\n        type = stream.eat(\"/\") ? \"closeTag\" : \"openTag\";\n        state.tokenize = inTag;\n        return \"tag bracket\";\n      }\n    } else if (ch == \"&\") {\n      var ok;\n      if (stream.eat(\"#\")) {\n        if (stream.eat(\"x\")) {\n          ok = stream.eatWhile(/[a-fA-F\\d]/) && stream.eat(\";\");\n        } else {\n          ok = stream.eatWhile(/[\\d]/) && stream.eat(\";\");\n        }\n      } else {\n        ok = stream.eatWhile(/[\\w\\.\\-:]/) && stream.eat(\";\");\n      }\n      return ok ? \"atom\" : \"error\";\n    } else {\n      stream.eatWhile(/[^&<]/);\n      return null;\n    }\n  }\n  inText.isInText = true;\n\n  function inTag(stream, state) {\n    var ch = stream.next();\n    if (ch == \">\" || (ch == \"/\" && stream.eat(\">\"))) {\n      state.tokenize = inText;\n      type = ch == \">\" ? \"endTag\" : \"selfcloseTag\";\n      return \"tag bracket\";\n    } else if (ch == \"=\") {\n      type = \"equals\";\n      return null;\n    } else if (ch == \"<\") {\n      state.tokenize = inText;\n      state.state = baseState;\n      state.tagName = state.tagStart = null;\n      var next = state.tokenize(stream, state);\n      return next ? next + \" tag error\" : \"tag error\";\n    } else if (/[\\'\\\"]/.test(ch)) {\n      state.tokenize = inAttribute(ch);\n      state.stringStartCol = stream.column();\n      return state.tokenize(stream, state);\n    } else {\n      stream.match(/^[^\\s\\u00a0=<>\\\"\\']*[^\\s\\u00a0=<>\\\"\\'\\/]/);\n      return \"word\";\n    }\n  }\n\n  function inAttribute(quote) {\n    var closure = function(stream, state) {\n      while (!stream.eol()) {\n        if (stream.next() == quote) {\n          state.tokenize = inTag;\n          break;\n        }\n      }\n      return \"string\";\n    };\n    closure.isInAttribute = true;\n    return closure;\n  }\n\n  function inBlock(style, terminator) {\n    return function(stream, state) {\n      while (!stream.eol()) {\n        if (stream.match(terminator)) {\n          state.tokenize = inText;\n          break;\n        }\n        stream.next();\n      }\n      return style;\n    }\n  }\n\n  function doctype(depth) {\n    return function(stream, state) {\n      var ch;\n      while ((ch = stream.next()) != null) {\n        if (ch == \"<\") {\n          state.tokenize = doctype(depth + 1);\n          return state.tokenize(stream, state);\n        } else if (ch == \">\") {\n          if (depth == 1) {\n            state.tokenize = inText;\n            break;\n          } else {\n            state.tokenize = doctype(depth - 1);\n            return state.tokenize(stream, state);\n          }\n        }\n      }\n      return \"meta\";\n    };\n  }\n\n  function Context(state, tagName, startOfLine) {\n    this.prev = state.context;\n    this.tagName = tagName;\n    this.indent = state.indented;\n    this.startOfLine = startOfLine;\n    if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))\n      this.noIndent = true;\n  }\n  function popContext(state) {\n    if (state.context) state.context = state.context.prev;\n  }\n  function maybePopContext(state, nextTagName) {\n    var parentTagName;\n    while (true) {\n      if (!state.context) {\n        return;\n      }\n      parentTagName = state.context.tagName;\n      if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||\n          !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {\n        return;\n      }\n      popContext(state);\n    }\n  }\n\n  function baseState(type, stream, state) {\n    if (type == \"openTag\") {\n      state.tagStart = stream.column();\n      return tagNameState;\n    } else if (type == \"closeTag\") {\n      return closeTagNameState;\n    } else {\n      return baseState;\n    }\n  }\n  function tagNameState(type, stream, state) {\n    if (type == \"word\") {\n      state.tagName = stream.current();\n      setStyle = \"tag\";\n      return attrState;\n    } else if (config.allowMissingTagName && type == \"endTag\") {\n      setStyle = \"tag bracket\";\n      return attrState(type, stream, state);\n    } else {\n      setStyle = \"error\";\n      return tagNameState;\n    }\n  }\n  function closeTagNameState(type, stream, state) {\n    if (type == \"word\") {\n      var tagName = stream.current();\n      if (state.context && state.context.tagName != tagName &&\n          config.implicitlyClosed.hasOwnProperty(state.context.tagName))\n        popContext(state);\n      if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {\n        setStyle = \"tag\";\n        return closeState;\n      } else {\n        setStyle = \"tag error\";\n        return closeStateErr;\n      }\n    } else if (config.allowMissingTagName && type == \"endTag\") {\n      setStyle = \"tag bracket\";\n      return closeState(type, stream, state);\n    } else {\n      setStyle = \"error\";\n      return closeStateErr;\n    }\n  }\n\n  function closeState(type, _stream, state) {\n    if (type != \"endTag\") {\n      setStyle = \"error\";\n      return closeState;\n    }\n    popContext(state);\n    return baseState;\n  }\n  function closeStateErr(type, stream, state) {\n    setStyle = \"error\";\n    return closeState(type, stream, state);\n  }\n\n  function attrState(type, _stream, state) {\n    if (type == \"word\") {\n      setStyle = \"attribute\";\n      return attrEqState;\n    } else if (type == \"endTag\" || type == \"selfcloseTag\") {\n      var tagName = state.tagName, tagStart = state.tagStart;\n      state.tagName = state.tagStart = null;\n      if (type == \"selfcloseTag\" ||\n          config.autoSelfClosers.hasOwnProperty(tagName)) {\n        maybePopContext(state, tagName);\n      } else {\n        maybePopContext(state, tagName);\n        state.context = new Context(state, tagName, tagStart == state.indented);\n      }\n      return baseState;\n    }\n    setStyle = \"error\";\n    return attrState;\n  }\n  function attrEqState(type, stream, state) {\n    if (type == \"equals\") return attrValueState;\n    if (!config.allowMissing) setStyle = \"error\";\n    return attrState(type, stream, state);\n  }\n  function attrValueState(type, stream, state) {\n    if (type == \"string\") return attrContinuedState;\n    if (type == \"word\" && config.allowUnquoted) {setStyle = \"string\"; return attrState;}\n    setStyle = \"error\";\n    return attrState(type, stream, state);\n  }\n  function attrContinuedState(type, stream, state) {\n    if (type == \"string\") return attrContinuedState;\n    return attrState(type, stream, state);\n  }\n\n  return {\n    startState: function(baseIndent) {\n      var state = {tokenize: inText,\n                   state: baseState,\n                   indented: baseIndent || 0,\n                   tagName: null, tagStart: null,\n                   context: null}\n      if (baseIndent != null) state.baseIndent = baseIndent\n      return state\n    },\n\n    token: function(stream, state) {\n      if (!state.tagName && stream.sol())\n        state.indented = stream.indentation();\n\n      if (stream.eatSpace()) return null;\n      type = null;\n      var style = state.tokenize(stream, state);\n      if ((style || type) && style != \"comment\") {\n        setStyle = null;\n        state.state = state.state(type || style, stream, state);\n        if (setStyle)\n          style = setStyle == \"error\" ? style + \" error\" : setStyle;\n      }\n      return style;\n    },\n\n    indent: function(state, textAfter, fullLine) {\n      var context = state.context;\n      // Indent multi-line strings (e.g. css).\n      if (state.tokenize.isInAttribute) {\n        if (state.tagStart == state.indented)\n          return state.stringStartCol + 1;\n        else\n          return state.indented + indentUnit;\n      }\n      if (context && context.noIndent) return CodeMirror.Pass;\n      if (state.tokenize != inTag && state.tokenize != inText)\n        return fullLine ? fullLine.match(/^(\\s*)/)[0].length : 0;\n      // Indent the starts of attribute names.\n      if (state.tagName) {\n        if (config.multilineTagIndentPastTag !== false)\n          return state.tagStart + state.tagName.length + 2;\n        else\n          return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);\n      }\n      if (config.alignCDATA && /<!\\[CDATA\\[/.test(textAfter)) return 0;\n      var tagAfter = textAfter && /^<(\\/)?([\\w_:\\.-]*)/.exec(textAfter);\n      if (tagAfter && tagAfter[1]) { // Closing tag spotted\n        while (context) {\n          if (context.tagName == tagAfter[2]) {\n            context = context.prev;\n            break;\n          } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {\n            context = context.prev;\n          } else {\n            break;\n          }\n        }\n      } else if (tagAfter) { // Opening tag spotted\n        while (context) {\n          var grabbers = config.contextGrabbers[context.tagName];\n          if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))\n            context = context.prev;\n          else\n            break;\n        }\n      }\n      while (context && context.prev && !context.startOfLine)\n        context = context.prev;\n      if (context) return context.indent + indentUnit;\n      else return state.baseIndent || 0;\n    },\n\n    electricInput: /<\\/[\\s\\w:]+>$/,\n    blockCommentStart: \"<!--\",\n    blockCommentEnd: \"-->\",\n\n    configuration: config.htmlMode ? \"html\" : \"xml\",\n    helperType: config.htmlMode ? \"html\" : \"xml\",\n\n    skipAttribute: function(state) {\n      if (state.state == attrValueState)\n        state.state = attrState\n    },\n\n    xmlCurrentTag: function(state) {\n      return state.tagName ? {name: state.tagName, close: state.type == \"closeTag\"} : null\n    },\n\n    xmlCurrentContext: function(state) {\n      var context = []\n      for (var cx = state.context; cx; cx = cx.prev)\n        if (cx.tagName) context.push(cx.tagName)\n      return context.reverse()\n    }\n  };\n});\n\nCodeMirror.defineMIME(\"text/xml\", \"xml\");\nCodeMirror.defineMIME(\"application/xml\", \"xml\");\nif (!CodeMirror.mimeModes.hasOwnProperty(\"text/html\"))\n  CodeMirror.defineMIME(\"text/html\", {name: \"xml\", htmlMode: true});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/base/codemirror.css",
    "content": "/* BASICS */\n\n.CodeMirror {\n  /* Set height, width, borders, and global font properties here */\n  font-family: monospace;\n  height: 300px;\n  color: black;\n  direction: ltr;\n}\n\n/* PADDING */\n\n.CodeMirror-lines {\n  padding: 4px 0; /* Vertical padding around content */\n}\n.CodeMirror pre.CodeMirror-line,\n.CodeMirror pre.CodeMirror-line-like {\n  padding: 0 4px; /* Horizontal padding of content */\n}\n\n.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  background-color: white; /* The little square between H and V scrollbars */\n}\n\n/* GUTTER */\n\n.CodeMirror-gutters {\n  border-right: 1px solid #ddd;\n  background-color: #f7f7f7;\n  white-space: nowrap;\n}\n.CodeMirror-linenumbers {}\n.CodeMirror-linenumber {\n  padding: 0 3px 0 5px;\n  min-width: 20px;\n  text-align: right;\n  color: #999;\n  white-space: nowrap;\n}\n\n.CodeMirror-guttermarker { color: black; }\n.CodeMirror-guttermarker-subtle { color: #999; }\n\n/* CURSOR */\n\n.CodeMirror-cursor {\n  border-left: 1px solid black;\n  border-right: none;\n  width: 0;\n}\n/* Shown when moving in bi-directional text */\n.CodeMirror div.CodeMirror-secondarycursor {\n  border-left: 1px solid silver;\n}\n.cm-fat-cursor .CodeMirror-cursor {\n  width: auto;\n  border: 0 !important;\n  background: #7e7;\n}\n.cm-fat-cursor div.CodeMirror-cursors {\n  z-index: 1;\n}\n.cm-fat-cursor-mark {\n  background-color: rgba(20, 255, 20, 0.5);\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  animation: blink 1.06s steps(1) infinite;\n}\n.cm-animate-fat-cursor {\n  width: auto;\n  border: 0;\n  -webkit-animation: blink 1.06s steps(1) infinite;\n  -moz-animation: blink 1.06s steps(1) infinite;\n  animation: blink 1.06s steps(1) infinite;\n  background-color: #7e7;\n}\n@-moz-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@-webkit-keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n@keyframes blink {\n  0% {}\n  50% { background-color: transparent; }\n  100% {}\n}\n\n/* Can style cursor different in overwrite (non-insert) mode */\n.CodeMirror-overwrite .CodeMirror-cursor {}\n\n.cm-tab { display: inline-block; text-decoration: inherit; }\n\n.CodeMirror-rulers {\n  position: absolute;\n  left: 0; right: 0; top: -50px; bottom: 0;\n  overflow: hidden;\n}\n.CodeMirror-ruler {\n  border-left: 1px solid #ccc;\n  top: 0; bottom: 0;\n  position: absolute;\n}\n\n/* DEFAULT THEME */\n\n.cm-s-default .cm-header {color: blue;}\n.cm-s-default .cm-quote {color: #090;}\n.cm-negative {color: #d44;}\n.cm-positive {color: #292;}\n.cm-header, .cm-strong {font-weight: bold;}\n.cm-em {font-style: italic;}\n.cm-link {text-decoration: underline;}\n.cm-strikethrough {text-decoration: line-through;}\n\n.cm-s-default .cm-keyword {color: #708;}\n.cm-s-default .cm-atom {color: #219;}\n.cm-s-default .cm-number {color: #164;}\n.cm-s-default .cm-def {color: #00f;}\n.cm-s-default .cm-variable,\n.cm-s-default .cm-punctuation,\n.cm-s-default .cm-property,\n.cm-s-default .cm-operator {}\n.cm-s-default .cm-variable-2 {color: #05a;}\n.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}\n.cm-s-default .cm-comment {color: #a50;}\n.cm-s-default .cm-string {color: #a11;}\n.cm-s-default .cm-string-2 {color: #f50;}\n.cm-s-default .cm-meta {color: #555;}\n.cm-s-default .cm-qualifier {color: #555;}\n.cm-s-default .cm-builtin {color: #30a;}\n.cm-s-default .cm-bracket {color: #997;}\n.cm-s-default .cm-tag {color: #170;}\n.cm-s-default .cm-attribute {color: #00c;}\n.cm-s-default .cm-hr {color: #999;}\n.cm-s-default .cm-link {color: #00c;}\n\n.cm-s-default .cm-error {color: #f00;}\n.cm-invalidchar {color: #f00;}\n\n.CodeMirror-composing { border-bottom: 2px solid; }\n\n/* Default styles for common addons */\n\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}\n.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }\n.CodeMirror-activeline-background {background: #e8f2ff;}\n\n/* STOP */\n\n/* The rest of this file contains styles related to the mechanics of\n   the editor. You probably shouldn't touch them. */\n\n.CodeMirror {\n  position: relative;\n  overflow: hidden;\n  background: white;\n}\n\n.CodeMirror-scroll {\n  overflow: scroll !important; /* Things will break if this is overridden */\n  /* 50px is the magic margin used to hide the element's real scrollbars */\n  /* See overflow: hidden in .CodeMirror */\n  margin-bottom: -50px; margin-right: -50px;\n  padding-bottom: 50px;\n  height: 100%;\n  outline: none; /* Prevent dragging from highlighting the element */\n  position: relative;\n}\n.CodeMirror-sizer {\n  position: relative;\n  border-right: 50px solid transparent;\n}\n\n/* The fake, visible scrollbars. Used to force redraw during scrolling\n   before actual scrolling happens, thus preventing shaking and\n   flickering artifacts. */\n.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {\n  position: absolute;\n  z-index: 6;\n  display: none;\n}\n.CodeMirror-vscrollbar {\n  right: 0; top: 0;\n  overflow-x: hidden;\n  overflow-y: scroll;\n}\n.CodeMirror-hscrollbar {\n  bottom: 0; left: 0;\n  overflow-y: hidden;\n  overflow-x: scroll;\n}\n.CodeMirror-scrollbar-filler {\n  right: 0; bottom: 0;\n}\n.CodeMirror-gutter-filler {\n  left: 0; bottom: 0;\n}\n\n.CodeMirror-gutters {\n  position: absolute; left: 0; top: 0;\n  min-height: 100%;\n  z-index: 3;\n}\n.CodeMirror-gutter {\n  white-space: normal;\n  height: 100%;\n  display: inline-block;\n  vertical-align: top;\n  margin-bottom: -50px;\n}\n.CodeMirror-gutter-wrapper {\n  position: absolute;\n  z-index: 4;\n  background: none !important;\n  border: none !important;\n}\n.CodeMirror-gutter-background {\n  position: absolute;\n  top: 0; bottom: 0;\n  z-index: 4;\n}\n.CodeMirror-gutter-elt {\n  position: absolute;\n  cursor: default;\n  z-index: 4;\n}\n.CodeMirror-gutter-wrapper ::selection { background-color: transparent }\n.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }\n\n.CodeMirror-lines {\n  cursor: text;\n  min-height: 1px; /* prevents collapsing before first draw */\n}\n.CodeMirror pre.CodeMirror-line,\n.CodeMirror pre.CodeMirror-line-like {\n  /* Reset some styles that the rest of the page might have set */\n  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;\n  border-width: 0;\n  background: transparent;\n  font-family: inherit;\n  font-size: inherit;\n  margin: 0;\n  white-space: pre;\n  word-wrap: normal;\n  line-height: inherit;\n  color: inherit;\n  z-index: 2;\n  position: relative;\n  overflow: visible;\n  -webkit-tap-highlight-color: transparent;\n  -webkit-font-variant-ligatures: contextual;\n  font-variant-ligatures: contextual;\n}\n.CodeMirror-wrap pre.CodeMirror-line,\n.CodeMirror-wrap pre.CodeMirror-line-like {\n  word-wrap: break-word;\n  white-space: pre-wrap;\n  word-break: normal;\n}\n\n.CodeMirror-linebackground {\n  position: absolute;\n  left: 0; right: 0; top: 0; bottom: 0;\n  z-index: 0;\n}\n\n.CodeMirror-linewidget {\n  position: relative;\n  z-index: 2;\n  padding: 0.1px; /* Force widget margins to stay inside of the container */\n}\n\n.CodeMirror-widget {}\n\n.CodeMirror-rtl pre { direction: rtl; }\n\n.CodeMirror-code {\n  outline: none;\n}\n\n/* Force content-box sizing for the elements where we expect it */\n.CodeMirror-scroll,\n.CodeMirror-sizer,\n.CodeMirror-gutter,\n.CodeMirror-gutters,\n.CodeMirror-linenumber {\n  -moz-box-sizing: content-box;\n  box-sizing: content-box;\n}\n\n.CodeMirror-measure {\n  position: absolute;\n  width: 100%;\n  height: 0;\n  overflow: hidden;\n  visibility: hidden;\n}\n\n.CodeMirror-cursor {\n  position: absolute;\n  pointer-events: none;\n}\n.CodeMirror-measure pre { position: static; }\n\ndiv.CodeMirror-cursors {\n  visibility: hidden;\n  position: relative;\n  z-index: 3;\n}\ndiv.CodeMirror-dragcursors {\n  visibility: visible;\n}\n\n.CodeMirror-focused div.CodeMirror-cursors {\n  visibility: visible;\n}\n\n.CodeMirror-selected { background: #d9d9d9; }\n.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }\n.CodeMirror-crosshair { cursor: crosshair; }\n.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }\n.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }\n\n.cm-searching {\n  background-color: #ffa;\n  background-color: rgba(255, 255, 0, .4);\n}\n\n/* Used to force a border model for a node */\n.cm-force-border { padding-right: .1px; }\n\n@media print {\n  /* Hide the cursor when printing */\n  .CodeMirror div.CodeMirror-cursors {\n    visibility: hidden;\n  }\n}\n\n/* See issue #2901 */\n.cm-tab-wrap-hack:after { content: ''; }\n\n/* Help users use markselection to safely style text background */\nspan.CodeMirror-selectedtext { background: none; }\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/base/codemirror.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// This is CodeMirror (https://codemirror.net), a code editor\n// implemented in JavaScript on top of the browser's DOM.\n//\n// You can find some technical background for some of the code below\n// at http://marijnhaverbeke.nl/blog/#cm-internals .\n\n(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n  typeof define === 'function' && define.amd ? define(factory) :\n  (global = global || self, global.CodeMirror = factory());\n}(this, (function () { 'use strict';\n\n  // Kludges for bugs and behavior differences that can't be feature\n  // detected are enabled based on userAgent etc sniffing.\n  var userAgent = navigator.userAgent;\n  var platform = navigator.platform;\n\n  var gecko = /gecko\\/\\d/i.test(userAgent);\n  var ie_upto10 = /MSIE \\d/.test(userAgent);\n  var ie_11up = /Trident\\/(?:[7-9]|\\d{2,})\\..*rv:(\\d+)/.exec(userAgent);\n  var edge = /Edge\\/(\\d+)/.exec(userAgent);\n  var ie = ie_upto10 || ie_11up || edge;\n  var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]);\n  var webkit = !edge && /WebKit\\//.test(userAgent);\n  var qtwebkit = webkit && /Qt\\/\\d+\\.\\d+/.test(userAgent);\n  var chrome = !edge && /Chrome\\//.test(userAgent);\n  var presto = /Opera\\//.test(userAgent);\n  var safari = /Apple Computer/.test(navigator.vendor);\n  var mac_geMountainLion = /Mac OS X 1\\d\\D([8-9]|\\d\\d)\\D/.test(userAgent);\n  var phantom = /PhantomJS/.test(userAgent);\n\n  var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\\/\\w+/.test(userAgent);\n  var android = /Android/.test(userAgent);\n  // This is woefully incomplete. Suggestions for alternative methods welcome.\n  var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);\n  var mac = ios || /Mac/.test(platform);\n  var chromeOS = /\\bCrOS\\b/.test(userAgent);\n  var windows = /win/i.test(platform);\n\n  var presto_version = presto && userAgent.match(/Version\\/(\\d*\\.\\d*)/);\n  if (presto_version) { presto_version = Number(presto_version[1]); }\n  if (presto_version && presto_version >= 15) { presto = false; webkit = true; }\n  // Some browsers use the wrong event properties to signal cmd/ctrl on OS X\n  var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11));\n  var captureRightClick = gecko || (ie && ie_version >= 9);\n\n  function classTest(cls) { return new RegExp(\"(^|\\\\s)\" + cls + \"(?:$|\\\\s)\\\\s*\") }\n\n  var rmClass = function(node, cls) {\n    var current = node.className;\n    var match = classTest(cls).exec(current);\n    if (match) {\n      var after = current.slice(match.index + match[0].length);\n      node.className = current.slice(0, match.index) + (after ? match[1] + after : \"\");\n    }\n  };\n\n  function removeChildren(e) {\n    for (var count = e.childNodes.length; count > 0; --count)\n      { e.removeChild(e.firstChild); }\n    return e\n  }\n\n  function removeChildrenAndAdd(parent, e) {\n    return removeChildren(parent).appendChild(e)\n  }\n\n  function elt(tag, content, className, style) {\n    var e = document.createElement(tag);\n    if (className) { e.className = className; }\n    if (style) { e.style.cssText = style; }\n    if (typeof content == \"string\") { e.appendChild(document.createTextNode(content)); }\n    else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } }\n    return e\n  }\n  // wrapper for elt, which removes the elt from the accessibility tree\n  function eltP(tag, content, className, style) {\n    var e = elt(tag, content, className, style);\n    e.setAttribute(\"role\", \"presentation\");\n    return e\n  }\n\n  var range;\n  if (document.createRange) { range = function(node, start, end, endNode) {\n    var r = document.createRange();\n    r.setEnd(endNode || node, end);\n    r.setStart(node, start);\n    return r\n  }; }\n  else { range = function(node, start, end) {\n    var r = document.body.createTextRange();\n    try { r.moveToElementText(node.parentNode); }\n    catch(e) { return r }\n    r.collapse(true);\n    r.moveEnd(\"character\", end);\n    r.moveStart(\"character\", start);\n    return r\n  }; }\n\n  function contains(parent, child) {\n    if (child.nodeType == 3) // Android browser always returns false when child is a textnode\n      { child = child.parentNode; }\n    if (parent.contains)\n      { return parent.contains(child) }\n    do {\n      if (child.nodeType == 11) { child = child.host; }\n      if (child == parent) { return true }\n    } while (child = child.parentNode)\n  }\n\n  function activeElt() {\n    // IE and Edge may throw an \"Unspecified Error\" when accessing document.activeElement.\n    // IE < 10 will throw when accessed while the page is loading or in an iframe.\n    // IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.\n    var activeElement;\n    try {\n      activeElement = document.activeElement;\n    } catch(e) {\n      activeElement = document.body || null;\n    }\n    while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)\n      { activeElement = activeElement.shadowRoot.activeElement; }\n    return activeElement\n  }\n\n  function addClass(node, cls) {\n    var current = node.className;\n    if (!classTest(cls).test(current)) { node.className += (current ? \" \" : \"\") + cls; }\n  }\n  function joinClasses(a, b) {\n    var as = a.split(\" \");\n    for (var i = 0; i < as.length; i++)\n      { if (as[i] && !classTest(as[i]).test(b)) { b += \" \" + as[i]; } }\n    return b\n  }\n\n  var selectInput = function(node) { node.select(); };\n  if (ios) // Mobile Safari apparently has a bug where select() is broken.\n    { selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; }\n  else if (ie) // Suppress mysterious IE10 errors\n    { selectInput = function(node) { try { node.select(); } catch(_e) {} }; }\n\n  function bind(f) {\n    var args = Array.prototype.slice.call(arguments, 1);\n    return function(){return f.apply(null, args)}\n  }\n\n  function copyObj(obj, target, overwrite) {\n    if (!target) { target = {}; }\n    for (var prop in obj)\n      { if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop)))\n        { target[prop] = obj[prop]; } }\n    return target\n  }\n\n  // Counts the column offset in a string, taking tabs into account.\n  // Used mostly to find indentation.\n  function countColumn(string, end, tabSize, startIndex, startValue) {\n    if (end == null) {\n      end = string.search(/[^\\s\\u00a0]/);\n      if (end == -1) { end = string.length; }\n    }\n    for (var i = startIndex || 0, n = startValue || 0;;) {\n      var nextTab = string.indexOf(\"\\t\", i);\n      if (nextTab < 0 || nextTab >= end)\n        { return n + (end - i) }\n      n += nextTab - i;\n      n += tabSize - (n % tabSize);\n      i = nextTab + 1;\n    }\n  }\n\n  var Delayed = function() {\n    this.id = null;\n    this.f = null;\n    this.time = 0;\n    this.handler = bind(this.onTimeout, this);\n  };\n  Delayed.prototype.onTimeout = function (self) {\n    self.id = 0;\n    if (self.time <= +new Date) {\n      self.f();\n    } else {\n      setTimeout(self.handler, self.time - +new Date);\n    }\n  };\n  Delayed.prototype.set = function (ms, f) {\n    this.f = f;\n    var time = +new Date + ms;\n    if (!this.id || time < this.time) {\n      clearTimeout(this.id);\n      this.id = setTimeout(this.handler, ms);\n      this.time = time;\n    }\n  };\n\n  function indexOf(array, elt) {\n    for (var i = 0; i < array.length; ++i)\n      { if (array[i] == elt) { return i } }\n    return -1\n  }\n\n  // Number of pixels added to scroller and sizer to hide scrollbar\n  var scrollerGap = 50;\n\n  // Returned or thrown by various protocols to signal 'I'm not\n  // handling this'.\n  var Pass = {toString: function(){return \"CodeMirror.Pass\"}};\n\n  // Reused option objects for setSelection & friends\n  var sel_dontScroll = {scroll: false}, sel_mouse = {origin: \"*mouse\"}, sel_move = {origin: \"+move\"};\n\n  // The inverse of countColumn -- find the offset that corresponds to\n  // a particular column.\n  function findColumn(string, goal, tabSize) {\n    for (var pos = 0, col = 0;;) {\n      var nextTab = string.indexOf(\"\\t\", pos);\n      if (nextTab == -1) { nextTab = string.length; }\n      var skipped = nextTab - pos;\n      if (nextTab == string.length || col + skipped >= goal)\n        { return pos + Math.min(skipped, goal - col) }\n      col += nextTab - pos;\n      col += tabSize - (col % tabSize);\n      pos = nextTab + 1;\n      if (col >= goal) { return pos }\n    }\n  }\n\n  var spaceStrs = [\"\"];\n  function spaceStr(n) {\n    while (spaceStrs.length <= n)\n      { spaceStrs.push(lst(spaceStrs) + \" \"); }\n    return spaceStrs[n]\n  }\n\n  function lst(arr) { return arr[arr.length-1] }\n\n  function map(array, f) {\n    var out = [];\n    for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); }\n    return out\n  }\n\n  function insertSorted(array, value, score) {\n    var pos = 0, priority = score(value);\n    while (pos < array.length && score(array[pos]) <= priority) { pos++; }\n    array.splice(pos, 0, value);\n  }\n\n  function nothing() {}\n\n  function createObj(base, props) {\n    var inst;\n    if (Object.create) {\n      inst = Object.create(base);\n    } else {\n      nothing.prototype = base;\n      inst = new nothing();\n    }\n    if (props) { copyObj(props, inst); }\n    return inst\n  }\n\n  var nonASCIISingleCaseWordChar = /[\\u00df\\u0587\\u0590-\\u05f4\\u0600-\\u06ff\\u3040-\\u309f\\u30a0-\\u30ff\\u3400-\\u4db5\\u4e00-\\u9fcc\\uac00-\\ud7af]/;\n  function isWordCharBasic(ch) {\n    return /\\w/.test(ch) || ch > \"\\x80\" &&\n      (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))\n  }\n  function isWordChar(ch, helper) {\n    if (!helper) { return isWordCharBasic(ch) }\n    if (helper.source.indexOf(\"\\\\w\") > -1 && isWordCharBasic(ch)) { return true }\n    return helper.test(ch)\n  }\n\n  function isEmpty(obj) {\n    for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }\n    return true\n  }\n\n  // Extending unicode characters. A series of a non-extending char +\n  // any number of extending chars is treated as a single unit as far\n  // as editing and measuring is concerned. This is not fully correct,\n  // since some scripts/fonts/browsers also treat other configurations\n  // of code points as a group.\n  var extendingChars = /[\\u0300-\\u036f\\u0483-\\u0489\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u064b-\\u065e\\u0670\\u06d6-\\u06dc\\u06de-\\u06e4\\u06e7\\u06e8\\u06ea-\\u06ed\\u0711\\u0730-\\u074a\\u07a6-\\u07b0\\u07eb-\\u07f3\\u0816-\\u0819\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0900-\\u0902\\u093c\\u0941-\\u0948\\u094d\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09bc\\u09be\\u09c1-\\u09c4\\u09cd\\u09d7\\u09e2\\u09e3\\u0a01\\u0a02\\u0a3c\\u0a41\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a70\\u0a71\\u0a75\\u0a81\\u0a82\\u0abc\\u0ac1-\\u0ac5\\u0ac7\\u0ac8\\u0acd\\u0ae2\\u0ae3\\u0b01\\u0b3c\\u0b3e\\u0b3f\\u0b41-\\u0b44\\u0b4d\\u0b56\\u0b57\\u0b62\\u0b63\\u0b82\\u0bbe\\u0bc0\\u0bcd\\u0bd7\\u0c3e-\\u0c40\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62\\u0c63\\u0cbc\\u0cbf\\u0cc2\\u0cc6\\u0ccc\\u0ccd\\u0cd5\\u0cd6\\u0ce2\\u0ce3\\u0d3e\\u0d41-\\u0d44\\u0d4d\\u0d57\\u0d62\\u0d63\\u0dca\\u0dcf\\u0dd2-\\u0dd4\\u0dd6\\u0ddf\\u0e31\\u0e34-\\u0e3a\\u0e47-\\u0e4e\\u0eb1\\u0eb4-\\u0eb9\\u0ebb\\u0ebc\\u0ec8-\\u0ecd\\u0f18\\u0f19\\u0f35\\u0f37\\u0f39\\u0f71-\\u0f7e\\u0f80-\\u0f84\\u0f86\\u0f87\\u0f90-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u102d-\\u1030\\u1032-\\u1037\\u1039\\u103a\\u103d\\u103e\\u1058\\u1059\\u105e-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108d\\u109d\\u135f\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17b7-\\u17bd\\u17c6\\u17c9-\\u17d3\\u17dd\\u180b-\\u180d\\u18a9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193b\\u1a17\\u1a18\\u1a56\\u1a58-\\u1a5e\\u1a60\\u1a62\\u1a65-\\u1a6c\\u1a73-\\u1a7c\\u1a7f\\u1b00-\\u1b03\\u1b34\\u1b36-\\u1b3a\\u1b3c\\u1b42\\u1b6b-\\u1b73\\u1b80\\u1b81\\u1ba2-\\u1ba5\\u1ba8\\u1ba9\\u1c2c-\\u1c33\\u1c36\\u1c37\\u1cd0-\\u1cd2\\u1cd4-\\u1ce0\\u1ce2-\\u1ce8\\u1ced\\u1dc0-\\u1de6\\u1dfd-\\u1dff\\u200c\\u200d\\u20d0-\\u20f0\\u2cef-\\u2cf1\\u2de0-\\u2dff\\u302a-\\u302f\\u3099\\u309a\\ua66f-\\ua672\\ua67c\\ua67d\\ua6f0\\ua6f1\\ua802\\ua806\\ua80b\\ua825\\ua826\\ua8c4\\ua8e0-\\ua8f1\\ua926-\\ua92d\\ua947-\\ua951\\ua980-\\ua982\\ua9b3\\ua9b6-\\ua9b9\\ua9bc\\uaa29-\\uaa2e\\uaa31\\uaa32\\uaa35\\uaa36\\uaa43\\uaa4c\\uaab0\\uaab2-\\uaab4\\uaab7\\uaab8\\uaabe\\uaabf\\uaac1\\uabe5\\uabe8\\uabed\\udc00-\\udfff\\ufb1e\\ufe00-\\ufe0f\\ufe20-\\ufe26\\uff9e\\uff9f]/;\n  function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch) }\n\n  // Returns a number from the range [`0`; `str.length`] unless `pos` is outside that range.\n  function skipExtendingChars(str, pos, dir) {\n    while ((dir < 0 ? pos > 0 : pos < str.length) && isExtendingChar(str.charAt(pos))) { pos += dir; }\n    return pos\n  }\n\n  // Returns the value from the range [`from`; `to`] that satisfies\n  // `pred` and is closest to `from`. Assumes that at least `to`\n  // satisfies `pred`. Supports `from` being greater than `to`.\n  function findFirst(pred, from, to) {\n    // At any point we are certain `to` satisfies `pred`, don't know\n    // whether `from` does.\n    var dir = from > to ? -1 : 1;\n    for (;;) {\n      if (from == to) { return from }\n      var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF);\n      if (mid == from) { return pred(mid) ? from : to }\n      if (pred(mid)) { to = mid; }\n      else { from = mid + dir; }\n    }\n  }\n\n  // BIDI HELPERS\n\n  function iterateBidiSections(order, from, to, f) {\n    if (!order) { return f(from, to, \"ltr\", 0) }\n    var found = false;\n    for (var i = 0; i < order.length; ++i) {\n      var part = order[i];\n      if (part.from < to && part.to > from || from == to && part.to == from) {\n        f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? \"rtl\" : \"ltr\", i);\n        found = true;\n      }\n    }\n    if (!found) { f(from, to, \"ltr\"); }\n  }\n\n  var bidiOther = null;\n  function getBidiPartAt(order, ch, sticky) {\n    var found;\n    bidiOther = null;\n    for (var i = 0; i < order.length; ++i) {\n      var cur = order[i];\n      if (cur.from < ch && cur.to > ch) { return i }\n      if (cur.to == ch) {\n        if (cur.from != cur.to && sticky == \"before\") { found = i; }\n        else { bidiOther = i; }\n      }\n      if (cur.from == ch) {\n        if (cur.from != cur.to && sticky != \"before\") { found = i; }\n        else { bidiOther = i; }\n      }\n    }\n    return found != null ? found : bidiOther\n  }\n\n  // Bidirectional ordering algorithm\n  // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm\n  // that this (partially) implements.\n\n  // One-char codes used for character types:\n  // L (L):   Left-to-Right\n  // R (R):   Right-to-Left\n  // r (AL):  Right-to-Left Arabic\n  // 1 (EN):  European Number\n  // + (ES):  European Number Separator\n  // % (ET):  European Number Terminator\n  // n (AN):  Arabic Number\n  // , (CS):  Common Number Separator\n  // m (NSM): Non-Spacing Mark\n  // b (BN):  Boundary Neutral\n  // s (B):   Paragraph Separator\n  // t (S):   Segment Separator\n  // w (WS):  Whitespace\n  // N (ON):  Other Neutrals\n\n  // Returns null if characters are ordered as they appear\n  // (left-to-right), or an array of sections ({from, to, level}\n  // objects) in the order in which they occur visually.\n  var bidiOrdering = (function() {\n    // Character types for codepoints 0 to 0xff\n    var lowTypes = \"bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN\";\n    // Character types for codepoints 0x600 to 0x6f9\n    var arabicTypes = \"nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111\";\n    function charType(code) {\n      if (code <= 0xf7) { return lowTypes.charAt(code) }\n      else if (0x590 <= code && code <= 0x5f4) { return \"R\" }\n      else if (0x600 <= code && code <= 0x6f9) { return arabicTypes.charAt(code - 0x600) }\n      else if (0x6ee <= code && code <= 0x8ac) { return \"r\" }\n      else if (0x2000 <= code && code <= 0x200b) { return \"w\" }\n      else if (code == 0x200c) { return \"b\" }\n      else { return \"L\" }\n    }\n\n    var bidiRE = /[\\u0590-\\u05f4\\u0600-\\u06ff\\u0700-\\u08ac]/;\n    var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;\n\n    function BidiSpan(level, from, to) {\n      this.level = level;\n      this.from = from; this.to = to;\n    }\n\n    return function(str, direction) {\n      var outerType = direction == \"ltr\" ? \"L\" : \"R\";\n\n      if (str.length == 0 || direction == \"ltr\" && !bidiRE.test(str)) { return false }\n      var len = str.length, types = [];\n      for (var i = 0; i < len; ++i)\n        { types.push(charType(str.charCodeAt(i))); }\n\n      // W1. Examine each non-spacing mark (NSM) in the level run, and\n      // change the type of the NSM to the type of the previous\n      // character. If the NSM is at the start of the level run, it will\n      // get the type of sor.\n      for (var i$1 = 0, prev = outerType; i$1 < len; ++i$1) {\n        var type = types[i$1];\n        if (type == \"m\") { types[i$1] = prev; }\n        else { prev = type; }\n      }\n\n      // W2. Search backwards from each instance of a European number\n      // until the first strong type (R, L, AL, or sor) is found. If an\n      // AL is found, change the type of the European number to Arabic\n      // number.\n      // W3. Change all ALs to R.\n      for (var i$2 = 0, cur = outerType; i$2 < len; ++i$2) {\n        var type$1 = types[i$2];\n        if (type$1 == \"1\" && cur == \"r\") { types[i$2] = \"n\"; }\n        else if (isStrong.test(type$1)) { cur = type$1; if (type$1 == \"r\") { types[i$2] = \"R\"; } }\n      }\n\n      // W4. A single European separator between two European numbers\n      // changes to a European number. A single common separator between\n      // two numbers of the same type changes to that type.\n      for (var i$3 = 1, prev$1 = types[0]; i$3 < len - 1; ++i$3) {\n        var type$2 = types[i$3];\n        if (type$2 == \"+\" && prev$1 == \"1\" && types[i$3+1] == \"1\") { types[i$3] = \"1\"; }\n        else if (type$2 == \",\" && prev$1 == types[i$3+1] &&\n                 (prev$1 == \"1\" || prev$1 == \"n\")) { types[i$3] = prev$1; }\n        prev$1 = type$2;\n      }\n\n      // W5. A sequence of European terminators adjacent to European\n      // numbers changes to all European numbers.\n      // W6. Otherwise, separators and terminators change to Other\n      // Neutral.\n      for (var i$4 = 0; i$4 < len; ++i$4) {\n        var type$3 = types[i$4];\n        if (type$3 == \",\") { types[i$4] = \"N\"; }\n        else if (type$3 == \"%\") {\n          var end = (void 0);\n          for (end = i$4 + 1; end < len && types[end] == \"%\"; ++end) {}\n          var replace = (i$4 && types[i$4-1] == \"!\") || (end < len && types[end] == \"1\") ? \"1\" : \"N\";\n          for (var j = i$4; j < end; ++j) { types[j] = replace; }\n          i$4 = end - 1;\n        }\n      }\n\n      // W7. Search backwards from each instance of a European number\n      // until the first strong type (R, L, or sor) is found. If an L is\n      // found, then change the type of the European number to L.\n      for (var i$5 = 0, cur$1 = outerType; i$5 < len; ++i$5) {\n        var type$4 = types[i$5];\n        if (cur$1 == \"L\" && type$4 == \"1\") { types[i$5] = \"L\"; }\n        else if (isStrong.test(type$4)) { cur$1 = type$4; }\n      }\n\n      // N1. A sequence of neutrals takes the direction of the\n      // surrounding strong text if the text on both sides has the same\n      // direction. European and Arabic numbers act as if they were R in\n      // terms of their influence on neutrals. Start-of-level-run (sor)\n      // and end-of-level-run (eor) are used at level run boundaries.\n      // N2. Any remaining neutrals take the embedding direction.\n      for (var i$6 = 0; i$6 < len; ++i$6) {\n        if (isNeutral.test(types[i$6])) {\n          var end$1 = (void 0);\n          for (end$1 = i$6 + 1; end$1 < len && isNeutral.test(types[end$1]); ++end$1) {}\n          var before = (i$6 ? types[i$6-1] : outerType) == \"L\";\n          var after = (end$1 < len ? types[end$1] : outerType) == \"L\";\n          var replace$1 = before == after ? (before ? \"L\" : \"R\") : outerType;\n          for (var j$1 = i$6; j$1 < end$1; ++j$1) { types[j$1] = replace$1; }\n          i$6 = end$1 - 1;\n        }\n      }\n\n      // Here we depart from the documented algorithm, in order to avoid\n      // building up an actual levels array. Since there are only three\n      // levels (0, 1, 2) in an implementation that doesn't take\n      // explicit embedding into account, we can build up the order on\n      // the fly, without following the level-based algorithm.\n      var order = [], m;\n      for (var i$7 = 0; i$7 < len;) {\n        if (countsAsLeft.test(types[i$7])) {\n          var start = i$7;\n          for (++i$7; i$7 < len && countsAsLeft.test(types[i$7]); ++i$7) {}\n          order.push(new BidiSpan(0, start, i$7));\n        } else {\n          var pos = i$7, at = order.length, isRTL = direction == \"rtl\" ? 1 : 0;\n          for (++i$7; i$7 < len && types[i$7] != \"L\"; ++i$7) {}\n          for (var j$2 = pos; j$2 < i$7;) {\n            if (countsAsNum.test(types[j$2])) {\n              if (pos < j$2) { order.splice(at, 0, new BidiSpan(1, pos, j$2)); at += isRTL; }\n              var nstart = j$2;\n              for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}\n              order.splice(at, 0, new BidiSpan(2, nstart, j$2));\n              at += isRTL;\n              pos = j$2;\n            } else { ++j$2; }\n          }\n          if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); }\n        }\n      }\n      if (direction == \"ltr\") {\n        if (order[0].level == 1 && (m = str.match(/^\\s+/))) {\n          order[0].from = m[0].length;\n          order.unshift(new BidiSpan(0, 0, m[0].length));\n        }\n        if (lst(order).level == 1 && (m = str.match(/\\s+$/))) {\n          lst(order).to -= m[0].length;\n          order.push(new BidiSpan(0, len - m[0].length, len));\n        }\n      }\n\n      return direction == \"rtl\" ? order.reverse() : order\n    }\n  })();\n\n  // Get the bidi ordering for the given line (and cache it). Returns\n  // false for lines that are fully left-to-right, and an array of\n  // BidiSpan objects otherwise.\n  function getOrder(line, direction) {\n    var order = line.order;\n    if (order == null) { order = line.order = bidiOrdering(line.text, direction); }\n    return order\n  }\n\n  // EVENT HANDLING\n\n  // Lightweight event framework. on/off also work on DOM nodes,\n  // registering native DOM handlers.\n\n  var noHandlers = [];\n\n  var on = function(emitter, type, f) {\n    if (emitter.addEventListener) {\n      emitter.addEventListener(type, f, false);\n    } else if (emitter.attachEvent) {\n      emitter.attachEvent(\"on\" + type, f);\n    } else {\n      var map = emitter._handlers || (emitter._handlers = {});\n      map[type] = (map[type] || noHandlers).concat(f);\n    }\n  };\n\n  function getHandlers(emitter, type) {\n    return emitter._handlers && emitter._handlers[type] || noHandlers\n  }\n\n  function off(emitter, type, f) {\n    if (emitter.removeEventListener) {\n      emitter.removeEventListener(type, f, false);\n    } else if (emitter.detachEvent) {\n      emitter.detachEvent(\"on\" + type, f);\n    } else {\n      var map = emitter._handlers, arr = map && map[type];\n      if (arr) {\n        var index = indexOf(arr, f);\n        if (index > -1)\n          { map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); }\n      }\n    }\n  }\n\n  function signal(emitter, type /*, values...*/) {\n    var handlers = getHandlers(emitter, type);\n    if (!handlers.length) { return }\n    var args = Array.prototype.slice.call(arguments, 2);\n    for (var i = 0; i < handlers.length; ++i) { handlers[i].apply(null, args); }\n  }\n\n  // The DOM events that CodeMirror handles can be overridden by\n  // registering a (non-DOM) handler on the editor for the event name,\n  // and preventDefault-ing the event in that handler.\n  function signalDOMEvent(cm, e, override) {\n    if (typeof e == \"string\")\n      { e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; }\n    signal(cm, override || e.type, cm, e);\n    return e_defaultPrevented(e) || e.codemirrorIgnore\n  }\n\n  function signalCursorActivity(cm) {\n    var arr = cm._handlers && cm._handlers.cursorActivity;\n    if (!arr) { return }\n    var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []);\n    for (var i = 0; i < arr.length; ++i) { if (indexOf(set, arr[i]) == -1)\n      { set.push(arr[i]); } }\n  }\n\n  function hasHandler(emitter, type) {\n    return getHandlers(emitter, type).length > 0\n  }\n\n  // Add on and off methods to a constructor's prototype, to make\n  // registering events on such objects more convenient.\n  function eventMixin(ctor) {\n    ctor.prototype.on = function(type, f) {on(this, type, f);};\n    ctor.prototype.off = function(type, f) {off(this, type, f);};\n  }\n\n  // Due to the fact that we still support jurassic IE versions, some\n  // compatibility wrappers are needed.\n\n  function e_preventDefault(e) {\n    if (e.preventDefault) { e.preventDefault(); }\n    else { e.returnValue = false; }\n  }\n  function e_stopPropagation(e) {\n    if (e.stopPropagation) { e.stopPropagation(); }\n    else { e.cancelBubble = true; }\n  }\n  function e_defaultPrevented(e) {\n    return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false\n  }\n  function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}\n\n  function e_target(e) {return e.target || e.srcElement}\n  function e_button(e) {\n    var b = e.which;\n    if (b == null) {\n      if (e.button & 1) { b = 1; }\n      else if (e.button & 2) { b = 3; }\n      else if (e.button & 4) { b = 2; }\n    }\n    if (mac && e.ctrlKey && b == 1) { b = 3; }\n    return b\n  }\n\n  // Detect drag-and-drop\n  var dragAndDrop = function() {\n    // There is *some* kind of drag-and-drop support in IE6-8, but I\n    // couldn't get it to work yet.\n    if (ie && ie_version < 9) { return false }\n    var div = elt('div');\n    return \"draggable\" in div || \"dragDrop\" in div\n  }();\n\n  var zwspSupported;\n  function zeroWidthElement(measure) {\n    if (zwspSupported == null) {\n      var test = elt(\"span\", \"\\u200b\");\n      removeChildrenAndAdd(measure, elt(\"span\", [test, document.createTextNode(\"x\")]));\n      if (measure.firstChild.offsetHeight != 0)\n        { zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); }\n    }\n    var node = zwspSupported ? elt(\"span\", \"\\u200b\") :\n      elt(\"span\", \"\\u00a0\", null, \"display: inline-block; width: 1px; margin-right: -1px\");\n    node.setAttribute(\"cm-text\", \"\");\n    return node\n  }\n\n  // Feature-detect IE's crummy client rect reporting for bidi text\n  var badBidiRects;\n  function hasBadBidiRects(measure) {\n    if (badBidiRects != null) { return badBidiRects }\n    var txt = removeChildrenAndAdd(measure, document.createTextNode(\"A\\u062eA\"));\n    var r0 = range(txt, 0, 1).getBoundingClientRect();\n    var r1 = range(txt, 1, 2).getBoundingClientRect();\n    removeChildren(measure);\n    if (!r0 || r0.left == r0.right) { return false } // Safari returns null in some cases (#2780)\n    return badBidiRects = (r1.right - r0.right < 3)\n  }\n\n  // See if \"\".split is the broken IE version, if so, provide an\n  // alternative way to split lines.\n  var splitLinesAuto = \"\\n\\nb\".split(/\\n/).length != 3 ? function (string) {\n    var pos = 0, result = [], l = string.length;\n    while (pos <= l) {\n      var nl = string.indexOf(\"\\n\", pos);\n      if (nl == -1) { nl = string.length; }\n      var line = string.slice(pos, string.charAt(nl - 1) == \"\\r\" ? nl - 1 : nl);\n      var rt = line.indexOf(\"\\r\");\n      if (rt != -1) {\n        result.push(line.slice(0, rt));\n        pos += rt + 1;\n      } else {\n        result.push(line);\n        pos = nl + 1;\n      }\n    }\n    return result\n  } : function (string) { return string.split(/\\r\\n?|\\n/); };\n\n  var hasSelection = window.getSelection ? function (te) {\n    try { return te.selectionStart != te.selectionEnd }\n    catch(e) { return false }\n  } : function (te) {\n    var range;\n    try {range = te.ownerDocument.selection.createRange();}\n    catch(e) {}\n    if (!range || range.parentElement() != te) { return false }\n    return range.compareEndPoints(\"StartToEnd\", range) != 0\n  };\n\n  var hasCopyEvent = (function () {\n    var e = elt(\"div\");\n    if (\"oncopy\" in e) { return true }\n    e.setAttribute(\"oncopy\", \"return;\");\n    return typeof e.oncopy == \"function\"\n  })();\n\n  var badZoomedRects = null;\n  function hasBadZoomedRects(measure) {\n    if (badZoomedRects != null) { return badZoomedRects }\n    var node = removeChildrenAndAdd(measure, elt(\"span\", \"x\"));\n    var normal = node.getBoundingClientRect();\n    var fromRange = range(node, 0, 1).getBoundingClientRect();\n    return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1\n  }\n\n  // Known modes, by name and by MIME\n  var modes = {}, mimeModes = {};\n\n  // Extra arguments are stored as the mode's dependencies, which is\n  // used by (legacy) mechanisms like loadmode.js to automatically\n  // load a mode. (Preferred mechanism is the require/define calls.)\n  function defineMode(name, mode) {\n    if (arguments.length > 2)\n      { mode.dependencies = Array.prototype.slice.call(arguments, 2); }\n    modes[name] = mode;\n  }\n\n  function defineMIME(mime, spec) {\n    mimeModes[mime] = spec;\n  }\n\n  // Given a MIME type, a {name, ...options} config object, or a name\n  // string, return a mode config object.\n  function resolveMode(spec) {\n    if (typeof spec == \"string\" && mimeModes.hasOwnProperty(spec)) {\n      spec = mimeModes[spec];\n    } else if (spec && typeof spec.name == \"string\" && mimeModes.hasOwnProperty(spec.name)) {\n      var found = mimeModes[spec.name];\n      if (typeof found == \"string\") { found = {name: found}; }\n      spec = createObj(found, spec);\n      spec.name = found.name;\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+xml$/.test(spec)) {\n      return resolveMode(\"application/xml\")\n    } else if (typeof spec == \"string\" && /^[\\w\\-]+\\/[\\w\\-]+\\+json$/.test(spec)) {\n      return resolveMode(\"application/json\")\n    }\n    if (typeof spec == \"string\") { return {name: spec} }\n    else { return spec || {name: \"null\"} }\n  }\n\n  // Given a mode spec (anything that resolveMode accepts), find and\n  // initialize an actual mode object.\n  function getMode(options, spec) {\n    spec = resolveMode(spec);\n    var mfactory = modes[spec.name];\n    if (!mfactory) { return getMode(options, \"text/plain\") }\n    var modeObj = mfactory(options, spec);\n    if (modeExtensions.hasOwnProperty(spec.name)) {\n      var exts = modeExtensions[spec.name];\n      for (var prop in exts) {\n        if (!exts.hasOwnProperty(prop)) { continue }\n        if (modeObj.hasOwnProperty(prop)) { modeObj[\"_\" + prop] = modeObj[prop]; }\n        modeObj[prop] = exts[prop];\n      }\n    }\n    modeObj.name = spec.name;\n    if (spec.helperType) { modeObj.helperType = spec.helperType; }\n    if (spec.modeProps) { for (var prop$1 in spec.modeProps)\n      { modeObj[prop$1] = spec.modeProps[prop$1]; } }\n\n    return modeObj\n  }\n\n  // This can be used to attach properties to mode objects from\n  // outside the actual mode definition.\n  var modeExtensions = {};\n  function extendMode(mode, properties) {\n    var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});\n    copyObj(properties, exts);\n  }\n\n  function copyState(mode, state) {\n    if (state === true) { return state }\n    if (mode.copyState) { return mode.copyState(state) }\n    var nstate = {};\n    for (var n in state) {\n      var val = state[n];\n      if (val instanceof Array) { val = val.concat([]); }\n      nstate[n] = val;\n    }\n    return nstate\n  }\n\n  // Given a mode and a state (for that mode), find the inner mode and\n  // state at the position that the state refers to.\n  function innerMode(mode, state) {\n    var info;\n    while (mode.innerMode) {\n      info = mode.innerMode(state);\n      if (!info || info.mode == mode) { break }\n      state = info.state;\n      mode = info.mode;\n    }\n    return info || {mode: mode, state: state}\n  }\n\n  function startState(mode, a1, a2) {\n    return mode.startState ? mode.startState(a1, a2) : true\n  }\n\n  // STRING STREAM\n\n  // Fed to the mode parsers, provides helper functions to make\n  // parsers more succinct.\n\n  var StringStream = function(string, tabSize, lineOracle) {\n    this.pos = this.start = 0;\n    this.string = string;\n    this.tabSize = tabSize || 8;\n    this.lastColumnPos = this.lastColumnValue = 0;\n    this.lineStart = 0;\n    this.lineOracle = lineOracle;\n  };\n\n  StringStream.prototype.eol = function () {return this.pos >= this.string.length};\n  StringStream.prototype.sol = function () {return this.pos == this.lineStart};\n  StringStream.prototype.peek = function () {return this.string.charAt(this.pos) || undefined};\n  StringStream.prototype.next = function () {\n    if (this.pos < this.string.length)\n      { return this.string.charAt(this.pos++) }\n  };\n  StringStream.prototype.eat = function (match) {\n    var ch = this.string.charAt(this.pos);\n    var ok;\n    if (typeof match == \"string\") { ok = ch == match; }\n    else { ok = ch && (match.test ? match.test(ch) : match(ch)); }\n    if (ok) {++this.pos; return ch}\n  };\n  StringStream.prototype.eatWhile = function (match) {\n    var start = this.pos;\n    while (this.eat(match)){}\n    return this.pos > start\n  };\n  StringStream.prototype.eatSpace = function () {\n    var start = this.pos;\n    while (/[\\s\\u00a0]/.test(this.string.charAt(this.pos))) { ++this.pos; }\n    return this.pos > start\n  };\n  StringStream.prototype.skipToEnd = function () {this.pos = this.string.length;};\n  StringStream.prototype.skipTo = function (ch) {\n    var found = this.string.indexOf(ch, this.pos);\n    if (found > -1) {this.pos = found; return true}\n  };\n  StringStream.prototype.backUp = function (n) {this.pos -= n;};\n  StringStream.prototype.column = function () {\n    if (this.lastColumnPos < this.start) {\n      this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue);\n      this.lastColumnPos = this.start;\n    }\n    return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)\n  };\n  StringStream.prototype.indentation = function () {\n    return countColumn(this.string, null, this.tabSize) -\n      (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0)\n  };\n  StringStream.prototype.match = function (pattern, consume, caseInsensitive) {\n    if (typeof pattern == \"string\") {\n      var cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; };\n      var substr = this.string.substr(this.pos, pattern.length);\n      if (cased(substr) == cased(pattern)) {\n        if (consume !== false) { this.pos += pattern.length; }\n        return true\n      }\n    } else {\n      var match = this.string.slice(this.pos).match(pattern);\n      if (match && match.index > 0) { return null }\n      if (match && consume !== false) { this.pos += match[0].length; }\n      return match\n    }\n  };\n  StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};\n  StringStream.prototype.hideFirstChars = function (n, inner) {\n    this.lineStart += n;\n    try { return inner() }\n    finally { this.lineStart -= n; }\n  };\n  StringStream.prototype.lookAhead = function (n) {\n    var oracle = this.lineOracle;\n    return oracle && oracle.lookAhead(n)\n  };\n  StringStream.prototype.baseToken = function () {\n    var oracle = this.lineOracle;\n    return oracle && oracle.baseToken(this.pos)\n  };\n\n  // Find the line object corresponding to the given line number.\n  function getLine(doc, n) {\n    n -= doc.first;\n    if (n < 0 || n >= doc.size) { throw new Error(\"There is no line \" + (n + doc.first) + \" in the document.\") }\n    var chunk = doc;\n    while (!chunk.lines) {\n      for (var i = 0;; ++i) {\n        var child = chunk.children[i], sz = child.chunkSize();\n        if (n < sz) { chunk = child; break }\n        n -= sz;\n      }\n    }\n    return chunk.lines[n]\n  }\n\n  // Get the part of a document between two positions, as an array of\n  // strings.\n  function getBetween(doc, start, end) {\n    var out = [], n = start.line;\n    doc.iter(start.line, end.line + 1, function (line) {\n      var text = line.text;\n      if (n == end.line) { text = text.slice(0, end.ch); }\n      if (n == start.line) { text = text.slice(start.ch); }\n      out.push(text);\n      ++n;\n    });\n    return out\n  }\n  // Get the lines between from and to, as array of strings.\n  function getLines(doc, from, to) {\n    var out = [];\n    doc.iter(from, to, function (line) { out.push(line.text); }); // iter aborts when callback returns truthy value\n    return out\n  }\n\n  // Update the height of a line, propagating the height change\n  // upwards to parent nodes.\n  function updateLineHeight(line, height) {\n    var diff = height - line.height;\n    if (diff) { for (var n = line; n; n = n.parent) { n.height += diff; } }\n  }\n\n  // Given a line object, find its line number by walking up through\n  // its parent links.\n  function lineNo(line) {\n    if (line.parent == null) { return null }\n    var cur = line.parent, no = indexOf(cur.lines, line);\n    for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {\n      for (var i = 0;; ++i) {\n        if (chunk.children[i] == cur) { break }\n        no += chunk.children[i].chunkSize();\n      }\n    }\n    return no + cur.first\n  }\n\n  // Find the line at the given vertical position, using the height\n  // information in the document tree.\n  function lineAtHeight(chunk, h) {\n    var n = chunk.first;\n    outer: do {\n      for (var i$1 = 0; i$1 < chunk.children.length; ++i$1) {\n        var child = chunk.children[i$1], ch = child.height;\n        if (h < ch) { chunk = child; continue outer }\n        h -= ch;\n        n += child.chunkSize();\n      }\n      return n\n    } while (!chunk.lines)\n    var i = 0;\n    for (; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i], lh = line.height;\n      if (h < lh) { break }\n      h -= lh;\n    }\n    return n + i\n  }\n\n  function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}\n\n  function lineNumberFor(options, i) {\n    return String(options.lineNumberFormatter(i + options.firstLineNumber))\n  }\n\n  // A Pos instance represents a position within the text.\n  function Pos(line, ch, sticky) {\n    if ( sticky === void 0 ) sticky = null;\n\n    if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }\n    this.line = line;\n    this.ch = ch;\n    this.sticky = sticky;\n  }\n\n  // Compare two positions, return 0 if they are the same, a negative\n  // number when a is less, and a positive number otherwise.\n  function cmp(a, b) { return a.line - b.line || a.ch - b.ch }\n\n  function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }\n\n  function copyPos(x) {return Pos(x.line, x.ch)}\n  function maxPos(a, b) { return cmp(a, b) < 0 ? b : a }\n  function minPos(a, b) { return cmp(a, b) < 0 ? a : b }\n\n  // Most of the external API clips given positions to make sure they\n  // actually exist within the document.\n  function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))}\n  function clipPos(doc, pos) {\n    if (pos.line < doc.first) { return Pos(doc.first, 0) }\n    var last = doc.first + doc.size - 1;\n    if (pos.line > last) { return Pos(last, getLine(doc, last).text.length) }\n    return clipToLen(pos, getLine(doc, pos.line).text.length)\n  }\n  function clipToLen(pos, linelen) {\n    var ch = pos.ch;\n    if (ch == null || ch > linelen) { return Pos(pos.line, linelen) }\n    else if (ch < 0) { return Pos(pos.line, 0) }\n    else { return pos }\n  }\n  function clipPosArray(doc, array) {\n    var out = [];\n    for (var i = 0; i < array.length; i++) { out[i] = clipPos(doc, array[i]); }\n    return out\n  }\n\n  var SavedContext = function(state, lookAhead) {\n    this.state = state;\n    this.lookAhead = lookAhead;\n  };\n\n  var Context = function(doc, state, line, lookAhead) {\n    this.state = state;\n    this.doc = doc;\n    this.line = line;\n    this.maxLookAhead = lookAhead || 0;\n    this.baseTokens = null;\n    this.baseTokenPos = 1;\n  };\n\n  Context.prototype.lookAhead = function (n) {\n    var line = this.doc.getLine(this.line + n);\n    if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; }\n    return line\n  };\n\n  Context.prototype.baseToken = function (n) {\n    if (!this.baseTokens) { return null }\n    while (this.baseTokens[this.baseTokenPos] <= n)\n      { this.baseTokenPos += 2; }\n    var type = this.baseTokens[this.baseTokenPos + 1];\n    return {type: type && type.replace(/( |^)overlay .*/, \"\"),\n            size: this.baseTokens[this.baseTokenPos] - n}\n  };\n\n  Context.prototype.nextLine = function () {\n    this.line++;\n    if (this.maxLookAhead > 0) { this.maxLookAhead--; }\n  };\n\n  Context.fromSaved = function (doc, saved, line) {\n    if (saved instanceof SavedContext)\n      { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }\n    else\n      { return new Context(doc, copyState(doc.mode, saved), line) }\n  };\n\n  Context.prototype.save = function (copy) {\n    var state = copy !== false ? copyState(this.doc.mode, this.state) : this.state;\n    return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state\n  };\n\n\n  // Compute a style array (an array starting with a mode generation\n  // -- for invalidation -- followed by pairs of end positions and\n  // style strings), which is used to highlight the tokens on the\n  // line.\n  function highlightLine(cm, line, context, forceToEnd) {\n    // A styles array always starts with a number identifying the\n    // mode/overlays that it is based on (for easy invalidation).\n    var st = [cm.state.modeGen], lineClasses = {};\n    // Compute the base array of styles\n    runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },\n            lineClasses, forceToEnd);\n    var state = context.state;\n\n    // Run overlays, adjust style array.\n    var loop = function ( o ) {\n      context.baseTokens = st;\n      var overlay = cm.state.overlays[o], i = 1, at = 0;\n      context.state = true;\n      runMode(cm, line.text, overlay.mode, context, function (end, style) {\n        var start = i;\n        // Ensure there's a token end at the current position, and that i points at it\n        while (at < end) {\n          var i_end = st[i];\n          if (i_end > end)\n            { st.splice(i, 1, end, st[i+1], i_end); }\n          i += 2;\n          at = Math.min(end, i_end);\n        }\n        if (!style) { return }\n        if (overlay.opaque) {\n          st.splice(start, i - start, end, \"overlay \" + style);\n          i = start + 2;\n        } else {\n          for (; start < i; start += 2) {\n            var cur = st[start+1];\n            st[start+1] = (cur ? cur + \" \" : \"\") + \"overlay \" + style;\n          }\n        }\n      }, lineClasses);\n      context.state = state;\n      context.baseTokens = null;\n      context.baseTokenPos = 1;\n    };\n\n    for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );\n\n    return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}\n  }\n\n  function getLineStyles(cm, line, updateFrontier) {\n    if (!line.styles || line.styles[0] != cm.state.modeGen) {\n      var context = getContextBefore(cm, lineNo(line));\n      var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state);\n      var result = highlightLine(cm, line, context);\n      if (resetState) { context.state = resetState; }\n      line.stateAfter = context.save(!resetState);\n      line.styles = result.styles;\n      if (result.classes) { line.styleClasses = result.classes; }\n      else if (line.styleClasses) { line.styleClasses = null; }\n      if (updateFrontier === cm.doc.highlightFrontier)\n        { cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier); }\n    }\n    return line.styles\n  }\n\n  function getContextBefore(cm, n, precise) {\n    var doc = cm.doc, display = cm.display;\n    if (!doc.mode.startState) { return new Context(doc, true, n) }\n    var start = findStartLine(cm, n, precise);\n    var saved = start > doc.first && getLine(doc, start - 1).stateAfter;\n    var context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start);\n\n    doc.iter(start, n, function (line) {\n      processLine(cm, line.text, context);\n      var pos = context.line;\n      line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null;\n      context.nextLine();\n    });\n    if (precise) { doc.modeFrontier = context.line; }\n    return context\n  }\n\n  // Lightweight form of highlight -- proceed over this line and\n  // update state, but don't save a style array. Used for lines that\n  // aren't currently visible.\n  function processLine(cm, text, context, startAt) {\n    var mode = cm.doc.mode;\n    var stream = new StringStream(text, cm.options.tabSize, context);\n    stream.start = stream.pos = startAt || 0;\n    if (text == \"\") { callBlankLine(mode, context.state); }\n    while (!stream.eol()) {\n      readToken(mode, stream, context.state);\n      stream.start = stream.pos;\n    }\n  }\n\n  function callBlankLine(mode, state) {\n    if (mode.blankLine) { return mode.blankLine(state) }\n    if (!mode.innerMode) { return }\n    var inner = innerMode(mode, state);\n    if (inner.mode.blankLine) { return inner.mode.blankLine(inner.state) }\n  }\n\n  function readToken(mode, stream, state, inner) {\n    for (var i = 0; i < 10; i++) {\n      if (inner) { inner[0] = innerMode(mode, state).mode; }\n      var style = mode.token(stream, state);\n      if (stream.pos > stream.start) { return style }\n    }\n    throw new Error(\"Mode \" + mode.name + \" failed to advance stream.\")\n  }\n\n  var Token = function(stream, type, state) {\n    this.start = stream.start; this.end = stream.pos;\n    this.string = stream.current();\n    this.type = type || null;\n    this.state = state;\n  };\n\n  // Utility for getTokenAt and getLineTokens\n  function takeToken(cm, pos, precise, asArray) {\n    var doc = cm.doc, mode = doc.mode, style;\n    pos = clipPos(doc, pos);\n    var line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise);\n    var stream = new StringStream(line.text, cm.options.tabSize, context), tokens;\n    if (asArray) { tokens = []; }\n    while ((asArray || stream.pos < pos.ch) && !stream.eol()) {\n      stream.start = stream.pos;\n      style = readToken(mode, stream, context.state);\n      if (asArray) { tokens.push(new Token(stream, style, copyState(doc.mode, context.state))); }\n    }\n    return asArray ? tokens : new Token(stream, style, context.state)\n  }\n\n  function extractLineClasses(type, output) {\n    if (type) { for (;;) {\n      var lineClass = type.match(/(?:^|\\s+)line-(background-)?(\\S+)/);\n      if (!lineClass) { break }\n      type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length);\n      var prop = lineClass[1] ? \"bgClass\" : \"textClass\";\n      if (output[prop] == null)\n        { output[prop] = lineClass[2]; }\n      else if (!(new RegExp(\"(?:^|\\\\s)\" + lineClass[2] + \"(?:$|\\\\s)\")).test(output[prop]))\n        { output[prop] += \" \" + lineClass[2]; }\n    } }\n    return type\n  }\n\n  // Run the given mode's parser over a line, calling f for each token.\n  function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) {\n    var flattenSpans = mode.flattenSpans;\n    if (flattenSpans == null) { flattenSpans = cm.options.flattenSpans; }\n    var curStart = 0, curStyle = null;\n    var stream = new StringStream(text, cm.options.tabSize, context), style;\n    var inner = cm.options.addModeClass && [null];\n    if (text == \"\") { extractLineClasses(callBlankLine(mode, context.state), lineClasses); }\n    while (!stream.eol()) {\n      if (stream.pos > cm.options.maxHighlightLength) {\n        flattenSpans = false;\n        if (forceToEnd) { processLine(cm, text, context, stream.pos); }\n        stream.pos = text.length;\n        style = null;\n      } else {\n        style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses);\n      }\n      if (inner) {\n        var mName = inner[0].name;\n        if (mName) { style = \"m-\" + (style ? mName + \" \" + style : mName); }\n      }\n      if (!flattenSpans || curStyle != style) {\n        while (curStart < stream.start) {\n          curStart = Math.min(stream.start, curStart + 5000);\n          f(curStart, curStyle);\n        }\n        curStyle = style;\n      }\n      stream.start = stream.pos;\n    }\n    while (curStart < stream.pos) {\n      // Webkit seems to refuse to render text nodes longer than 57444\n      // characters, and returns inaccurate measurements in nodes\n      // starting around 5000 chars.\n      var pos = Math.min(stream.pos, curStart + 5000);\n      f(pos, curStyle);\n      curStart = pos;\n    }\n  }\n\n  // Finds the line to start with when starting a parse. Tries to\n  // find a line with a stateAfter, so that it can start with a\n  // valid state. If that fails, it returns the line with the\n  // smallest indentation, which tends to need the least context to\n  // parse correctly.\n  function findStartLine(cm, n, precise) {\n    var minindent, minline, doc = cm.doc;\n    var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100);\n    for (var search = n; search > lim; --search) {\n      if (search <= doc.first) { return doc.first }\n      var line = getLine(doc, search - 1), after = line.stateAfter;\n      if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier))\n        { return search }\n      var indented = countColumn(line.text, null, cm.options.tabSize);\n      if (minline == null || minindent > indented) {\n        minline = search - 1;\n        minindent = indented;\n      }\n    }\n    return minline\n  }\n\n  function retreatFrontier(doc, n) {\n    doc.modeFrontier = Math.min(doc.modeFrontier, n);\n    if (doc.highlightFrontier < n - 10) { return }\n    var start = doc.first;\n    for (var line = n - 1; line > start; line--) {\n      var saved = getLine(doc, line).stateAfter;\n      // change is on 3\n      // state on line 1 looked ahead 2 -- so saw 3\n      // test 1 + 2 < 3 should cover this\n      if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) {\n        start = line + 1;\n        break\n      }\n    }\n    doc.highlightFrontier = Math.min(doc.highlightFrontier, start);\n  }\n\n  // Optimize some code when these features are not used.\n  var sawReadOnlySpans = false, sawCollapsedSpans = false;\n\n  function seeReadOnlySpans() {\n    sawReadOnlySpans = true;\n  }\n\n  function seeCollapsedSpans() {\n    sawCollapsedSpans = true;\n  }\n\n  // TEXTMARKER SPANS\n\n  function MarkedSpan(marker, from, to) {\n    this.marker = marker;\n    this.from = from; this.to = to;\n  }\n\n  // Search an array of spans for a span matching the given marker.\n  function getMarkedSpanFor(spans, marker) {\n    if (spans) { for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.marker == marker) { return span }\n    } }\n  }\n  // Remove a span from an array, returning undefined if no spans are\n  // left (we don't store arrays for lines without spans).\n  function removeMarkedSpan(spans, span) {\n    var r;\n    for (var i = 0; i < spans.length; ++i)\n      { if (spans[i] != span) { (r || (r = [])).push(spans[i]); } }\n    return r\n  }\n  // Add a span to a line.\n  function addMarkedSpan(line, span) {\n    line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];\n    span.marker.attachLine(line);\n  }\n\n  // Used for the algorithm that adjusts markers for a change in the\n  // document. These functions cut an array of spans at a given\n  // character position, returning an array of remaining chunks (or\n  // undefined if nothing remains).\n  function markedSpansBefore(old, startCh, isInsert) {\n    var nw;\n    if (old) { for (var i = 0; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);\n      if (startsBefore || span.from == startCh && marker.type == \"bookmark\" && (!isInsert || !span.marker.insertLeft)) {\n        var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh)\n        ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to));\n      }\n    } }\n    return nw\n  }\n  function markedSpansAfter(old, endCh, isInsert) {\n    var nw;\n    if (old) { for (var i = 0; i < old.length; ++i) {\n      var span = old[i], marker = span.marker;\n      var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);\n      if (endsAfter || span.from == endCh && marker.type == \"bookmark\" && (!isInsert || span.marker.insertLeft)) {\n        var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh)\n        ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh,\n                                              span.to == null ? null : span.to - endCh));\n      }\n    } }\n    return nw\n  }\n\n  // Given a change object, compute the new set of marker spans that\n  // cover the line in which the change took place. Removes spans\n  // entirely within the change, reconnects spans belonging to the\n  // same marker that appear on both sides of the change, and cuts off\n  // spans partially within the change. Returns an array of span\n  // arrays with one element for each line in (after) the change.\n  function stretchSpansOverChange(doc, change) {\n    if (change.full) { return null }\n    var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;\n    var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;\n    if (!oldFirst && !oldLast) { return null }\n\n    var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0;\n    // Get the spans that 'stick out' on both sides\n    var first = markedSpansBefore(oldFirst, startCh, isInsert);\n    var last = markedSpansAfter(oldLast, endCh, isInsert);\n\n    // Next, merge those two ends\n    var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);\n    if (first) {\n      // Fix up .to properties of first\n      for (var i = 0; i < first.length; ++i) {\n        var span = first[i];\n        if (span.to == null) {\n          var found = getMarkedSpanFor(last, span.marker);\n          if (!found) { span.to = startCh; }\n          else if (sameLine) { span.to = found.to == null ? null : found.to + offset; }\n        }\n      }\n    }\n    if (last) {\n      // Fix up .from in last (or move them into first in case of sameLine)\n      for (var i$1 = 0; i$1 < last.length; ++i$1) {\n        var span$1 = last[i$1];\n        if (span$1.to != null) { span$1.to += offset; }\n        if (span$1.from == null) {\n          var found$1 = getMarkedSpanFor(first, span$1.marker);\n          if (!found$1) {\n            span$1.from = offset;\n            if (sameLine) { (first || (first = [])).push(span$1); }\n          }\n        } else {\n          span$1.from += offset;\n          if (sameLine) { (first || (first = [])).push(span$1); }\n        }\n      }\n    }\n    // Make sure we didn't create any zero-length spans\n    if (first) { first = clearEmptySpans(first); }\n    if (last && last != first) { last = clearEmptySpans(last); }\n\n    var newMarkers = [first];\n    if (!sameLine) {\n      // Fill gap with whole-line-spans\n      var gap = change.text.length - 2, gapMarkers;\n      if (gap > 0 && first)\n        { for (var i$2 = 0; i$2 < first.length; ++i$2)\n          { if (first[i$2].to == null)\n            { (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i$2].marker, null, null)); } } }\n      for (var i$3 = 0; i$3 < gap; ++i$3)\n        { newMarkers.push(gapMarkers); }\n      newMarkers.push(last);\n    }\n    return newMarkers\n  }\n\n  // Remove spans that are empty and don't have a clearWhenEmpty\n  // option of false.\n  function clearEmptySpans(spans) {\n    for (var i = 0; i < spans.length; ++i) {\n      var span = spans[i];\n      if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false)\n        { spans.splice(i--, 1); }\n    }\n    if (!spans.length) { return null }\n    return spans\n  }\n\n  // Used to 'clip' out readOnly ranges when making a change.\n  function removeReadOnlyRanges(doc, from, to) {\n    var markers = null;\n    doc.iter(from.line, to.line + 1, function (line) {\n      if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {\n        var mark = line.markedSpans[i].marker;\n        if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))\n          { (markers || (markers = [])).push(mark); }\n      } }\n    });\n    if (!markers) { return null }\n    var parts = [{from: from, to: to}];\n    for (var i = 0; i < markers.length; ++i) {\n      var mk = markers[i], m = mk.find(0);\n      for (var j = 0; j < parts.length; ++j) {\n        var p = parts[j];\n        if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) { continue }\n        var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to);\n        if (dfrom < 0 || !mk.inclusiveLeft && !dfrom)\n          { newParts.push({from: p.from, to: m.from}); }\n        if (dto > 0 || !mk.inclusiveRight && !dto)\n          { newParts.push({from: m.to, to: p.to}); }\n        parts.splice.apply(parts, newParts);\n        j += newParts.length - 3;\n      }\n    }\n    return parts\n  }\n\n  // Connect or disconnect spans from a line.\n  function detachMarkedSpans(line) {\n    var spans = line.markedSpans;\n    if (!spans) { return }\n    for (var i = 0; i < spans.length; ++i)\n      { spans[i].marker.detachLine(line); }\n    line.markedSpans = null;\n  }\n  function attachMarkedSpans(line, spans) {\n    if (!spans) { return }\n    for (var i = 0; i < spans.length; ++i)\n      { spans[i].marker.attachLine(line); }\n    line.markedSpans = spans;\n  }\n\n  // Helpers used when computing which overlapping collapsed span\n  // counts as the larger one.\n  function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 }\n  function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 }\n\n  // Returns a number indicating which of two overlapping collapsed\n  // spans is larger (and thus includes the other). Falls back to\n  // comparing ids when the spans cover exactly the same range.\n  function compareCollapsedMarkers(a, b) {\n    var lenDiff = a.lines.length - b.lines.length;\n    if (lenDiff != 0) { return lenDiff }\n    var aPos = a.find(), bPos = b.find();\n    var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b);\n    if (fromCmp) { return -fromCmp }\n    var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b);\n    if (toCmp) { return toCmp }\n    return b.id - a.id\n  }\n\n  // Find out whether a line ends or starts in a collapsed span. If\n  // so, return the marker for that span.\n  function collapsedSpanAtSide(line, start) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (sp.marker.collapsed && (start ? sp.from : sp.to) == null &&\n          (!found || compareCollapsedMarkers(found, sp.marker) < 0))\n        { found = sp.marker; }\n    } }\n    return found\n  }\n  function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) }\n  function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) }\n\n  function collapsedSpanAround(line, ch) {\n    var sps = sawCollapsedSpans && line.markedSpans, found;\n    if (sps) { for (var i = 0; i < sps.length; ++i) {\n      var sp = sps[i];\n      if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) &&\n          (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; }\n    } }\n    return found\n  }\n\n  // Test whether there exists a collapsed span that partially\n  // overlaps (covers the start or end, but not both) of a new span.\n  // Such overlap is not allowed.\n  function conflictingCollapsedRange(doc, lineNo, from, to, marker) {\n    var line = getLine(doc, lineNo);\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) { for (var i = 0; i < sps.length; ++i) {\n      var sp = sps[i];\n      if (!sp.marker.collapsed) { continue }\n      var found = sp.marker.find(0);\n      var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker);\n      var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker);\n      if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) { continue }\n      if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) ||\n          fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0))\n        { return true }\n    } }\n  }\n\n  // A visual line is a line as drawn on the screen. Folding, for\n  // example, can cause multiple logical lines to appear on the same\n  // visual line. This finds the start of the visual line that the\n  // given line is part of (usually that is the line itself).\n  function visualLine(line) {\n    var merged;\n    while (merged = collapsedSpanAtStart(line))\n      { line = merged.find(-1, true).line; }\n    return line\n  }\n\n  function visualLineEnd(line) {\n    var merged;\n    while (merged = collapsedSpanAtEnd(line))\n      { line = merged.find(1, true).line; }\n    return line\n  }\n\n  // Returns an array of logical lines that continue the visual line\n  // started by the argument, or undefined if there are no such lines.\n  function visualLineContinued(line) {\n    var merged, lines;\n    while (merged = collapsedSpanAtEnd(line)) {\n      line = merged.find(1, true).line\n      ;(lines || (lines = [])).push(line);\n    }\n    return lines\n  }\n\n  // Get the line number of the start of the visual line that the\n  // given line number is part of.\n  function visualLineNo(doc, lineN) {\n    var line = getLine(doc, lineN), vis = visualLine(line);\n    if (line == vis) { return lineN }\n    return lineNo(vis)\n  }\n\n  // Get the line number of the start of the next visual line after\n  // the given line.\n  function visualLineEndNo(doc, lineN) {\n    if (lineN > doc.lastLine()) { return lineN }\n    var line = getLine(doc, lineN), merged;\n    if (!lineIsHidden(doc, line)) { return lineN }\n    while (merged = collapsedSpanAtEnd(line))\n      { line = merged.find(1, true).line; }\n    return lineNo(line) + 1\n  }\n\n  // Compute whether a line is hidden. Lines count as hidden when they\n  // are part of a visual line that starts with another line, or when\n  // they are entirely covered by collapsed, non-widget span.\n  function lineIsHidden(doc, line) {\n    var sps = sawCollapsedSpans && line.markedSpans;\n    if (sps) { for (var sp = (void 0), i = 0; i < sps.length; ++i) {\n      sp = sps[i];\n      if (!sp.marker.collapsed) { continue }\n      if (sp.from == null) { return true }\n      if (sp.marker.widgetNode) { continue }\n      if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))\n        { return true }\n    } }\n  }\n  function lineIsHiddenInner(doc, line, span) {\n    if (span.to == null) {\n      var end = span.marker.find(1, true);\n      return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker))\n    }\n    if (span.marker.inclusiveRight && span.to == line.text.length)\n      { return true }\n    for (var sp = (void 0), i = 0; i < line.markedSpans.length; ++i) {\n      sp = line.markedSpans[i];\n      if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to &&\n          (sp.to == null || sp.to != span.from) &&\n          (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&\n          lineIsHiddenInner(doc, line, sp)) { return true }\n    }\n  }\n\n  // Find the height above the given line.\n  function heightAtLine(lineObj) {\n    lineObj = visualLine(lineObj);\n\n    var h = 0, chunk = lineObj.parent;\n    for (var i = 0; i < chunk.lines.length; ++i) {\n      var line = chunk.lines[i];\n      if (line == lineObj) { break }\n      else { h += line.height; }\n    }\n    for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {\n      for (var i$1 = 0; i$1 < p.children.length; ++i$1) {\n        var cur = p.children[i$1];\n        if (cur == chunk) { break }\n        else { h += cur.height; }\n      }\n    }\n    return h\n  }\n\n  // Compute the character length of a line, taking into account\n  // collapsed ranges (see markText) that might hide parts, and join\n  // other lines onto it.\n  function lineLength(line) {\n    if (line.height == 0) { return 0 }\n    var len = line.text.length, merged, cur = line;\n    while (merged = collapsedSpanAtStart(cur)) {\n      var found = merged.find(0, true);\n      cur = found.from.line;\n      len += found.from.ch - found.to.ch;\n    }\n    cur = line;\n    while (merged = collapsedSpanAtEnd(cur)) {\n      var found$1 = merged.find(0, true);\n      len -= cur.text.length - found$1.from.ch;\n      cur = found$1.to.line;\n      len += cur.text.length - found$1.to.ch;\n    }\n    return len\n  }\n\n  // Find the longest line in the document.\n  function findMaxLine(cm) {\n    var d = cm.display, doc = cm.doc;\n    d.maxLine = getLine(doc, doc.first);\n    d.maxLineLength = lineLength(d.maxLine);\n    d.maxLineChanged = true;\n    doc.iter(function (line) {\n      var len = lineLength(line);\n      if (len > d.maxLineLength) {\n        d.maxLineLength = len;\n        d.maxLine = line;\n      }\n    });\n  }\n\n  // LINE DATA STRUCTURE\n\n  // Line objects. These hold state related to a line, including\n  // highlighting info (the styles array).\n  var Line = function(text, markedSpans, estimateHeight) {\n    this.text = text;\n    attachMarkedSpans(this, markedSpans);\n    this.height = estimateHeight ? estimateHeight(this) : 1;\n  };\n\n  Line.prototype.lineNo = function () { return lineNo(this) };\n  eventMixin(Line);\n\n  // Change the content (text, markers) of a line. Automatically\n  // invalidates cached information and tries to re-estimate the\n  // line's height.\n  function updateLine(line, text, markedSpans, estimateHeight) {\n    line.text = text;\n    if (line.stateAfter) { line.stateAfter = null; }\n    if (line.styles) { line.styles = null; }\n    if (line.order != null) { line.order = null; }\n    detachMarkedSpans(line);\n    attachMarkedSpans(line, markedSpans);\n    var estHeight = estimateHeight ? estimateHeight(line) : 1;\n    if (estHeight != line.height) { updateLineHeight(line, estHeight); }\n  }\n\n  // Detach a line from the document tree and its markers.\n  function cleanUpLine(line) {\n    line.parent = null;\n    detachMarkedSpans(line);\n  }\n\n  // Convert a style as returned by a mode (either null, or a string\n  // containing one or more styles) to a CSS style. This is cached,\n  // and also looks for line-wide styles.\n  var styleToClassCache = {}, styleToClassCacheWithMode = {};\n  function interpretTokenStyle(style, options) {\n    if (!style || /^\\s*$/.test(style)) { return null }\n    var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;\n    return cache[style] ||\n      (cache[style] = style.replace(/\\S+/g, \"cm-$&\"))\n  }\n\n  // Render the DOM representation of the text of a line. Also builds\n  // up a 'line map', which points at the DOM nodes that represent\n  // specific stretches of text, and is used by the measuring code.\n  // The returned object contains the DOM node, this map, and\n  // information about line-wide styles that were set by the mode.\n  function buildLineContent(cm, lineView) {\n    // The padding-right forces the element to have a 'border', which\n    // is needed on Webkit to be able to get line-level bounding\n    // rectangles for it (in measureChar).\n    var content = eltP(\"span\", null, null, webkit ? \"padding-right: .1px\" : null);\n    var builder = {pre: eltP(\"pre\", [content], \"CodeMirror-line\"), content: content,\n                   col: 0, pos: 0, cm: cm,\n                   trailingSpace: false,\n                   splitSpaces: cm.getOption(\"lineWrapping\")};\n    lineView.measure = {};\n\n    // Iterate over the logical lines that make up this visual line.\n    for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) {\n      var line = i ? lineView.rest[i - 1] : lineView.line, order = (void 0);\n      builder.pos = 0;\n      builder.addToken = buildToken;\n      // Optionally wire in some hacks into the token-rendering\n      // algorithm, to deal with browser quirks.\n      if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction)))\n        { builder.addToken = buildTokenBadBidi(builder.addToken, order); }\n      builder.map = [];\n      var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line);\n      insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate));\n      if (line.styleClasses) {\n        if (line.styleClasses.bgClass)\n          { builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || \"\"); }\n        if (line.styleClasses.textClass)\n          { builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || \"\"); }\n      }\n\n      // Ensure at least a single node is present, for measuring.\n      if (builder.map.length == 0)\n        { builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); }\n\n      // Store the map and a cache object for the current logical line\n      if (i == 0) {\n        lineView.measure.map = builder.map;\n        lineView.measure.cache = {};\n      } else {\n  (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map)\n        ;(lineView.measure.caches || (lineView.measure.caches = [])).push({});\n      }\n    }\n\n    // See issue #2901\n    if (webkit) {\n      var last = builder.content.lastChild;\n      if (/\\bcm-tab\\b/.test(last.className) || (last.querySelector && last.querySelector(\".cm-tab\")))\n        { builder.content.className = \"cm-tab-wrap-hack\"; }\n    }\n\n    signal(cm, \"renderLine\", cm, lineView.line, builder.pre);\n    if (builder.pre.className)\n      { builder.textClass = joinClasses(builder.pre.className, builder.textClass || \"\"); }\n\n    return builder\n  }\n\n  function defaultSpecialCharPlaceholder(ch) {\n    var token = elt(\"span\", \"\\u2022\", \"cm-invalidchar\");\n    token.title = \"\\\\u\" + ch.charCodeAt(0).toString(16);\n    token.setAttribute(\"aria-label\", token.title);\n    return token\n  }\n\n  // Build up the DOM representation for a single token, and add it to\n  // the line map. Takes care to render special characters separately.\n  function buildToken(builder, text, style, startStyle, endStyle, css, attributes) {\n    if (!text) { return }\n    var displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text;\n    var special = builder.cm.state.specialChars, mustWrap = false;\n    var content;\n    if (!special.test(text)) {\n      builder.col += text.length;\n      content = document.createTextNode(displayText);\n      builder.map.push(builder.pos, builder.pos + text.length, content);\n      if (ie && ie_version < 9) { mustWrap = true; }\n      builder.pos += text.length;\n    } else {\n      content = document.createDocumentFragment();\n      var pos = 0;\n      while (true) {\n        special.lastIndex = pos;\n        var m = special.exec(text);\n        var skipped = m ? m.index - pos : text.length - pos;\n        if (skipped) {\n          var txt = document.createTextNode(displayText.slice(pos, pos + skipped));\n          if (ie && ie_version < 9) { content.appendChild(elt(\"span\", [txt])); }\n          else { content.appendChild(txt); }\n          builder.map.push(builder.pos, builder.pos + skipped, txt);\n          builder.col += skipped;\n          builder.pos += skipped;\n        }\n        if (!m) { break }\n        pos += skipped + 1;\n        var txt$1 = (void 0);\n        if (m[0] == \"\\t\") {\n          var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;\n          txt$1 = content.appendChild(elt(\"span\", spaceStr(tabWidth), \"cm-tab\"));\n          txt$1.setAttribute(\"role\", \"presentation\");\n          txt$1.setAttribute(\"cm-text\", \"\\t\");\n          builder.col += tabWidth;\n        } else if (m[0] == \"\\r\" || m[0] == \"\\n\") {\n          txt$1 = content.appendChild(elt(\"span\", m[0] == \"\\r\" ? \"\\u240d\" : \"\\u2424\", \"cm-invalidchar\"));\n          txt$1.setAttribute(\"cm-text\", m[0]);\n          builder.col += 1;\n        } else {\n          txt$1 = builder.cm.options.specialCharPlaceholder(m[0]);\n          txt$1.setAttribute(\"cm-text\", m[0]);\n          if (ie && ie_version < 9) { content.appendChild(elt(\"span\", [txt$1])); }\n          else { content.appendChild(txt$1); }\n          builder.col += 1;\n        }\n        builder.map.push(builder.pos, builder.pos + 1, txt$1);\n        builder.pos++;\n      }\n    }\n    builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32;\n    if (style || startStyle || endStyle || mustWrap || css) {\n      var fullStyle = style || \"\";\n      if (startStyle) { fullStyle += startStyle; }\n      if (endStyle) { fullStyle += endStyle; }\n      var token = elt(\"span\", [content], fullStyle, css);\n      if (attributes) {\n        for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != \"style\" && attr != \"class\")\n          { token.setAttribute(attr, attributes[attr]); } }\n      }\n      return builder.content.appendChild(token)\n    }\n    builder.content.appendChild(content);\n  }\n\n  // Change some spaces to NBSP to prevent the browser from collapsing\n  // trailing spaces at the end of a line when rendering text (issue #1362).\n  function splitSpaces(text, trailingBefore) {\n    if (text.length > 1 && !/  /.test(text)) { return text }\n    var spaceBefore = trailingBefore, result = \"\";\n    for (var i = 0; i < text.length; i++) {\n      var ch = text.charAt(i);\n      if (ch == \" \" && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32))\n        { ch = \"\\u00a0\"; }\n      result += ch;\n      spaceBefore = ch == \" \";\n    }\n    return result\n  }\n\n  // Work around nonsense dimensions being reported for stretches of\n  // right-to-left text.\n  function buildTokenBadBidi(inner, order) {\n    return function (builder, text, style, startStyle, endStyle, css, attributes) {\n      style = style ? style + \" cm-force-border\" : \"cm-force-border\";\n      var start = builder.pos, end = start + text.length;\n      for (;;) {\n        // Find the part that overlaps with the start of this text\n        var part = (void 0);\n        for (var i = 0; i < order.length; i++) {\n          part = order[i];\n          if (part.to > start && part.from <= start) { break }\n        }\n        if (part.to >= end) { return inner(builder, text, style, startStyle, endStyle, css, attributes) }\n        inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes);\n        startStyle = null;\n        text = text.slice(part.to - start);\n        start = part.to;\n      }\n    }\n  }\n\n  function buildCollapsedSpan(builder, size, marker, ignoreWidget) {\n    var widget = !ignoreWidget && marker.widgetNode;\n    if (widget) { builder.map.push(builder.pos, builder.pos + size, widget); }\n    if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) {\n      if (!widget)\n        { widget = builder.content.appendChild(document.createElement(\"span\")); }\n      widget.setAttribute(\"cm-marker\", marker.id);\n    }\n    if (widget) {\n      builder.cm.display.input.setUneditable(widget);\n      builder.content.appendChild(widget);\n    }\n    builder.pos += size;\n    builder.trailingSpace = false;\n  }\n\n  // Outputs a number of spans to make up a line, taking highlighting\n  // and marked text into account.\n  function insertLineContent(line, builder, styles) {\n    var spans = line.markedSpans, allText = line.text, at = 0;\n    if (!spans) {\n      for (var i$1 = 1; i$1 < styles.length; i$1+=2)\n        { builder.addToken(builder, allText.slice(at, at = styles[i$1]), interpretTokenStyle(styles[i$1+1], builder.cm.options)); }\n      return\n    }\n\n    var len = allText.length, pos = 0, i = 1, text = \"\", style, css;\n    var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes;\n    for (;;) {\n      if (nextChange == pos) { // Update current marker set\n        spanStyle = spanEndStyle = spanStartStyle = css = \"\";\n        attributes = null;\n        collapsed = null; nextChange = Infinity;\n        var foundBookmarks = [], endStyles = (void 0);\n        for (var j = 0; j < spans.length; ++j) {\n          var sp = spans[j], m = sp.marker;\n          if (m.type == \"bookmark\" && sp.from == pos && m.widgetNode) {\n            foundBookmarks.push(m);\n          } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) {\n            if (sp.to != null && sp.to != pos && nextChange > sp.to) {\n              nextChange = sp.to;\n              spanEndStyle = \"\";\n            }\n            if (m.className) { spanStyle += \" \" + m.className; }\n            if (m.css) { css = (css ? css + \";\" : \"\") + m.css; }\n            if (m.startStyle && sp.from == pos) { spanStartStyle += \" \" + m.startStyle; }\n            if (m.endStyle && sp.to == nextChange) { (endStyles || (endStyles = [])).push(m.endStyle, sp.to); }\n            // support for the old title property\n            // https://github.com/codemirror/CodeMirror/pull/5673\n            if (m.title) { (attributes || (attributes = {})).title = m.title; }\n            if (m.attributes) {\n              for (var attr in m.attributes)\n                { (attributes || (attributes = {}))[attr] = m.attributes[attr]; }\n            }\n            if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))\n              { collapsed = sp; }\n          } else if (sp.from > pos && nextChange > sp.from) {\n            nextChange = sp.from;\n          }\n        }\n        if (endStyles) { for (var j$1 = 0; j$1 < endStyles.length; j$1 += 2)\n          { if (endStyles[j$1 + 1] == nextChange) { spanEndStyle += \" \" + endStyles[j$1]; } } }\n\n        if (!collapsed || collapsed.from == pos) { for (var j$2 = 0; j$2 < foundBookmarks.length; ++j$2)\n          { buildCollapsedSpan(builder, 0, foundBookmarks[j$2]); } }\n        if (collapsed && (collapsed.from || 0) == pos) {\n          buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,\n                             collapsed.marker, collapsed.from == null);\n          if (collapsed.to == null) { return }\n          if (collapsed.to == pos) { collapsed = false; }\n        }\n      }\n      if (pos >= len) { break }\n\n      var upto = Math.min(len, nextChange);\n      while (true) {\n        if (text) {\n          var end = pos + text.length;\n          if (!collapsed) {\n            var tokenText = end > upto ? text.slice(0, upto - pos) : text;\n            builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,\n                             spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : \"\", css, attributes);\n          }\n          if (end >= upto) {text = text.slice(upto - pos); pos = upto; break}\n          pos = end;\n          spanStartStyle = \"\";\n        }\n        text = allText.slice(at, at = styles[i++]);\n        style = interpretTokenStyle(styles[i++], builder.cm.options);\n      }\n    }\n  }\n\n\n  // These objects are used to represent the visible (currently drawn)\n  // part of the document. A LineView may correspond to multiple\n  // logical lines, if those are connected by collapsed ranges.\n  function LineView(doc, line, lineN) {\n    // The starting line\n    this.line = line;\n    // Continuing lines, if any\n    this.rest = visualLineContinued(line);\n    // Number of logical lines in this visual line\n    this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1;\n    this.node = this.text = null;\n    this.hidden = lineIsHidden(doc, line);\n  }\n\n  // Create a range of LineView objects for the given lines.\n  function buildViewArray(cm, from, to) {\n    var array = [], nextPos;\n    for (var pos = from; pos < to; pos = nextPos) {\n      var view = new LineView(cm.doc, getLine(cm.doc, pos), pos);\n      nextPos = pos + view.size;\n      array.push(view);\n    }\n    return array\n  }\n\n  var operationGroup = null;\n\n  function pushOperation(op) {\n    if (operationGroup) {\n      operationGroup.ops.push(op);\n    } else {\n      op.ownsGroup = operationGroup = {\n        ops: [op],\n        delayedCallbacks: []\n      };\n    }\n  }\n\n  function fireCallbacksForOps(group) {\n    // Calls delayed callbacks and cursorActivity handlers until no\n    // new ones appear\n    var callbacks = group.delayedCallbacks, i = 0;\n    do {\n      for (; i < callbacks.length; i++)\n        { callbacks[i].call(null); }\n      for (var j = 0; j < group.ops.length; j++) {\n        var op = group.ops[j];\n        if (op.cursorActivityHandlers)\n          { while (op.cursorActivityCalled < op.cursorActivityHandlers.length)\n            { op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); } }\n      }\n    } while (i < callbacks.length)\n  }\n\n  function finishOperation(op, endCb) {\n    var group = op.ownsGroup;\n    if (!group) { return }\n\n    try { fireCallbacksForOps(group); }\n    finally {\n      operationGroup = null;\n      endCb(group);\n    }\n  }\n\n  var orphanDelayedCallbacks = null;\n\n  // Often, we want to signal events at a point where we are in the\n  // middle of some work, but don't want the handler to start calling\n  // other methods on the editor, which might be in an inconsistent\n  // state or simply not expect any other events to happen.\n  // signalLater looks whether there are any handlers, and schedules\n  // them to be executed when the last operation ends, or, if no\n  // operation is active, when a timeout fires.\n  function signalLater(emitter, type /*, values...*/) {\n    var arr = getHandlers(emitter, type);\n    if (!arr.length) { return }\n    var args = Array.prototype.slice.call(arguments, 2), list;\n    if (operationGroup) {\n      list = operationGroup.delayedCallbacks;\n    } else if (orphanDelayedCallbacks) {\n      list = orphanDelayedCallbacks;\n    } else {\n      list = orphanDelayedCallbacks = [];\n      setTimeout(fireOrphanDelayed, 0);\n    }\n    var loop = function ( i ) {\n      list.push(function () { return arr[i].apply(null, args); });\n    };\n\n    for (var i = 0; i < arr.length; ++i)\n      loop( i );\n  }\n\n  function fireOrphanDelayed() {\n    var delayed = orphanDelayedCallbacks;\n    orphanDelayedCallbacks = null;\n    for (var i = 0; i < delayed.length; ++i) { delayed[i](); }\n  }\n\n  // When an aspect of a line changes, a string is added to\n  // lineView.changes. This updates the relevant part of the line's\n  // DOM structure.\n  function updateLineForChanges(cm, lineView, lineN, dims) {\n    for (var j = 0; j < lineView.changes.length; j++) {\n      var type = lineView.changes[j];\n      if (type == \"text\") { updateLineText(cm, lineView); }\n      else if (type == \"gutter\") { updateLineGutter(cm, lineView, lineN, dims); }\n      else if (type == \"class\") { updateLineClasses(cm, lineView); }\n      else if (type == \"widget\") { updateLineWidgets(cm, lineView, dims); }\n    }\n    lineView.changes = null;\n  }\n\n  // Lines with gutter elements, widgets or a background class need to\n  // be wrapped, and have the extra elements added to the wrapper div\n  function ensureLineWrapped(lineView) {\n    if (lineView.node == lineView.text) {\n      lineView.node = elt(\"div\", null, null, \"position: relative\");\n      if (lineView.text.parentNode)\n        { lineView.text.parentNode.replaceChild(lineView.node, lineView.text); }\n      lineView.node.appendChild(lineView.text);\n      if (ie && ie_version < 8) { lineView.node.style.zIndex = 2; }\n    }\n    return lineView.node\n  }\n\n  function updateLineBackground(cm, lineView) {\n    var cls = lineView.bgClass ? lineView.bgClass + \" \" + (lineView.line.bgClass || \"\") : lineView.line.bgClass;\n    if (cls) { cls += \" CodeMirror-linebackground\"; }\n    if (lineView.background) {\n      if (cls) { lineView.background.className = cls; }\n      else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; }\n    } else if (cls) {\n      var wrap = ensureLineWrapped(lineView);\n      lineView.background = wrap.insertBefore(elt(\"div\", null, cls), wrap.firstChild);\n      cm.display.input.setUneditable(lineView.background);\n    }\n  }\n\n  // Wrapper around buildLineContent which will reuse the structure\n  // in display.externalMeasured when possible.\n  function getLineContent(cm, lineView) {\n    var ext = cm.display.externalMeasured;\n    if (ext && ext.line == lineView.line) {\n      cm.display.externalMeasured = null;\n      lineView.measure = ext.measure;\n      return ext.built\n    }\n    return buildLineContent(cm, lineView)\n  }\n\n  // Redraw the line's text. Interacts with the background and text\n  // classes because the mode may output tokens that influence these\n  // classes.\n  function updateLineText(cm, lineView) {\n    var cls = lineView.text.className;\n    var built = getLineContent(cm, lineView);\n    if (lineView.text == lineView.node) { lineView.node = built.pre; }\n    lineView.text.parentNode.replaceChild(built.pre, lineView.text);\n    lineView.text = built.pre;\n    if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) {\n      lineView.bgClass = built.bgClass;\n      lineView.textClass = built.textClass;\n      updateLineClasses(cm, lineView);\n    } else if (cls) {\n      lineView.text.className = cls;\n    }\n  }\n\n  function updateLineClasses(cm, lineView) {\n    updateLineBackground(cm, lineView);\n    if (lineView.line.wrapClass)\n      { ensureLineWrapped(lineView).className = lineView.line.wrapClass; }\n    else if (lineView.node != lineView.text)\n      { lineView.node.className = \"\"; }\n    var textClass = lineView.textClass ? lineView.textClass + \" \" + (lineView.line.textClass || \"\") : lineView.line.textClass;\n    lineView.text.className = textClass || \"\";\n  }\n\n  function updateLineGutter(cm, lineView, lineN, dims) {\n    if (lineView.gutter) {\n      lineView.node.removeChild(lineView.gutter);\n      lineView.gutter = null;\n    }\n    if (lineView.gutterBackground) {\n      lineView.node.removeChild(lineView.gutterBackground);\n      lineView.gutterBackground = null;\n    }\n    if (lineView.line.gutterClass) {\n      var wrap = ensureLineWrapped(lineView);\n      lineView.gutterBackground = elt(\"div\", null, \"CodeMirror-gutter-background \" + lineView.line.gutterClass,\n                                      (\"left: \" + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px; width: \" + (dims.gutterTotalWidth) + \"px\"));\n      cm.display.input.setUneditable(lineView.gutterBackground);\n      wrap.insertBefore(lineView.gutterBackground, lineView.text);\n    }\n    var markers = lineView.line.gutterMarkers;\n    if (cm.options.lineNumbers || markers) {\n      var wrap$1 = ensureLineWrapped(lineView);\n      var gutterWrap = lineView.gutter = elt(\"div\", null, \"CodeMirror-gutter-wrapper\", (\"left: \" + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + \"px\"));\n      cm.display.input.setUneditable(gutterWrap);\n      wrap$1.insertBefore(gutterWrap, lineView.text);\n      if (lineView.line.gutterClass)\n        { gutterWrap.className += \" \" + lineView.line.gutterClass; }\n      if (cm.options.lineNumbers && (!markers || !markers[\"CodeMirror-linenumbers\"]))\n        { lineView.lineNumber = gutterWrap.appendChild(\n          elt(\"div\", lineNumberFor(cm.options, lineN),\n              \"CodeMirror-linenumber CodeMirror-gutter-elt\",\n              (\"left: \" + (dims.gutterLeft[\"CodeMirror-linenumbers\"]) + \"px; width: \" + (cm.display.lineNumInnerWidth) + \"px\"))); }\n      if (markers) { for (var k = 0; k < cm.display.gutterSpecs.length; ++k) {\n        var id = cm.display.gutterSpecs[k].className, found = markers.hasOwnProperty(id) && markers[id];\n        if (found)\n          { gutterWrap.appendChild(elt(\"div\", [found], \"CodeMirror-gutter-elt\",\n                                     (\"left: \" + (dims.gutterLeft[id]) + \"px; width: \" + (dims.gutterWidth[id]) + \"px\"))); }\n      } }\n    }\n  }\n\n  function updateLineWidgets(cm, lineView, dims) {\n    if (lineView.alignable) { lineView.alignable = null; }\n    var isWidget = classTest(\"CodeMirror-linewidget\");\n    for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {\n      next = node.nextSibling;\n      if (isWidget.test(node.className)) { lineView.node.removeChild(node); }\n    }\n    insertLineWidgets(cm, lineView, dims);\n  }\n\n  // Build a line's DOM representation from scratch\n  function buildLineElement(cm, lineView, lineN, dims) {\n    var built = getLineContent(cm, lineView);\n    lineView.text = lineView.node = built.pre;\n    if (built.bgClass) { lineView.bgClass = built.bgClass; }\n    if (built.textClass) { lineView.textClass = built.textClass; }\n\n    updateLineClasses(cm, lineView);\n    updateLineGutter(cm, lineView, lineN, dims);\n    insertLineWidgets(cm, lineView, dims);\n    return lineView.node\n  }\n\n  // A lineView may contain multiple logical lines (when merged by\n  // collapsed spans). The widgets for all of them need to be drawn.\n  function insertLineWidgets(cm, lineView, dims) {\n    insertLineWidgetsFor(cm, lineView.line, lineView, dims, true);\n    if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)\n      { insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); } }\n  }\n\n  function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) {\n    if (!line.widgets) { return }\n    var wrap = ensureLineWrapped(lineView);\n    for (var i = 0, ws = line.widgets; i < ws.length; ++i) {\n      var widget = ws[i], node = elt(\"div\", [widget.node], \"CodeMirror-linewidget\" + (widget.className ? \" \" + widget.className : \"\"));\n      if (!widget.handleMouseEvents) { node.setAttribute(\"cm-ignore-events\", \"true\"); }\n      positionLineWidget(widget, node, lineView, dims);\n      cm.display.input.setUneditable(node);\n      if (allowAbove && widget.above)\n        { wrap.insertBefore(node, lineView.gutter || lineView.text); }\n      else\n        { wrap.appendChild(node); }\n      signalLater(widget, \"redraw\");\n    }\n  }\n\n  function positionLineWidget(widget, node, lineView, dims) {\n    if (widget.noHScroll) {\n  (lineView.alignable || (lineView.alignable = [])).push(node);\n      var width = dims.wrapperWidth;\n      node.style.left = dims.fixedPos + \"px\";\n      if (!widget.coverGutter) {\n        width -= dims.gutterTotalWidth;\n        node.style.paddingLeft = dims.gutterTotalWidth + \"px\";\n      }\n      node.style.width = width + \"px\";\n    }\n    if (widget.coverGutter) {\n      node.style.zIndex = 5;\n      node.style.position = \"relative\";\n      if (!widget.noHScroll) { node.style.marginLeft = -dims.gutterTotalWidth + \"px\"; }\n    }\n  }\n\n  function widgetHeight(widget) {\n    if (widget.height != null) { return widget.height }\n    var cm = widget.doc.cm;\n    if (!cm) { return 0 }\n    if (!contains(document.body, widget.node)) {\n      var parentStyle = \"position: relative;\";\n      if (widget.coverGutter)\n        { parentStyle += \"margin-left: -\" + cm.display.gutters.offsetWidth + \"px;\"; }\n      if (widget.noHScroll)\n        { parentStyle += \"width: \" + cm.display.wrapper.clientWidth + \"px;\"; }\n      removeChildrenAndAdd(cm.display.measure, elt(\"div\", [widget.node], null, parentStyle));\n    }\n    return widget.height = widget.node.parentNode.offsetHeight\n  }\n\n  // Return true when the given mouse event happened in a widget\n  function eventInWidget(display, e) {\n    for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {\n      if (!n || (n.nodeType == 1 && n.getAttribute(\"cm-ignore-events\") == \"true\") ||\n          (n.parentNode == display.sizer && n != display.mover))\n        { return true }\n    }\n  }\n\n  // POSITION MEASUREMENT\n\n  function paddingTop(display) {return display.lineSpace.offsetTop}\n  function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight}\n  function paddingH(display) {\n    if (display.cachedPaddingH) { return display.cachedPaddingH }\n    var e = removeChildrenAndAdd(display.measure, elt(\"pre\", \"x\", \"CodeMirror-line-like\"));\n    var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle;\n    var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)};\n    if (!isNaN(data.left) && !isNaN(data.right)) { display.cachedPaddingH = data; }\n    return data\n  }\n\n  function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth }\n  function displayWidth(cm) {\n    return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth\n  }\n  function displayHeight(cm) {\n    return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight\n  }\n\n  // Ensure the lineView.wrapping.heights array is populated. This is\n  // an array of bottom offsets for the lines that make up a drawn\n  // line. When lineWrapping is on, there might be more than one\n  // height.\n  function ensureLineHeights(cm, lineView, rect) {\n    var wrapping = cm.options.lineWrapping;\n    var curWidth = wrapping && displayWidth(cm);\n    if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {\n      var heights = lineView.measure.heights = [];\n      if (wrapping) {\n        lineView.measure.width = curWidth;\n        var rects = lineView.text.firstChild.getClientRects();\n        for (var i = 0; i < rects.length - 1; i++) {\n          var cur = rects[i], next = rects[i + 1];\n          if (Math.abs(cur.bottom - next.bottom) > 2)\n            { heights.push((cur.bottom + next.top) / 2 - rect.top); }\n        }\n      }\n      heights.push(rect.bottom - rect.top);\n    }\n  }\n\n  // Find a line map (mapping character offsets to text nodes) and a\n  // measurement cache for the given line number. (A line view might\n  // contain multiple lines when collapsed ranges are present.)\n  function mapFromLineView(lineView, line, lineN) {\n    if (lineView.line == line)\n      { return {map: lineView.measure.map, cache: lineView.measure.cache} }\n    for (var i = 0; i < lineView.rest.length; i++)\n      { if (lineView.rest[i] == line)\n        { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }\n    for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)\n      { if (lineNo(lineView.rest[i$1]) > lineN)\n        { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }\n  }\n\n  // Render a line into the hidden node display.externalMeasured. Used\n  // when measurement is needed for a line that's not in the viewport.\n  function updateExternalMeasurement(cm, line) {\n    line = visualLine(line);\n    var lineN = lineNo(line);\n    var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN);\n    view.lineN = lineN;\n    var built = view.built = buildLineContent(cm, view);\n    view.text = built.pre;\n    removeChildrenAndAdd(cm.display.lineMeasure, built.pre);\n    return view\n  }\n\n  // Get a {top, bottom, left, right} box (in line-local coordinates)\n  // for a given character.\n  function measureChar(cm, line, ch, bias) {\n    return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias)\n  }\n\n  // Find a line view that corresponds to the given line number.\n  function findViewForLine(cm, lineN) {\n    if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo)\n      { return cm.display.view[findViewIndex(cm, lineN)] }\n    var ext = cm.display.externalMeasured;\n    if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)\n      { return ext }\n  }\n\n  // Measurement can be split in two steps, the set-up work that\n  // applies to the whole line, and the measurement of the actual\n  // character. Functions like coordsChar, that need to do a lot of\n  // measurements in a row, can thus ensure that the set-up work is\n  // only done once.\n  function prepareMeasureForLine(cm, line) {\n    var lineN = lineNo(line);\n    var view = findViewForLine(cm, lineN);\n    if (view && !view.text) {\n      view = null;\n    } else if (view && view.changes) {\n      updateLineForChanges(cm, view, lineN, getDimensions(cm));\n      cm.curOp.forceUpdate = true;\n    }\n    if (!view)\n      { view = updateExternalMeasurement(cm, line); }\n\n    var info = mapFromLineView(view, line, lineN);\n    return {\n      line: line, view: view, rect: null,\n      map: info.map, cache: info.cache, before: info.before,\n      hasHeights: false\n    }\n  }\n\n  // Given a prepared measurement object, measures the position of an\n  // actual character (or fetches it from the cache).\n  function measureCharPrepared(cm, prepared, ch, bias, varHeight) {\n    if (prepared.before) { ch = -1; }\n    var key = ch + (bias || \"\"), found;\n    if (prepared.cache.hasOwnProperty(key)) {\n      found = prepared.cache[key];\n    } else {\n      if (!prepared.rect)\n        { prepared.rect = prepared.view.text.getBoundingClientRect(); }\n      if (!prepared.hasHeights) {\n        ensureLineHeights(cm, prepared.view, prepared.rect);\n        prepared.hasHeights = true;\n      }\n      found = measureCharInner(cm, prepared, ch, bias);\n      if (!found.bogus) { prepared.cache[key] = found; }\n    }\n    return {left: found.left, right: found.right,\n            top: varHeight ? found.rtop : found.top,\n            bottom: varHeight ? found.rbottom : found.bottom}\n  }\n\n  var nullRect = {left: 0, right: 0, top: 0, bottom: 0};\n\n  function nodeAndOffsetInLineMap(map, ch, bias) {\n    var node, start, end, collapse, mStart, mEnd;\n    // First, search the line map for the text node corresponding to,\n    // or closest to, the target character.\n    for (var i = 0; i < map.length; i += 3) {\n      mStart = map[i];\n      mEnd = map[i + 1];\n      if (ch < mStart) {\n        start = 0; end = 1;\n        collapse = \"left\";\n      } else if (ch < mEnd) {\n        start = ch - mStart;\n        end = start + 1;\n      } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {\n        end = mEnd - mStart;\n        start = end - 1;\n        if (ch >= mEnd) { collapse = \"right\"; }\n      }\n      if (start != null) {\n        node = map[i + 2];\n        if (mStart == mEnd && bias == (node.insertLeft ? \"left\" : \"right\"))\n          { collapse = bias; }\n        if (bias == \"left\" && start == 0)\n          { while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {\n            node = map[(i -= 3) + 2];\n            collapse = \"left\";\n          } }\n        if (bias == \"right\" && start == mEnd - mStart)\n          { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {\n            node = map[(i += 3) + 2];\n            collapse = \"right\";\n          } }\n        break\n      }\n    }\n    return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}\n  }\n\n  function getUsefulRect(rects, bias) {\n    var rect = nullRect;\n    if (bias == \"left\") { for (var i = 0; i < rects.length; i++) {\n      if ((rect = rects[i]).left != rect.right) { break }\n    } } else { for (var i$1 = rects.length - 1; i$1 >= 0; i$1--) {\n      if ((rect = rects[i$1]).left != rect.right) { break }\n    } }\n    return rect\n  }\n\n  function measureCharInner(cm, prepared, ch, bias) {\n    var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);\n    var node = place.node, start = place.start, end = place.end, collapse = place.collapse;\n\n    var rect;\n    if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.\n      for (var i$1 = 0; i$1 < 4; i$1++) { // Retry a maximum of 4 times when nonsense rectangles are returned\n        while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) { --start; }\n        while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) { ++end; }\n        if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart)\n          { rect = node.parentNode.getBoundingClientRect(); }\n        else\n          { rect = getUsefulRect(range(node, start, end).getClientRects(), bias); }\n        if (rect.left || rect.right || start == 0) { break }\n        end = start;\n        start = start - 1;\n        collapse = \"right\";\n      }\n      if (ie && ie_version < 11) { rect = maybeUpdateRectForZooming(cm.display.measure, rect); }\n    } else { // If it is a widget, simply get the box for the whole widget.\n      if (start > 0) { collapse = bias = \"right\"; }\n      var rects;\n      if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1)\n        { rect = rects[bias == \"right\" ? rects.length - 1 : 0]; }\n      else\n        { rect = node.getBoundingClientRect(); }\n    }\n    if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) {\n      var rSpan = node.parentNode.getClientRects()[0];\n      if (rSpan)\n        { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; }\n      else\n        { rect = nullRect; }\n    }\n\n    var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top;\n    var mid = (rtop + rbot) / 2;\n    var heights = prepared.view.measure.heights;\n    var i = 0;\n    for (; i < heights.length - 1; i++)\n      { if (mid < heights[i]) { break } }\n    var top = i ? heights[i - 1] : 0, bot = heights[i];\n    var result = {left: (collapse == \"right\" ? rect.right : rect.left) - prepared.rect.left,\n                  right: (collapse == \"left\" ? rect.left : rect.right) - prepared.rect.left,\n                  top: top, bottom: bot};\n    if (!rect.left && !rect.right) { result.bogus = true; }\n    if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }\n\n    return result\n  }\n\n  // Work around problem with bounding client rects on ranges being\n  // returned incorrectly when zoomed on IE10 and below.\n  function maybeUpdateRectForZooming(measure, rect) {\n    if (!window.screen || screen.logicalXDPI == null ||\n        screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))\n      { return rect }\n    var scaleX = screen.logicalXDPI / screen.deviceXDPI;\n    var scaleY = screen.logicalYDPI / screen.deviceYDPI;\n    return {left: rect.left * scaleX, right: rect.right * scaleX,\n            top: rect.top * scaleY, bottom: rect.bottom * scaleY}\n  }\n\n  function clearLineMeasurementCacheFor(lineView) {\n    if (lineView.measure) {\n      lineView.measure.cache = {};\n      lineView.measure.heights = null;\n      if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)\n        { lineView.measure.caches[i] = {}; } }\n    }\n  }\n\n  function clearLineMeasurementCache(cm) {\n    cm.display.externalMeasure = null;\n    removeChildren(cm.display.lineMeasure);\n    for (var i = 0; i < cm.display.view.length; i++)\n      { clearLineMeasurementCacheFor(cm.display.view[i]); }\n  }\n\n  function clearCaches(cm) {\n    clearLineMeasurementCache(cm);\n    cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null;\n    if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; }\n    cm.display.lineNumChars = null;\n  }\n\n  function pageScrollX() {\n    // Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206\n    // which causes page_Offset and bounding client rects to use\n    // different reference viewports and invalidate our calculations.\n    if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }\n    return window.pageXOffset || (document.documentElement || document.body).scrollLeft\n  }\n  function pageScrollY() {\n    if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }\n    return window.pageYOffset || (document.documentElement || document.body).scrollTop\n  }\n\n  function widgetTopHeight(lineObj) {\n    var height = 0;\n    if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above)\n      { height += widgetHeight(lineObj.widgets[i]); } } }\n    return height\n  }\n\n  // Converts a {top, bottom, left, right} box from line-local\n  // coordinates into another coordinate system. Context may be one of\n  // \"line\", \"div\" (display.lineDiv), \"local\"./null (editor), \"window\",\n  // or \"page\".\n  function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {\n    if (!includeWidgets) {\n      var height = widgetTopHeight(lineObj);\n      rect.top += height; rect.bottom += height;\n    }\n    if (context == \"line\") { return rect }\n    if (!context) { context = \"local\"; }\n    var yOff = heightAtLine(lineObj);\n    if (context == \"local\") { yOff += paddingTop(cm.display); }\n    else { yOff -= cm.display.viewOffset; }\n    if (context == \"page\" || context == \"window\") {\n      var lOff = cm.display.lineSpace.getBoundingClientRect();\n      yOff += lOff.top + (context == \"window\" ? 0 : pageScrollY());\n      var xOff = lOff.left + (context == \"window\" ? 0 : pageScrollX());\n      rect.left += xOff; rect.right += xOff;\n    }\n    rect.top += yOff; rect.bottom += yOff;\n    return rect\n  }\n\n  // Coverts a box from \"div\" coords to another coordinate system.\n  // Context may be \"window\", \"page\", \"div\", or \"local\"./null.\n  function fromCoordSystem(cm, coords, context) {\n    if (context == \"div\") { return coords }\n    var left = coords.left, top = coords.top;\n    // First move into \"page\" coordinate system\n    if (context == \"page\") {\n      left -= pageScrollX();\n      top -= pageScrollY();\n    } else if (context == \"local\" || !context) {\n      var localBox = cm.display.sizer.getBoundingClientRect();\n      left += localBox.left;\n      top += localBox.top;\n    }\n\n    var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();\n    return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}\n  }\n\n  function charCoords(cm, pos, context, lineObj, bias) {\n    if (!lineObj) { lineObj = getLine(cm.doc, pos.line); }\n    return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context)\n  }\n\n  // Returns a box for a given cursor position, which may have an\n  // 'other' property containing the position of the secondary cursor\n  // on a bidi boundary.\n  // A cursor Pos(line, char, \"before\") is on the same visual line as `char - 1`\n  // and after `char - 1` in writing order of `char - 1`\n  // A cursor Pos(line, char, \"after\") is on the same visual line as `char`\n  // and before `char` in writing order of `char`\n  // Examples (upper-case letters are RTL, lower-case are LTR):\n  //     Pos(0, 1, ...)\n  //     before   after\n  // ab     a|b     a|b\n  // aB     a|B     aB|\n  // Ab     |Ab     A|b\n  // AB     B|A     B|A\n  // Every position after the last character on a line is considered to stick\n  // to the last character on the line.\n  function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) {\n    lineObj = lineObj || getLine(cm.doc, pos.line);\n    if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }\n    function get(ch, right) {\n      var m = measureCharPrepared(cm, preparedMeasure, ch, right ? \"right\" : \"left\", varHeight);\n      if (right) { m.left = m.right; } else { m.right = m.left; }\n      return intoCoordSystem(cm, lineObj, m, context)\n    }\n    var order = getOrder(lineObj, cm.doc.direction), ch = pos.ch, sticky = pos.sticky;\n    if (ch >= lineObj.text.length) {\n      ch = lineObj.text.length;\n      sticky = \"before\";\n    } else if (ch <= 0) {\n      ch = 0;\n      sticky = \"after\";\n    }\n    if (!order) { return get(sticky == \"before\" ? ch - 1 : ch, sticky == \"before\") }\n\n    function getBidi(ch, partPos, invert) {\n      var part = order[partPos], right = part.level == 1;\n      return get(invert ? ch - 1 : ch, right != invert)\n    }\n    var partPos = getBidiPartAt(order, ch, sticky);\n    var other = bidiOther;\n    var val = getBidi(ch, partPos, sticky == \"before\");\n    if (other != null) { val.other = getBidi(ch, other, sticky != \"before\"); }\n    return val\n  }\n\n  // Used to cheaply estimate the coordinates for a position. Used for\n  // intermediate scroll updates.\n  function estimateCoords(cm, pos) {\n    var left = 0;\n    pos = clipPos(cm.doc, pos);\n    if (!cm.options.lineWrapping) { left = charWidth(cm.display) * pos.ch; }\n    var lineObj = getLine(cm.doc, pos.line);\n    var top = heightAtLine(lineObj) + paddingTop(cm.display);\n    return {left: left, right: left, top: top, bottom: top + lineObj.height}\n  }\n\n  // Positions returned by coordsChar contain some extra information.\n  // xRel is the relative x position of the input coordinates compared\n  // to the found position (so xRel > 0 means the coordinates are to\n  // the right of the character position, for example). When outside\n  // is true, that means the coordinates lie outside the line's\n  // vertical range.\n  function PosWithInfo(line, ch, sticky, outside, xRel) {\n    var pos = Pos(line, ch, sticky);\n    pos.xRel = xRel;\n    if (outside) { pos.outside = outside; }\n    return pos\n  }\n\n  // Compute the character position closest to the given coordinates.\n  // Input must be lineSpace-local (\"div\" coordinate system).\n  function coordsChar(cm, x, y) {\n    var doc = cm.doc;\n    y += cm.display.viewOffset;\n    if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -1) }\n    var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1;\n    if (lineN > last)\n      { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 1) }\n    if (x < 0) { x = 0; }\n\n    var lineObj = getLine(doc, lineN);\n    for (;;) {\n      var found = coordsCharInner(cm, lineObj, lineN, x, y);\n      var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0));\n      if (!collapsed) { return found }\n      var rangeEnd = collapsed.find(1);\n      if (rangeEnd.line == lineN) { return rangeEnd }\n      lineObj = getLine(doc, lineN = rangeEnd.line);\n    }\n  }\n\n  function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {\n    y -= widgetTopHeight(lineObj);\n    var end = lineObj.text.length;\n    var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0);\n    end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end);\n    return {begin: begin, end: end}\n  }\n\n  function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {\n    if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }\n    var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), \"line\").top;\n    return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)\n  }\n\n  // Returns true if the given side of a box is after the given\n  // coordinates, in top-to-bottom, left-to-right order.\n  function boxIsAfter(box, x, y, left) {\n    return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x\n  }\n\n  function coordsCharInner(cm, lineObj, lineNo, x, y) {\n    // Move y into line-local coordinate space\n    y -= heightAtLine(lineObj);\n    var preparedMeasure = prepareMeasureForLine(cm, lineObj);\n    // When directly calling `measureCharPrepared`, we have to adjust\n    // for the widgets at this line.\n    var widgetHeight = widgetTopHeight(lineObj);\n    var begin = 0, end = lineObj.text.length, ltr = true;\n\n    var order = getOrder(lineObj, cm.doc.direction);\n    // If the line isn't plain left-to-right text, first figure out\n    // which bidi section the coordinates fall into.\n    if (order) {\n      var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)\n                   (cm, lineObj, lineNo, preparedMeasure, order, x, y);\n      ltr = part.level != 1;\n      // The awkward -1 offsets are needed because findFirst (called\n      // on these below) will treat its first bound as inclusive,\n      // second as exclusive, but we want to actually address the\n      // characters in the part's range\n      begin = ltr ? part.from : part.to - 1;\n      end = ltr ? part.to : part.from - 1;\n    }\n\n    // A binary search to find the first character whose bounding box\n    // starts after the coordinates. If we run across any whose box wrap\n    // the coordinates, store that.\n    var chAround = null, boxAround = null;\n    var ch = findFirst(function (ch) {\n      var box = measureCharPrepared(cm, preparedMeasure, ch);\n      box.top += widgetHeight; box.bottom += widgetHeight;\n      if (!boxIsAfter(box, x, y, false)) { return false }\n      if (box.top <= y && box.left <= x) {\n        chAround = ch;\n        boxAround = box;\n      }\n      return true\n    }, begin, end);\n\n    var baseX, sticky, outside = false;\n    // If a box around the coordinates was found, use that\n    if (boxAround) {\n      // Distinguish coordinates nearer to the left or right side of the box\n      var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr;\n      ch = chAround + (atStart ? 0 : 1);\n      sticky = atStart ? \"after\" : \"before\";\n      baseX = atLeft ? boxAround.left : boxAround.right;\n    } else {\n      // (Adjust for extended bound, if necessary.)\n      if (!ltr && (ch == end || ch == begin)) { ch++; }\n      // To determine which side to associate with, get the box to the\n      // left of the character and compare it's vertical position to the\n      // coordinates\n      sticky = ch == 0 ? \"after\" : ch == lineObj.text.length ? \"before\" :\n        (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ?\n        \"after\" : \"before\";\n      // Now get accurate coordinates for this place, in order to get a\n      // base X position\n      var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), \"line\", lineObj, preparedMeasure);\n      baseX = coords.left;\n      outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0;\n    }\n\n    ch = skipExtendingChars(lineObj.text, ch, 1);\n    return PosWithInfo(lineNo, ch, sticky, outside, x - baseX)\n  }\n\n  function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {\n    // Bidi parts are sorted left-to-right, and in a non-line-wrapping\n    // situation, we can take this ordering to correspond to the visual\n    // ordering. This finds the first part whose end is after the given\n    // coordinates.\n    var index = findFirst(function (i) {\n      var part = order[i], ltr = part.level != 1;\n      return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? \"before\" : \"after\"),\n                                     \"line\", lineObj, preparedMeasure), x, y, true)\n    }, 0, order.length - 1);\n    var part = order[index];\n    // If this isn't the first part, the part's start is also after\n    // the coordinates, and the coordinates aren't on the same line as\n    // that start, move one part back.\n    if (index > 0) {\n      var ltr = part.level != 1;\n      var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? \"after\" : \"before\"),\n                               \"line\", lineObj, preparedMeasure);\n      if (boxIsAfter(start, x, y, true) && start.top > y)\n        { part = order[index - 1]; }\n    }\n    return part\n  }\n\n  function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {\n    // In a wrapped line, rtl text on wrapping boundaries can do things\n    // that don't correspond to the ordering in our `order` array at\n    // all, so a binary search doesn't work, and we want to return a\n    // part that only spans one line so that the binary search in\n    // coordsCharInner is safe. As such, we first find the extent of the\n    // wrapped line, and then do a flat search in which we discard any\n    // spans that aren't on the line.\n    var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);\n    var begin = ref.begin;\n    var end = ref.end;\n    if (/\\s/.test(lineObj.text.charAt(end - 1))) { end--; }\n    var part = null, closestDist = null;\n    for (var i = 0; i < order.length; i++) {\n      var p = order[i];\n      if (p.from >= end || p.to <= begin) { continue }\n      var ltr = p.level != 1;\n      var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right;\n      // Weigh against spans ending before this, so that they are only\n      // picked if nothing ends after\n      var dist = endX < x ? x - endX + 1e9 : endX - x;\n      if (!part || closestDist > dist) {\n        part = p;\n        closestDist = dist;\n      }\n    }\n    if (!part) { part = order[order.length - 1]; }\n    // Clip the part to the wrapped line.\n    if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; }\n    if (part.to > end) { part = {from: part.from, to: end, level: part.level}; }\n    return part\n  }\n\n  var measureText;\n  // Compute the default text height.\n  function textHeight(display) {\n    if (display.cachedTextHeight != null) { return display.cachedTextHeight }\n    if (measureText == null) {\n      measureText = elt(\"pre\", null, \"CodeMirror-line-like\");\n      // Measure a bunch of lines, for browsers that compute\n      // fractional heights.\n      for (var i = 0; i < 49; ++i) {\n        measureText.appendChild(document.createTextNode(\"x\"));\n        measureText.appendChild(elt(\"br\"));\n      }\n      measureText.appendChild(document.createTextNode(\"x\"));\n    }\n    removeChildrenAndAdd(display.measure, measureText);\n    var height = measureText.offsetHeight / 50;\n    if (height > 3) { display.cachedTextHeight = height; }\n    removeChildren(display.measure);\n    return height || 1\n  }\n\n  // Compute the default character width.\n  function charWidth(display) {\n    if (display.cachedCharWidth != null) { return display.cachedCharWidth }\n    var anchor = elt(\"span\", \"xxxxxxxxxx\");\n    var pre = elt(\"pre\", [anchor], \"CodeMirror-line-like\");\n    removeChildrenAndAdd(display.measure, pre);\n    var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;\n    if (width > 2) { display.cachedCharWidth = width; }\n    return width || 10\n  }\n\n  // Do a bulk-read of the DOM positions and sizes needed to draw the\n  // view, so that we don't interleave reading and writing to the DOM.\n  function getDimensions(cm) {\n    var d = cm.display, left = {}, width = {};\n    var gutterLeft = d.gutters.clientLeft;\n    for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {\n      var id = cm.display.gutterSpecs[i].className;\n      left[id] = n.offsetLeft + n.clientLeft + gutterLeft;\n      width[id] = n.clientWidth;\n    }\n    return {fixedPos: compensateForHScroll(d),\n            gutterTotalWidth: d.gutters.offsetWidth,\n            gutterLeft: left,\n            gutterWidth: width,\n            wrapperWidth: d.wrapper.clientWidth}\n  }\n\n  // Computes display.scroller.scrollLeft + display.gutters.offsetWidth,\n  // but using getBoundingClientRect to get a sub-pixel-accurate\n  // result.\n  function compensateForHScroll(display) {\n    return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left\n  }\n\n  // Returns a function that estimates the height of a line, to use as\n  // first approximation until the line becomes visible (and is thus\n  // properly measurable).\n  function estimateHeight(cm) {\n    var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;\n    var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);\n    return function (line) {\n      if (lineIsHidden(cm.doc, line)) { return 0 }\n\n      var widgetsHeight = 0;\n      if (line.widgets) { for (var i = 0; i < line.widgets.length; i++) {\n        if (line.widgets[i].height) { widgetsHeight += line.widgets[i].height; }\n      } }\n\n      if (wrapping)\n        { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }\n      else\n        { return widgetsHeight + th }\n    }\n  }\n\n  function estimateLineHeights(cm) {\n    var doc = cm.doc, est = estimateHeight(cm);\n    doc.iter(function (line) {\n      var estHeight = est(line);\n      if (estHeight != line.height) { updateLineHeight(line, estHeight); }\n    });\n  }\n\n  // Given a mouse event, find the corresponding position. If liberal\n  // is false, it checks whether a gutter or scrollbar was clicked,\n  // and returns null if it was. forRect is used by rectangular\n  // selections, and tries to estimate a character position even for\n  // coordinates beyond the right of the text.\n  function posFromMouse(cm, e, liberal, forRect) {\n    var display = cm.display;\n    if (!liberal && e_target(e).getAttribute(\"cm-not-content\") == \"true\") { return null }\n\n    var x, y, space = display.lineSpace.getBoundingClientRect();\n    // Fails unpredictably on IE[67] when mouse is dragged around quickly.\n    try { x = e.clientX - space.left; y = e.clientY - space.top; }\n    catch (e$1) { return null }\n    var coords = coordsChar(cm, x, y), line;\n    if (forRect && coords.xRel > 0 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) {\n      var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length;\n      coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff));\n    }\n    return coords\n  }\n\n  // Find the view element corresponding to a given line. Return null\n  // when the line isn't visible.\n  function findViewIndex(cm, n) {\n    if (n >= cm.display.viewTo) { return null }\n    n -= cm.display.viewFrom;\n    if (n < 0) { return null }\n    var view = cm.display.view;\n    for (var i = 0; i < view.length; i++) {\n      n -= view[i].size;\n      if (n < 0) { return i }\n    }\n  }\n\n  // Updates the display.view data structure for a given change to the\n  // document. From and to are in pre-change coordinates. Lendiff is\n  // the amount of lines added or subtracted by the change. This is\n  // used for changes that span multiple lines, or change the way\n  // lines are divided into visual lines. regLineChange (below)\n  // registers single-line changes.\n  function regChange(cm, from, to, lendiff) {\n    if (from == null) { from = cm.doc.first; }\n    if (to == null) { to = cm.doc.first + cm.doc.size; }\n    if (!lendiff) { lendiff = 0; }\n\n    var display = cm.display;\n    if (lendiff && to < display.viewTo &&\n        (display.updateLineNumbers == null || display.updateLineNumbers > from))\n      { display.updateLineNumbers = from; }\n\n    cm.curOp.viewChanged = true;\n\n    if (from >= display.viewTo) { // Change after\n      if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)\n        { resetView(cm); }\n    } else if (to <= display.viewFrom) { // Change before\n      if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {\n        resetView(cm);\n      } else {\n        display.viewFrom += lendiff;\n        display.viewTo += lendiff;\n      }\n    } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap\n      resetView(cm);\n    } else if (from <= display.viewFrom) { // Top overlap\n      var cut = viewCuttingPoint(cm, to, to + lendiff, 1);\n      if (cut) {\n        display.view = display.view.slice(cut.index);\n        display.viewFrom = cut.lineN;\n        display.viewTo += lendiff;\n      } else {\n        resetView(cm);\n      }\n    } else if (to >= display.viewTo) { // Bottom overlap\n      var cut$1 = viewCuttingPoint(cm, from, from, -1);\n      if (cut$1) {\n        display.view = display.view.slice(0, cut$1.index);\n        display.viewTo = cut$1.lineN;\n      } else {\n        resetView(cm);\n      }\n    } else { // Gap in the middle\n      var cutTop = viewCuttingPoint(cm, from, from, -1);\n      var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1);\n      if (cutTop && cutBot) {\n        display.view = display.view.slice(0, cutTop.index)\n          .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN))\n          .concat(display.view.slice(cutBot.index));\n        display.viewTo += lendiff;\n      } else {\n        resetView(cm);\n      }\n    }\n\n    var ext = display.externalMeasured;\n    if (ext) {\n      if (to < ext.lineN)\n        { ext.lineN += lendiff; }\n      else if (from < ext.lineN + ext.size)\n        { display.externalMeasured = null; }\n    }\n  }\n\n  // Register a change to a single line. Type must be one of \"text\",\n  // \"gutter\", \"class\", \"widget\"\n  function regLineChange(cm, line, type) {\n    cm.curOp.viewChanged = true;\n    var display = cm.display, ext = cm.display.externalMeasured;\n    if (ext && line >= ext.lineN && line < ext.lineN + ext.size)\n      { display.externalMeasured = null; }\n\n    if (line < display.viewFrom || line >= display.viewTo) { return }\n    var lineView = display.view[findViewIndex(cm, line)];\n    if (lineView.node == null) { return }\n    var arr = lineView.changes || (lineView.changes = []);\n    if (indexOf(arr, type) == -1) { arr.push(type); }\n  }\n\n  // Clear the view.\n  function resetView(cm) {\n    cm.display.viewFrom = cm.display.viewTo = cm.doc.first;\n    cm.display.view = [];\n    cm.display.viewOffset = 0;\n  }\n\n  function viewCuttingPoint(cm, oldN, newN, dir) {\n    var index = findViewIndex(cm, oldN), diff, view = cm.display.view;\n    if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size)\n      { return {index: index, lineN: newN} }\n    var n = cm.display.viewFrom;\n    for (var i = 0; i < index; i++)\n      { n += view[i].size; }\n    if (n != oldN) {\n      if (dir > 0) {\n        if (index == view.length - 1) { return null }\n        diff = (n + view[index].size) - oldN;\n        index++;\n      } else {\n        diff = n - oldN;\n      }\n      oldN += diff; newN += diff;\n    }\n    while (visualLineNo(cm.doc, newN) != newN) {\n      if (index == (dir < 0 ? 0 : view.length - 1)) { return null }\n      newN += dir * view[index - (dir < 0 ? 1 : 0)].size;\n      index += dir;\n    }\n    return {index: index, lineN: newN}\n  }\n\n  // Force the view to cover a given range, adding empty view element\n  // or clipping off existing ones as needed.\n  function adjustView(cm, from, to) {\n    var display = cm.display, view = display.view;\n    if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) {\n      display.view = buildViewArray(cm, from, to);\n      display.viewFrom = from;\n    } else {\n      if (display.viewFrom > from)\n        { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); }\n      else if (display.viewFrom < from)\n        { display.view = display.view.slice(findViewIndex(cm, from)); }\n      display.viewFrom = from;\n      if (display.viewTo < to)\n        { display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); }\n      else if (display.viewTo > to)\n        { display.view = display.view.slice(0, findViewIndex(cm, to)); }\n    }\n    display.viewTo = to;\n  }\n\n  // Count the number of lines in the view whose DOM representation is\n  // out of date (or nonexistent).\n  function countDirtyView(cm) {\n    var view = cm.display.view, dirty = 0;\n    for (var i = 0; i < view.length; i++) {\n      var lineView = view[i];\n      if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; }\n    }\n    return dirty\n  }\n\n  function updateSelection(cm) {\n    cm.display.input.showSelection(cm.display.input.prepareSelection());\n  }\n\n  function prepareSelection(cm, primary) {\n    if ( primary === void 0 ) primary = true;\n\n    var doc = cm.doc, result = {};\n    var curFragment = result.cursors = document.createDocumentFragment();\n    var selFragment = result.selection = document.createDocumentFragment();\n\n    for (var i = 0; i < doc.sel.ranges.length; i++) {\n      if (!primary && i == doc.sel.primIndex) { continue }\n      var range = doc.sel.ranges[i];\n      if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue }\n      var collapsed = range.empty();\n      if (collapsed || cm.options.showCursorWhenSelecting)\n        { drawSelectionCursor(cm, range.head, curFragment); }\n      if (!collapsed)\n        { drawSelectionRange(cm, range, selFragment); }\n    }\n    return result\n  }\n\n  // Draws a cursor for the given range\n  function drawSelectionCursor(cm, head, output) {\n    var pos = cursorCoords(cm, head, \"div\", null, null, !cm.options.singleCursorHeightPerLine);\n\n    var cursor = output.appendChild(elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor\"));\n    cursor.style.left = pos.left + \"px\";\n    cursor.style.top = pos.top + \"px\";\n    cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + \"px\";\n\n    if (pos.other) {\n      // Secondary cursor, shown when on a 'jump' in bi-directional text\n      var otherCursor = output.appendChild(elt(\"div\", \"\\u00a0\", \"CodeMirror-cursor CodeMirror-secondarycursor\"));\n      otherCursor.style.display = \"\";\n      otherCursor.style.left = pos.other.left + \"px\";\n      otherCursor.style.top = pos.other.top + \"px\";\n      otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + \"px\";\n    }\n  }\n\n  function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }\n\n  // Draws the given range as a highlighted selection\n  function drawSelectionRange(cm, range, output) {\n    var display = cm.display, doc = cm.doc;\n    var fragment = document.createDocumentFragment();\n    var padding = paddingH(cm.display), leftSide = padding.left;\n    var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;\n    var docLTR = doc.direction == \"ltr\";\n\n    function add(left, top, width, bottom) {\n      if (top < 0) { top = 0; }\n      top = Math.round(top);\n      bottom = Math.round(bottom);\n      fragment.appendChild(elt(\"div\", null, \"CodeMirror-selected\", (\"position: absolute; left: \" + left + \"px;\\n                             top: \" + top + \"px; width: \" + (width == null ? rightSide - left : width) + \"px;\\n                             height: \" + (bottom - top) + \"px\")));\n    }\n\n    function drawForLine(line, fromArg, toArg) {\n      var lineObj = getLine(doc, line);\n      var lineLen = lineObj.text.length;\n      var start, end;\n      function coords(ch, bias) {\n        return charCoords(cm, Pos(line, ch), \"div\", lineObj, bias)\n      }\n\n      function wrapX(pos, dir, side) {\n        var extent = wrappedLineExtentChar(cm, lineObj, null, pos);\n        var prop = (dir == \"ltr\") == (side == \"after\") ? \"left\" : \"right\";\n        var ch = side == \"after\" ? extent.begin : extent.end - (/\\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1);\n        return coords(ch, prop)[prop]\n      }\n\n      var order = getOrder(lineObj, doc.direction);\n      iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {\n        var ltr = dir == \"ltr\";\n        var fromPos = coords(from, ltr ? \"left\" : \"right\");\n        var toPos = coords(to - 1, ltr ? \"right\" : \"left\");\n\n        var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen;\n        var first = i == 0, last = !order || i == order.length - 1;\n        if (toPos.top - fromPos.top <= 3) { // Single line\n          var openLeft = (docLTR ? openStart : openEnd) && first;\n          var openRight = (docLTR ? openEnd : openStart) && last;\n          var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left;\n          var right = openRight ? rightSide : (ltr ? toPos : fromPos).right;\n          add(left, fromPos.top, right - left, fromPos.bottom);\n        } else { // Multiple lines\n          var topLeft, topRight, botLeft, botRight;\n          if (ltr) {\n            topLeft = docLTR && openStart && first ? leftSide : fromPos.left;\n            topRight = docLTR ? rightSide : wrapX(from, dir, \"before\");\n            botLeft = docLTR ? leftSide : wrapX(to, dir, \"after\");\n            botRight = docLTR && openEnd && last ? rightSide : toPos.right;\n          } else {\n            topLeft = !docLTR ? leftSide : wrapX(from, dir, \"before\");\n            topRight = !docLTR && openStart && first ? rightSide : fromPos.right;\n            botLeft = !docLTR && openEnd && last ? leftSide : toPos.left;\n            botRight = !docLTR ? rightSide : wrapX(to, dir, \"after\");\n          }\n          add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom);\n          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); }\n          add(botLeft, toPos.top, botRight - botLeft, toPos.bottom);\n        }\n\n        if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; }\n        if (cmpCoords(toPos, start) < 0) { start = toPos; }\n        if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; }\n        if (cmpCoords(toPos, end) < 0) { end = toPos; }\n      });\n      return {start: start, end: end}\n    }\n\n    var sFrom = range.from(), sTo = range.to();\n    if (sFrom.line == sTo.line) {\n      drawForLine(sFrom.line, sFrom.ch, sTo.ch);\n    } else {\n      var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line);\n      var singleVLine = visualLine(fromLine) == visualLine(toLine);\n      var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end;\n      var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start;\n      if (singleVLine) {\n        if (leftEnd.top < rightStart.top - 2) {\n          add(leftEnd.right, leftEnd.top, null, leftEnd.bottom);\n          add(leftSide, rightStart.top, rightStart.left, rightStart.bottom);\n        } else {\n          add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom);\n        }\n      }\n      if (leftEnd.bottom < rightStart.top)\n        { add(leftSide, leftEnd.bottom, null, rightStart.top); }\n    }\n\n    output.appendChild(fragment);\n  }\n\n  // Cursor-blinking\n  function restartBlink(cm) {\n    if (!cm.state.focused) { return }\n    var display = cm.display;\n    clearInterval(display.blinker);\n    var on = true;\n    display.cursorDiv.style.visibility = \"\";\n    if (cm.options.cursorBlinkRate > 0)\n      { display.blinker = setInterval(function () { return display.cursorDiv.style.visibility = (on = !on) ? \"\" : \"hidden\"; },\n        cm.options.cursorBlinkRate); }\n    else if (cm.options.cursorBlinkRate < 0)\n      { display.cursorDiv.style.visibility = \"hidden\"; }\n  }\n\n  function ensureFocus(cm) {\n    if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }\n  }\n\n  function delayBlurEvent(cm) {\n    cm.state.delayingBlurEvent = true;\n    setTimeout(function () { if (cm.state.delayingBlurEvent) {\n      cm.state.delayingBlurEvent = false;\n      onBlur(cm);\n    } }, 100);\n  }\n\n  function onFocus(cm, e) {\n    if (cm.state.delayingBlurEvent) { cm.state.delayingBlurEvent = false; }\n\n    if (cm.options.readOnly == \"nocursor\") { return }\n    if (!cm.state.focused) {\n      signal(cm, \"focus\", cm, e);\n      cm.state.focused = true;\n      addClass(cm.display.wrapper, \"CodeMirror-focused\");\n      // This test prevents this from firing when a context\n      // menu is closed (since the input reset would kill the\n      // select-all detection hack)\n      if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) {\n        cm.display.input.reset();\n        if (webkit) { setTimeout(function () { return cm.display.input.reset(true); }, 20); } // Issue #1730\n      }\n      cm.display.input.receivedFocus();\n    }\n    restartBlink(cm);\n  }\n  function onBlur(cm, e) {\n    if (cm.state.delayingBlurEvent) { return }\n\n    if (cm.state.focused) {\n      signal(cm, \"blur\", cm, e);\n      cm.state.focused = false;\n      rmClass(cm.display.wrapper, \"CodeMirror-focused\");\n    }\n    clearInterval(cm.display.blinker);\n    setTimeout(function () { if (!cm.state.focused) { cm.display.shift = false; } }, 150);\n  }\n\n  // Read the actual heights of the rendered lines, and update their\n  // stored heights to match.\n  function updateHeightsInViewport(cm) {\n    var display = cm.display;\n    var prevBottom = display.lineDiv.offsetTop;\n    for (var i = 0; i < display.view.length; i++) {\n      var cur = display.view[i], wrapping = cm.options.lineWrapping;\n      var height = (void 0), width = 0;\n      if (cur.hidden) { continue }\n      if (ie && ie_version < 8) {\n        var bot = cur.node.offsetTop + cur.node.offsetHeight;\n        height = bot - prevBottom;\n        prevBottom = bot;\n      } else {\n        var box = cur.node.getBoundingClientRect();\n        height = box.bottom - box.top;\n        // Check that lines don't extend past the right of the current\n        // editor width\n        if (!wrapping && cur.text.firstChild)\n          { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; }\n      }\n      var diff = cur.line.height - height;\n      if (diff > .005 || diff < -.005) {\n        updateLineHeight(cur.line, height);\n        updateWidgetHeight(cur.line);\n        if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)\n          { updateWidgetHeight(cur.rest[j]); } }\n      }\n      if (width > cm.display.sizerWidth) {\n        var chWidth = Math.ceil(width / charWidth(cm.display));\n        if (chWidth > cm.display.maxLineLength) {\n          cm.display.maxLineLength = chWidth;\n          cm.display.maxLine = cur.line;\n          cm.display.maxLineChanged = true;\n        }\n      }\n    }\n  }\n\n  // Read and store the height of line widgets associated with the\n  // given line.\n  function updateWidgetHeight(line) {\n    if (line.widgets) { for (var i = 0; i < line.widgets.length; ++i) {\n      var w = line.widgets[i], parent = w.node.parentNode;\n      if (parent) { w.height = parent.offsetHeight; }\n    } }\n  }\n\n  // Compute the lines that are visible in a given viewport (defaults\n  // the the current scroll position). viewport may contain top,\n  // height, and ensure (see op.scrollToPos) properties.\n  function visibleLines(display, doc, viewport) {\n    var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop;\n    top = Math.floor(top - paddingTop(display));\n    var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight;\n\n    var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom);\n    // Ensure is a {from: {line, ch}, to: {line, ch}} object, and\n    // forces those lines into the viewport (if possible).\n    if (viewport && viewport.ensure) {\n      var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line;\n      if (ensureFrom < from) {\n        from = ensureFrom;\n        to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight);\n      } else if (Math.min(ensureTo, doc.lastLine()) >= to) {\n        from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight);\n        to = ensureTo;\n      }\n    }\n    return {from: from, to: Math.max(to, from + 1)}\n  }\n\n  // SCROLLING THINGS INTO VIEW\n\n  // If an editor sits on the top or bottom of the window, partially\n  // scrolled out of view, this ensures that the cursor is visible.\n  function maybeScrollWindow(cm, rect) {\n    if (signalDOMEvent(cm, \"scrollCursorIntoView\")) { return }\n\n    var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;\n    if (rect.top + box.top < 0) { doScroll = true; }\n    else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; }\n    if (doScroll != null && !phantom) {\n      var scrollNode = elt(\"div\", \"\\u200b\", null, (\"position: absolute;\\n                         top: \" + (rect.top - display.viewOffset - paddingTop(cm.display)) + \"px;\\n                         height: \" + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + \"px;\\n                         left: \" + (rect.left) + \"px; width: \" + (Math.max(2, rect.right - rect.left)) + \"px;\"));\n      cm.display.lineSpace.appendChild(scrollNode);\n      scrollNode.scrollIntoView(doScroll);\n      cm.display.lineSpace.removeChild(scrollNode);\n    }\n  }\n\n  // Scroll a given position into view (immediately), verifying that\n  // it actually became visible (as line heights are accurately\n  // measured, the position of something may 'drift' during drawing).\n  function scrollPosIntoView(cm, pos, end, margin) {\n    if (margin == null) { margin = 0; }\n    var rect;\n    if (!cm.options.lineWrapping && pos == end) {\n      // Set pos and end to the cursor positions around the character pos sticks to\n      // If pos.sticky == \"before\", that is around pos.ch - 1, otherwise around pos.ch\n      // If pos == Pos(_, 0, \"before\"), pos and end are unchanged\n      pos = pos.ch ? Pos(pos.line, pos.sticky == \"before\" ? pos.ch - 1 : pos.ch, \"after\") : pos;\n      end = pos.sticky == \"before\" ? Pos(pos.line, pos.ch + 1, \"before\") : pos;\n    }\n    for (var limit = 0; limit < 5; limit++) {\n      var changed = false;\n      var coords = cursorCoords(cm, pos);\n      var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);\n      rect = {left: Math.min(coords.left, endCoords.left),\n              top: Math.min(coords.top, endCoords.top) - margin,\n              right: Math.max(coords.left, endCoords.left),\n              bottom: Math.max(coords.bottom, endCoords.bottom) + margin};\n      var scrollPos = calculateScrollPos(cm, rect);\n      var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;\n      if (scrollPos.scrollTop != null) {\n        updateScrollTop(cm, scrollPos.scrollTop);\n        if (Math.abs(cm.doc.scrollTop - startTop) > 1) { changed = true; }\n      }\n      if (scrollPos.scrollLeft != null) {\n        setScrollLeft(cm, scrollPos.scrollLeft);\n        if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; }\n      }\n      if (!changed) { break }\n    }\n    return rect\n  }\n\n  // Scroll a given set of coordinates into view (immediately).\n  function scrollIntoView(cm, rect) {\n    var scrollPos = calculateScrollPos(cm, rect);\n    if (scrollPos.scrollTop != null) { updateScrollTop(cm, scrollPos.scrollTop); }\n    if (scrollPos.scrollLeft != null) { setScrollLeft(cm, scrollPos.scrollLeft); }\n  }\n\n  // Calculate a new scroll position needed to scroll the given\n  // rectangle into view. Returns an object with scrollTop and\n  // scrollLeft properties. When these are undefined, the\n  // vertical/horizontal position does not need to be adjusted.\n  function calculateScrollPos(cm, rect) {\n    var display = cm.display, snapMargin = textHeight(cm.display);\n    if (rect.top < 0) { rect.top = 0; }\n    var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;\n    var screen = displayHeight(cm), result = {};\n    if (rect.bottom - rect.top > screen) { rect.bottom = rect.top + screen; }\n    var docBottom = cm.doc.height + paddingVert(display);\n    var atTop = rect.top < snapMargin, atBottom = rect.bottom > docBottom - snapMargin;\n    if (rect.top < screentop) {\n      result.scrollTop = atTop ? 0 : rect.top;\n    } else if (rect.bottom > screentop + screen) {\n      var newTop = Math.min(rect.top, (atBottom ? docBottom : rect.bottom) - screen);\n      if (newTop != screentop) { result.scrollTop = newTop; }\n    }\n\n    var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;\n    var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);\n    var tooWide = rect.right - rect.left > screenw;\n    if (tooWide) { rect.right = rect.left + screenw; }\n    if (rect.left < 10)\n      { result.scrollLeft = 0; }\n    else if (rect.left < screenleft)\n      { result.scrollLeft = Math.max(0, rect.left - (tooWide ? 0 : 10)); }\n    else if (rect.right > screenw + screenleft - 3)\n      { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; }\n    return result\n  }\n\n  // Store a relative adjustment to the scroll position in the current\n  // operation (to be applied when the operation finishes).\n  function addToScrollTop(cm, top) {\n    if (top == null) { return }\n    resolveScrollToPos(cm);\n    cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top;\n  }\n\n  // Make sure that at the end of the operation the current cursor is\n  // shown.\n  function ensureCursorVisible(cm) {\n    resolveScrollToPos(cm);\n    var cur = cm.getCursor();\n    cm.curOp.scrollToPos = {from: cur, to: cur, margin: cm.options.cursorScrollMargin};\n  }\n\n  function scrollToCoords(cm, x, y) {\n    if (x != null || y != null) { resolveScrollToPos(cm); }\n    if (x != null) { cm.curOp.scrollLeft = x; }\n    if (y != null) { cm.curOp.scrollTop = y; }\n  }\n\n  function scrollToRange(cm, range) {\n    resolveScrollToPos(cm);\n    cm.curOp.scrollToPos = range;\n  }\n\n  // When an operation has its scrollToPos property set, and another\n  // scroll action is applied before the end of the operation, this\n  // 'simulates' scrolling that position into view in a cheap way, so\n  // that the effect of intermediate scroll commands is not ignored.\n  function resolveScrollToPos(cm) {\n    var range = cm.curOp.scrollToPos;\n    if (range) {\n      cm.curOp.scrollToPos = null;\n      var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to);\n      scrollToCoordsRange(cm, from, to, range.margin);\n    }\n  }\n\n  function scrollToCoordsRange(cm, from, to, margin) {\n    var sPos = calculateScrollPos(cm, {\n      left: Math.min(from.left, to.left),\n      top: Math.min(from.top, to.top) - margin,\n      right: Math.max(from.right, to.right),\n      bottom: Math.max(from.bottom, to.bottom) + margin\n    });\n    scrollToCoords(cm, sPos.scrollLeft, sPos.scrollTop);\n  }\n\n  // Sync the scrollable area and scrollbars, ensure the viewport\n  // covers the visible area.\n  function updateScrollTop(cm, val) {\n    if (Math.abs(cm.doc.scrollTop - val) < 2) { return }\n    if (!gecko) { updateDisplaySimple(cm, {top: val}); }\n    setScrollTop(cm, val, true);\n    if (gecko) { updateDisplaySimple(cm); }\n    startWorker(cm, 100);\n  }\n\n  function setScrollTop(cm, val, forceScroll) {\n    val = Math.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val));\n    if (cm.display.scroller.scrollTop == val && !forceScroll) { return }\n    cm.doc.scrollTop = val;\n    cm.display.scrollbars.setScrollTop(val);\n    if (cm.display.scroller.scrollTop != val) { cm.display.scroller.scrollTop = val; }\n  }\n\n  // Sync scroller and scrollbar, ensure the gutter elements are\n  // aligned.\n  function setScrollLeft(cm, val, isScroller, forceScroll) {\n    val = Math.max(0, Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth));\n    if ((isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) && !forceScroll) { return }\n    cm.doc.scrollLeft = val;\n    alignHorizontally(cm);\n    if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; }\n    cm.display.scrollbars.setScrollLeft(val);\n  }\n\n  // SCROLLBARS\n\n  // Prepare DOM reads needed to update the scrollbars. Done in one\n  // shot to minimize update/measure roundtrips.\n  function measureForScrollbars(cm) {\n    var d = cm.display, gutterW = d.gutters.offsetWidth;\n    var docH = Math.round(cm.doc.height + paddingVert(cm.display));\n    return {\n      clientHeight: d.scroller.clientHeight,\n      viewHeight: d.wrapper.clientHeight,\n      scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth,\n      viewWidth: d.wrapper.clientWidth,\n      barLeft: cm.options.fixedGutter ? gutterW : 0,\n      docHeight: docH,\n      scrollHeight: docH + scrollGap(cm) + d.barHeight,\n      nativeBarWidth: d.nativeBarWidth,\n      gutterWidth: gutterW\n    }\n  }\n\n  var NativeScrollbars = function(place, scroll, cm) {\n    this.cm = cm;\n    var vert = this.vert = elt(\"div\", [elt(\"div\", null, null, \"min-width: 1px\")], \"CodeMirror-vscrollbar\");\n    var horiz = this.horiz = elt(\"div\", [elt(\"div\", null, null, \"height: 100%; min-height: 1px\")], \"CodeMirror-hscrollbar\");\n    vert.tabIndex = horiz.tabIndex = -1;\n    place(vert); place(horiz);\n\n    on(vert, \"scroll\", function () {\n      if (vert.clientHeight) { scroll(vert.scrollTop, \"vertical\"); }\n    });\n    on(horiz, \"scroll\", function () {\n      if (horiz.clientWidth) { scroll(horiz.scrollLeft, \"horizontal\"); }\n    });\n\n    this.checkedZeroWidth = false;\n    // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).\n    if (ie && ie_version < 8) { this.horiz.style.minHeight = this.vert.style.minWidth = \"18px\"; }\n  };\n\n  NativeScrollbars.prototype.update = function (measure) {\n    var needsH = measure.scrollWidth > measure.clientWidth + 1;\n    var needsV = measure.scrollHeight > measure.clientHeight + 1;\n    var sWidth = measure.nativeBarWidth;\n\n    if (needsV) {\n      this.vert.style.display = \"block\";\n      this.vert.style.bottom = needsH ? sWidth + \"px\" : \"0\";\n      var totalHeight = measure.viewHeight - (needsH ? sWidth : 0);\n      // A bug in IE8 can cause this value to be negative, so guard it.\n      this.vert.firstChild.style.height =\n        Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + \"px\";\n    } else {\n      this.vert.style.display = \"\";\n      this.vert.firstChild.style.height = \"0\";\n    }\n\n    if (needsH) {\n      this.horiz.style.display = \"block\";\n      this.horiz.style.right = needsV ? sWidth + \"px\" : \"0\";\n      this.horiz.style.left = measure.barLeft + \"px\";\n      var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0);\n      this.horiz.firstChild.style.width =\n        Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + \"px\";\n    } else {\n      this.horiz.style.display = \"\";\n      this.horiz.firstChild.style.width = \"0\";\n    }\n\n    if (!this.checkedZeroWidth && measure.clientHeight > 0) {\n      if (sWidth == 0) { this.zeroWidthHack(); }\n      this.checkedZeroWidth = true;\n    }\n\n    return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}\n  };\n\n  NativeScrollbars.prototype.setScrollLeft = function (pos) {\n    if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; }\n    if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, \"horiz\"); }\n  };\n\n  NativeScrollbars.prototype.setScrollTop = function (pos) {\n    if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; }\n    if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, \"vert\"); }\n  };\n\n  NativeScrollbars.prototype.zeroWidthHack = function () {\n    var w = mac && !mac_geMountainLion ? \"12px\" : \"18px\";\n    this.horiz.style.height = this.vert.style.width = w;\n    this.horiz.style.pointerEvents = this.vert.style.pointerEvents = \"none\";\n    this.disableHoriz = new Delayed;\n    this.disableVert = new Delayed;\n  };\n\n  NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {\n    bar.style.pointerEvents = \"auto\";\n    function maybeDisable() {\n      // To find out whether the scrollbar is still visible, we\n      // check whether the element under the pixel in the bottom\n      // right corner of the scrollbar box is the scrollbar box\n      // itself (when the bar is still visible) or its filler child\n      // (when the bar is hidden). If it is still visible, we keep\n      // it enabled, if it's hidden, we disable pointer events.\n      var box = bar.getBoundingClientRect();\n      var elt = type == \"vert\" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)\n          : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1);\n      if (elt != bar) { bar.style.pointerEvents = \"none\"; }\n      else { delay.set(1000, maybeDisable); }\n    }\n    delay.set(1000, maybeDisable);\n  };\n\n  NativeScrollbars.prototype.clear = function () {\n    var parent = this.horiz.parentNode;\n    parent.removeChild(this.horiz);\n    parent.removeChild(this.vert);\n  };\n\n  var NullScrollbars = function () {};\n\n  NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };\n  NullScrollbars.prototype.setScrollLeft = function () {};\n  NullScrollbars.prototype.setScrollTop = function () {};\n  NullScrollbars.prototype.clear = function () {};\n\n  function updateScrollbars(cm, measure) {\n    if (!measure) { measure = measureForScrollbars(cm); }\n    var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight;\n    updateScrollbarsInner(cm, measure);\n    for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) {\n      if (startWidth != cm.display.barWidth && cm.options.lineWrapping)\n        { updateHeightsInViewport(cm); }\n      updateScrollbarsInner(cm, measureForScrollbars(cm));\n      startWidth = cm.display.barWidth; startHeight = cm.display.barHeight;\n    }\n  }\n\n  // Re-synchronize the fake scrollbars with the actual size of the\n  // content.\n  function updateScrollbarsInner(cm, measure) {\n    var d = cm.display;\n    var sizes = d.scrollbars.update(measure);\n\n    d.sizer.style.paddingRight = (d.barWidth = sizes.right) + \"px\";\n    d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + \"px\";\n    d.heightForcer.style.borderBottom = sizes.bottom + \"px solid transparent\";\n\n    if (sizes.right && sizes.bottom) {\n      d.scrollbarFiller.style.display = \"block\";\n      d.scrollbarFiller.style.height = sizes.bottom + \"px\";\n      d.scrollbarFiller.style.width = sizes.right + \"px\";\n    } else { d.scrollbarFiller.style.display = \"\"; }\n    if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {\n      d.gutterFiller.style.display = \"block\";\n      d.gutterFiller.style.height = sizes.bottom + \"px\";\n      d.gutterFiller.style.width = measure.gutterWidth + \"px\";\n    } else { d.gutterFiller.style.display = \"\"; }\n  }\n\n  var scrollbarModel = {\"native\": NativeScrollbars, \"null\": NullScrollbars};\n\n  function initScrollbars(cm) {\n    if (cm.display.scrollbars) {\n      cm.display.scrollbars.clear();\n      if (cm.display.scrollbars.addClass)\n        { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); }\n    }\n\n    cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](function (node) {\n      cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller);\n      // Prevent clicks in the scrollbars from killing focus\n      on(node, \"mousedown\", function () {\n        if (cm.state.focused) { setTimeout(function () { return cm.display.input.focus(); }, 0); }\n      });\n      node.setAttribute(\"cm-not-content\", \"true\");\n    }, function (pos, axis) {\n      if (axis == \"horizontal\") { setScrollLeft(cm, pos); }\n      else { updateScrollTop(cm, pos); }\n    }, cm);\n    if (cm.display.scrollbars.addClass)\n      { addClass(cm.display.wrapper, cm.display.scrollbars.addClass); }\n  }\n\n  // Operations are used to wrap a series of changes to the editor\n  // state in such a way that each change won't have to update the\n  // cursor and display (which would be awkward, slow, and\n  // error-prone). Instead, display updates are batched and then all\n  // combined and executed at once.\n\n  var nextOpId = 0;\n  // Start a new operation.\n  function startOperation(cm) {\n    cm.curOp = {\n      cm: cm,\n      viewChanged: false,      // Flag that indicates that lines might need to be redrawn\n      startHeight: cm.doc.height, // Used to detect need to update scrollbar\n      forceUpdate: false,      // Used to force a redraw\n      updateInput: 0,       // Whether to reset the input textarea\n      typing: false,           // Whether this reset should be careful to leave existing text (for compositing)\n      changeObjs: null,        // Accumulated changes, for firing change events\n      cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on\n      cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already\n      selectionChanged: false, // Whether the selection needs to be redrawn\n      updateMaxLine: false,    // Set when the widest line needs to be determined anew\n      scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet\n      scrollToPos: null,       // Used to scroll to a specific position\n      focus: false,\n      id: ++nextOpId           // Unique ID\n    };\n    pushOperation(cm.curOp);\n  }\n\n  // Finish an operation, updating the display and signalling delayed events\n  function endOperation(cm) {\n    var op = cm.curOp;\n    if (op) { finishOperation(op, function (group) {\n      for (var i = 0; i < group.ops.length; i++)\n        { group.ops[i].cm.curOp = null; }\n      endOperations(group);\n    }); }\n  }\n\n  // The DOM updates done when an operation finishes are batched so\n  // that the minimum number of relayouts are required.\n  function endOperations(group) {\n    var ops = group.ops;\n    for (var i = 0; i < ops.length; i++) // Read DOM\n      { endOperation_R1(ops[i]); }\n    for (var i$1 = 0; i$1 < ops.length; i$1++) // Write DOM (maybe)\n      { endOperation_W1(ops[i$1]); }\n    for (var i$2 = 0; i$2 < ops.length; i$2++) // Read DOM\n      { endOperation_R2(ops[i$2]); }\n    for (var i$3 = 0; i$3 < ops.length; i$3++) // Write DOM (maybe)\n      { endOperation_W2(ops[i$3]); }\n    for (var i$4 = 0; i$4 < ops.length; i$4++) // Read DOM\n      { endOperation_finish(ops[i$4]); }\n  }\n\n  function endOperation_R1(op) {\n    var cm = op.cm, display = cm.display;\n    maybeClipScrollbars(cm);\n    if (op.updateMaxLine) { findMaxLine(cm); }\n\n    op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null ||\n      op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom ||\n                         op.scrollToPos.to.line >= display.viewTo) ||\n      display.maxLineChanged && cm.options.lineWrapping;\n    op.update = op.mustUpdate &&\n      new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate);\n  }\n\n  function endOperation_W1(op) {\n    op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);\n  }\n\n  function endOperation_R2(op) {\n    var cm = op.cm, display = cm.display;\n    if (op.updatedDisplay) { updateHeightsInViewport(cm); }\n\n    op.barMeasure = measureForScrollbars(cm);\n\n    // If the max line changed since it was last measured, measure it,\n    // and ensure the document's width matches it.\n    // updateDisplay_W2 will use these properties to do the actual resizing\n    if (display.maxLineChanged && !cm.options.lineWrapping) {\n      op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3;\n      cm.display.sizerWidth = op.adjustWidthTo;\n      op.barMeasure.scrollWidth =\n        Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth);\n      op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm));\n    }\n\n    if (op.updatedDisplay || op.selectionChanged)\n      { op.preparedSelection = display.input.prepareSelection(); }\n  }\n\n  function endOperation_W2(op) {\n    var cm = op.cm;\n\n    if (op.adjustWidthTo != null) {\n      cm.display.sizer.style.minWidth = op.adjustWidthTo + \"px\";\n      if (op.maxScrollLeft < cm.doc.scrollLeft)\n        { setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); }\n      cm.display.maxLineChanged = false;\n    }\n\n    var takeFocus = op.focus && op.focus == activeElt();\n    if (op.preparedSelection)\n      { cm.display.input.showSelection(op.preparedSelection, takeFocus); }\n    if (op.updatedDisplay || op.startHeight != cm.doc.height)\n      { updateScrollbars(cm, op.barMeasure); }\n    if (op.updatedDisplay)\n      { setDocumentHeight(cm, op.barMeasure); }\n\n    if (op.selectionChanged) { restartBlink(cm); }\n\n    if (cm.state.focused && op.updateInput)\n      { cm.display.input.reset(op.typing); }\n    if (takeFocus) { ensureFocus(op.cm); }\n  }\n\n  function endOperation_finish(op) {\n    var cm = op.cm, display = cm.display, doc = cm.doc;\n\n    if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); }\n\n    // Abort mouse wheel delta measurement, when scrolling explicitly\n    if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos))\n      { display.wheelStartX = display.wheelStartY = null; }\n\n    // Propagate the scroll position to the actual DOM scroller\n    if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); }\n\n    if (op.scrollLeft != null) { setScrollLeft(cm, op.scrollLeft, true, true); }\n    // If we need to scroll a specific position into view, do so.\n    if (op.scrollToPos) {\n      var rect = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),\n                                   clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);\n      maybeScrollWindow(cm, rect);\n    }\n\n    // Fire events for markers that are hidden/unidden by editing or\n    // undoing\n    var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;\n    if (hidden) { for (var i = 0; i < hidden.length; ++i)\n      { if (!hidden[i].lines.length) { signal(hidden[i], \"hide\"); } } }\n    if (unhidden) { for (var i$1 = 0; i$1 < unhidden.length; ++i$1)\n      { if (unhidden[i$1].lines.length) { signal(unhidden[i$1], \"unhide\"); } } }\n\n    if (display.wrapper.offsetHeight)\n      { doc.scrollTop = cm.display.scroller.scrollTop; }\n\n    // Fire change events, and delayed event handlers\n    if (op.changeObjs)\n      { signal(cm, \"changes\", cm, op.changeObjs); }\n    if (op.update)\n      { op.update.finish(); }\n  }\n\n  // Run the given function in an operation\n  function runInOp(cm, f) {\n    if (cm.curOp) { return f() }\n    startOperation(cm);\n    try { return f() }\n    finally { endOperation(cm); }\n  }\n  // Wraps a function in an operation. Returns the wrapped function.\n  function operation(cm, f) {\n    return function() {\n      if (cm.curOp) { return f.apply(cm, arguments) }\n      startOperation(cm);\n      try { return f.apply(cm, arguments) }\n      finally { endOperation(cm); }\n    }\n  }\n  // Used to add methods to editor and doc instances, wrapping them in\n  // operations.\n  function methodOp(f) {\n    return function() {\n      if (this.curOp) { return f.apply(this, arguments) }\n      startOperation(this);\n      try { return f.apply(this, arguments) }\n      finally { endOperation(this); }\n    }\n  }\n  function docMethodOp(f) {\n    return function() {\n      var cm = this.cm;\n      if (!cm || cm.curOp) { return f.apply(this, arguments) }\n      startOperation(cm);\n      try { return f.apply(this, arguments) }\n      finally { endOperation(cm); }\n    }\n  }\n\n  // HIGHLIGHT WORKER\n\n  function startWorker(cm, time) {\n    if (cm.doc.highlightFrontier < cm.display.viewTo)\n      { cm.state.highlight.set(time, bind(highlightWorker, cm)); }\n  }\n\n  function highlightWorker(cm) {\n    var doc = cm.doc;\n    if (doc.highlightFrontier >= cm.display.viewTo) { return }\n    var end = +new Date + cm.options.workTime;\n    var context = getContextBefore(cm, doc.highlightFrontier);\n    var changedLines = [];\n\n    doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function (line) {\n      if (context.line >= cm.display.viewFrom) { // Visible\n        var oldStyles = line.styles;\n        var resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null;\n        var highlighted = highlightLine(cm, line, context, true);\n        if (resetState) { context.state = resetState; }\n        line.styles = highlighted.styles;\n        var oldCls = line.styleClasses, newCls = highlighted.classes;\n        if (newCls) { line.styleClasses = newCls; }\n        else if (oldCls) { line.styleClasses = null; }\n        var ischange = !oldStyles || oldStyles.length != line.styles.length ||\n          oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);\n        for (var i = 0; !ischange && i < oldStyles.length; ++i) { ischange = oldStyles[i] != line.styles[i]; }\n        if (ischange) { changedLines.push(context.line); }\n        line.stateAfter = context.save();\n        context.nextLine();\n      } else {\n        if (line.text.length <= cm.options.maxHighlightLength)\n          { processLine(cm, line.text, context); }\n        line.stateAfter = context.line % 5 == 0 ? context.save() : null;\n        context.nextLine();\n      }\n      if (+new Date > end) {\n        startWorker(cm, cm.options.workDelay);\n        return true\n      }\n    });\n    doc.highlightFrontier = context.line;\n    doc.modeFrontier = Math.max(doc.modeFrontier, context.line);\n    if (changedLines.length) { runInOp(cm, function () {\n      for (var i = 0; i < changedLines.length; i++)\n        { regLineChange(cm, changedLines[i], \"text\"); }\n    }); }\n  }\n\n  // DISPLAY DRAWING\n\n  var DisplayUpdate = function(cm, viewport, force) {\n    var display = cm.display;\n\n    this.viewport = viewport;\n    // Store some values that we'll need later (but don't want to force a relayout for)\n    this.visible = visibleLines(display, cm.doc, viewport);\n    this.editorIsHidden = !display.wrapper.offsetWidth;\n    this.wrapperHeight = display.wrapper.clientHeight;\n    this.wrapperWidth = display.wrapper.clientWidth;\n    this.oldDisplayWidth = displayWidth(cm);\n    this.force = force;\n    this.dims = getDimensions(cm);\n    this.events = [];\n  };\n\n  DisplayUpdate.prototype.signal = function (emitter, type) {\n    if (hasHandler(emitter, type))\n      { this.events.push(arguments); }\n  };\n  DisplayUpdate.prototype.finish = function () {\n    for (var i = 0; i < this.events.length; i++)\n      { signal.apply(null, this.events[i]); }\n  };\n\n  function maybeClipScrollbars(cm) {\n    var display = cm.display;\n    if (!display.scrollbarsClipped && display.scroller.offsetWidth) {\n      display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth;\n      display.heightForcer.style.height = scrollGap(cm) + \"px\";\n      display.sizer.style.marginBottom = -display.nativeBarWidth + \"px\";\n      display.sizer.style.borderRightWidth = scrollGap(cm) + \"px\";\n      display.scrollbarsClipped = true;\n    }\n  }\n\n  function selectionSnapshot(cm) {\n    if (cm.hasFocus()) { return null }\n    var active = activeElt();\n    if (!active || !contains(cm.display.lineDiv, active)) { return null }\n    var result = {activeElt: active};\n    if (window.getSelection) {\n      var sel = window.getSelection();\n      if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {\n        result.anchorNode = sel.anchorNode;\n        result.anchorOffset = sel.anchorOffset;\n        result.focusNode = sel.focusNode;\n        result.focusOffset = sel.focusOffset;\n      }\n    }\n    return result\n  }\n\n  function restoreSelection(snapshot) {\n    if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }\n    snapshot.activeElt.focus();\n    if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&\n        snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {\n      var sel = window.getSelection(), range = document.createRange();\n      range.setEnd(snapshot.anchorNode, snapshot.anchorOffset);\n      range.collapse(false);\n      sel.removeAllRanges();\n      sel.addRange(range);\n      sel.extend(snapshot.focusNode, snapshot.focusOffset);\n    }\n  }\n\n  // Does the actual updating of the line display. Bails out\n  // (returning false) when there is nothing to be done and forced is\n  // false.\n  function updateDisplayIfNeeded(cm, update) {\n    var display = cm.display, doc = cm.doc;\n\n    if (update.editorIsHidden) {\n      resetView(cm);\n      return false\n    }\n\n    // Bail out if the visible area is already rendered and nothing changed.\n    if (!update.force &&\n        update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&\n        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&\n        display.renderedView == display.view && countDirtyView(cm) == 0)\n      { return false }\n\n    if (maybeUpdateLineNumberWidth(cm)) {\n      resetView(cm);\n      update.dims = getDimensions(cm);\n    }\n\n    // Compute a suitable new viewport (from & to)\n    var end = doc.first + doc.size;\n    var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);\n    var to = Math.min(end, update.visible.to + cm.options.viewportMargin);\n    if (display.viewFrom < from && from - display.viewFrom < 20) { from = Math.max(doc.first, display.viewFrom); }\n    if (display.viewTo > to && display.viewTo - to < 20) { to = Math.min(end, display.viewTo); }\n    if (sawCollapsedSpans) {\n      from = visualLineNo(cm.doc, from);\n      to = visualLineEndNo(cm.doc, to);\n    }\n\n    var different = from != display.viewFrom || to != display.viewTo ||\n      display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;\n    adjustView(cm, from, to);\n\n    display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));\n    // Position the mover div to align with the current scroll position\n    cm.display.mover.style.top = display.viewOffset + \"px\";\n\n    var toUpdate = countDirtyView(cm);\n    if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&\n        (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))\n      { return false }\n\n    // For big changes, we hide the enclosing element during the\n    // update, since that speeds up the operations on most browsers.\n    var selSnapshot = selectionSnapshot(cm);\n    if (toUpdate > 4) { display.lineDiv.style.display = \"none\"; }\n    patchDisplay(cm, display.updateLineNumbers, update.dims);\n    if (toUpdate > 4) { display.lineDiv.style.display = \"\"; }\n    display.renderedView = display.view;\n    // There might have been a widget with a focused element that got\n    // hidden or updated, if so re-focus it.\n    restoreSelection(selSnapshot);\n\n    // Prevent selection and cursors from interfering with the scroll\n    // width and height.\n    removeChildren(display.cursorDiv);\n    removeChildren(display.selectionDiv);\n    display.gutters.style.height = display.sizer.style.minHeight = 0;\n\n    if (different) {\n      display.lastWrapHeight = update.wrapperHeight;\n      display.lastWrapWidth = update.wrapperWidth;\n      startWorker(cm, 400);\n    }\n\n    display.updateLineNumbers = null;\n\n    return true\n  }\n\n  function postUpdateDisplay(cm, update) {\n    var viewport = update.viewport;\n\n    for (var first = true;; first = false) {\n      if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) {\n        // Clip forced viewport to actual scrollable area.\n        if (viewport && viewport.top != null)\n          { viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; }\n        // Updated line heights might result in the drawn area not\n        // actually covering the viewport. Keep looping until it does.\n        update.visible = visibleLines(cm.display, cm.doc, viewport);\n        if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo)\n          { break }\n      } else if (first) {\n        update.visible = visibleLines(cm.display, cm.doc, viewport);\n      }\n      if (!updateDisplayIfNeeded(cm, update)) { break }\n      updateHeightsInViewport(cm);\n      var barMeasure = measureForScrollbars(cm);\n      updateSelection(cm);\n      updateScrollbars(cm, barMeasure);\n      setDocumentHeight(cm, barMeasure);\n      update.force = false;\n    }\n\n    update.signal(cm, \"update\", cm);\n    if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) {\n      update.signal(cm, \"viewportChange\", cm, cm.display.viewFrom, cm.display.viewTo);\n      cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo;\n    }\n  }\n\n  function updateDisplaySimple(cm, viewport) {\n    var update = new DisplayUpdate(cm, viewport);\n    if (updateDisplayIfNeeded(cm, update)) {\n      updateHeightsInViewport(cm);\n      postUpdateDisplay(cm, update);\n      var barMeasure = measureForScrollbars(cm);\n      updateSelection(cm);\n      updateScrollbars(cm, barMeasure);\n      setDocumentHeight(cm, barMeasure);\n      update.finish();\n    }\n  }\n\n  // Sync the actual display DOM structure with display.view, removing\n  // nodes for lines that are no longer in view, and creating the ones\n  // that are not there yet, and updating the ones that are out of\n  // date.\n  function patchDisplay(cm, updateNumbersFrom, dims) {\n    var display = cm.display, lineNumbers = cm.options.lineNumbers;\n    var container = display.lineDiv, cur = container.firstChild;\n\n    function rm(node) {\n      var next = node.nextSibling;\n      // Works around a throw-scroll bug in OS X Webkit\n      if (webkit && mac && cm.display.currentWheelTarget == node)\n        { node.style.display = \"none\"; }\n      else\n        { node.parentNode.removeChild(node); }\n      return next\n    }\n\n    var view = display.view, lineN = display.viewFrom;\n    // Loop over the elements in the view, syncing cur (the DOM nodes\n    // in display.lineDiv) with the view as we go.\n    for (var i = 0; i < view.length; i++) {\n      var lineView = view[i];\n      if (lineView.hidden) ; else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet\n        var node = buildLineElement(cm, lineView, lineN, dims);\n        container.insertBefore(node, cur);\n      } else { // Already drawn\n        while (cur != lineView.node) { cur = rm(cur); }\n        var updateNumber = lineNumbers && updateNumbersFrom != null &&\n          updateNumbersFrom <= lineN && lineView.lineNumber;\n        if (lineView.changes) {\n          if (indexOf(lineView.changes, \"gutter\") > -1) { updateNumber = false; }\n          updateLineForChanges(cm, lineView, lineN, dims);\n        }\n        if (updateNumber) {\n          removeChildren(lineView.lineNumber);\n          lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN)));\n        }\n        cur = lineView.node.nextSibling;\n      }\n      lineN += lineView.size;\n    }\n    while (cur) { cur = rm(cur); }\n  }\n\n  function updateGutterSpace(display) {\n    var width = display.gutters.offsetWidth;\n    display.sizer.style.marginLeft = width + \"px\";\n  }\n\n  function setDocumentHeight(cm, measure) {\n    cm.display.sizer.style.minHeight = measure.docHeight + \"px\";\n    cm.display.heightForcer.style.top = measure.docHeight + \"px\";\n    cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + \"px\";\n  }\n\n  // Re-align line numbers and gutter marks to compensate for\n  // horizontal scrolling.\n  function alignHorizontally(cm) {\n    var display = cm.display, view = display.view;\n    if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) { return }\n    var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;\n    var gutterW = display.gutters.offsetWidth, left = comp + \"px\";\n    for (var i = 0; i < view.length; i++) { if (!view[i].hidden) {\n      if (cm.options.fixedGutter) {\n        if (view[i].gutter)\n          { view[i].gutter.style.left = left; }\n        if (view[i].gutterBackground)\n          { view[i].gutterBackground.style.left = left; }\n      }\n      var align = view[i].alignable;\n      if (align) { for (var j = 0; j < align.length; j++)\n        { align[j].style.left = left; } }\n    } }\n    if (cm.options.fixedGutter)\n      { display.gutters.style.left = (comp + gutterW) + \"px\"; }\n  }\n\n  // Used to ensure that the line number gutter is still the right\n  // size for the current document size. Returns true when an update\n  // is needed.\n  function maybeUpdateLineNumberWidth(cm) {\n    if (!cm.options.lineNumbers) { return false }\n    var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;\n    if (last.length != display.lineNumChars) {\n      var test = display.measure.appendChild(elt(\"div\", [elt(\"div\", last)],\n                                                 \"CodeMirror-linenumber CodeMirror-gutter-elt\"));\n      var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;\n      display.lineGutter.style.width = \"\";\n      display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1;\n      display.lineNumWidth = display.lineNumInnerWidth + padding;\n      display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;\n      display.lineGutter.style.width = display.lineNumWidth + \"px\";\n      updateGutterSpace(cm.display);\n      return true\n    }\n    return false\n  }\n\n  function getGutters(gutters, lineNumbers) {\n    var result = [], sawLineNumbers = false;\n    for (var i = 0; i < gutters.length; i++) {\n      var name = gutters[i], style = null;\n      if (typeof name != \"string\") { style = name.style; name = name.className; }\n      if (name == \"CodeMirror-linenumbers\") {\n        if (!lineNumbers) { continue }\n        else { sawLineNumbers = true; }\n      }\n      result.push({className: name, style: style});\n    }\n    if (lineNumbers && !sawLineNumbers) { result.push({className: \"CodeMirror-linenumbers\", style: null}); }\n    return result\n  }\n\n  // Rebuild the gutter elements, ensure the margin to the left of the\n  // code matches their width.\n  function renderGutters(display) {\n    var gutters = display.gutters, specs = display.gutterSpecs;\n    removeChildren(gutters);\n    display.lineGutter = null;\n    for (var i = 0; i < specs.length; ++i) {\n      var ref = specs[i];\n      var className = ref.className;\n      var style = ref.style;\n      var gElt = gutters.appendChild(elt(\"div\", null, \"CodeMirror-gutter \" + className));\n      if (style) { gElt.style.cssText = style; }\n      if (className == \"CodeMirror-linenumbers\") {\n        display.lineGutter = gElt;\n        gElt.style.width = (display.lineNumWidth || 1) + \"px\";\n      }\n    }\n    gutters.style.display = specs.length ? \"\" : \"none\";\n    updateGutterSpace(display);\n  }\n\n  function updateGutters(cm) {\n    renderGutters(cm.display);\n    regChange(cm);\n    alignHorizontally(cm);\n  }\n\n  // The display handles the DOM integration, both for input reading\n  // and content drawing. It holds references to DOM nodes and\n  // display-related state.\n\n  function Display(place, doc, input, options) {\n    var d = this;\n    this.input = input;\n\n    // Covers bottom-right square when both scrollbars are present.\n    d.scrollbarFiller = elt(\"div\", null, \"CodeMirror-scrollbar-filler\");\n    d.scrollbarFiller.setAttribute(\"cm-not-content\", \"true\");\n    // Covers bottom of gutter when coverGutterNextToScrollbar is on\n    // and h scrollbar is present.\n    d.gutterFiller = elt(\"div\", null, \"CodeMirror-gutter-filler\");\n    d.gutterFiller.setAttribute(\"cm-not-content\", \"true\");\n    // Will contain the actual code, positioned to cover the viewport.\n    d.lineDiv = eltP(\"div\", null, \"CodeMirror-code\");\n    // Elements are added to these to represent selection and cursors.\n    d.selectionDiv = elt(\"div\", null, null, \"position: relative; z-index: 1\");\n    d.cursorDiv = elt(\"div\", null, \"CodeMirror-cursors\");\n    // A visibility: hidden element used to find the size of things.\n    d.measure = elt(\"div\", null, \"CodeMirror-measure\");\n    // When lines outside of the viewport are measured, they are drawn in this.\n    d.lineMeasure = elt(\"div\", null, \"CodeMirror-measure\");\n    // Wraps everything that needs to exist inside the vertically-padded coordinate system\n    d.lineSpace = eltP(\"div\", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],\n                      null, \"position: relative; outline: none\");\n    var lines = eltP(\"div\", [d.lineSpace], \"CodeMirror-lines\");\n    // Moved around its parent to cover visible view.\n    d.mover = elt(\"div\", [lines], null, \"position: relative\");\n    // Set to the height of the document, allowing scrolling.\n    d.sizer = elt(\"div\", [d.mover], \"CodeMirror-sizer\");\n    d.sizerWidth = null;\n    // Behavior of elts with overflow: auto and padding is\n    // inconsistent across browsers. This is used to ensure the\n    // scrollable area is big enough.\n    d.heightForcer = elt(\"div\", null, null, \"position: absolute; height: \" + scrollerGap + \"px; width: 1px;\");\n    // Will contain the gutters, if any.\n    d.gutters = elt(\"div\", null, \"CodeMirror-gutters\");\n    d.lineGutter = null;\n    // Actual scrollable element.\n    d.scroller = elt(\"div\", [d.sizer, d.heightForcer, d.gutters], \"CodeMirror-scroll\");\n    d.scroller.setAttribute(\"tabIndex\", \"-1\");\n    // The element in which the editor lives.\n    d.wrapper = elt(\"div\", [d.scrollbarFiller, d.gutterFiller, d.scroller], \"CodeMirror\");\n\n    // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)\n    if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }\n    if (!webkit && !(gecko && mobile)) { d.scroller.draggable = true; }\n\n    if (place) {\n      if (place.appendChild) { place.appendChild(d.wrapper); }\n      else { place(d.wrapper); }\n    }\n\n    // Current rendered range (may be bigger than the view window).\n    d.viewFrom = d.viewTo = doc.first;\n    d.reportedViewFrom = d.reportedViewTo = doc.first;\n    // Information about the rendered lines.\n    d.view = [];\n    d.renderedView = null;\n    // Holds info about a single rendered line when it was rendered\n    // for measurement, while not in view.\n    d.externalMeasured = null;\n    // Empty space (in pixels) above the view\n    d.viewOffset = 0;\n    d.lastWrapHeight = d.lastWrapWidth = 0;\n    d.updateLineNumbers = null;\n\n    d.nativeBarWidth = d.barHeight = d.barWidth = 0;\n    d.scrollbarsClipped = false;\n\n    // Used to only resize the line number gutter when necessary (when\n    // the amount of lines crosses a boundary that makes its width change)\n    d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;\n    // Set to true when a non-horizontal-scrolling line widget is\n    // added. As an optimization, line widget aligning is skipped when\n    // this is false.\n    d.alignWidgets = false;\n\n    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;\n\n    // Tracks the maximum line length so that the horizontal scrollbar\n    // can be kept static when scrolling.\n    d.maxLine = null;\n    d.maxLineLength = 0;\n    d.maxLineChanged = false;\n\n    // Used for measuring wheel scrolling granularity\n    d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;\n\n    // True when shift is held down.\n    d.shift = false;\n\n    // Used to track whether anything happened since the context menu\n    // was opened.\n    d.selForContextMenu = null;\n\n    d.activeTouch = null;\n\n    d.gutterSpecs = getGutters(options.gutters, options.lineNumbers);\n    renderGutters(d);\n\n    input.init(d);\n  }\n\n  // Since the delta values reported on mouse wheel events are\n  // unstandardized between browsers and even browser versions, and\n  // generally horribly unpredictable, this code starts by measuring\n  // the scroll effect that the first few mouse wheel events have,\n  // and, from that, detects the way it can convert deltas to pixel\n  // offsets afterwards.\n  //\n  // The reason we want to know the amount a wheel event will scroll\n  // is that it gives us a chance to update the display before the\n  // actual scrolling happens, reducing flickering.\n\n  var wheelSamples = 0, wheelPixelsPerUnit = null;\n  // Fill in a browser-detected starting value on browsers where we\n  // know one. These don't have to be accurate -- the result of them\n  // being wrong would just be a slight flicker on the first wheel\n  // scroll (if it is large enough).\n  if (ie) { wheelPixelsPerUnit = -.53; }\n  else if (gecko) { wheelPixelsPerUnit = 15; }\n  else if (chrome) { wheelPixelsPerUnit = -.7; }\n  else if (safari) { wheelPixelsPerUnit = -1/3; }\n\n  function wheelEventDelta(e) {\n    var dx = e.wheelDeltaX, dy = e.wheelDeltaY;\n    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) { dx = e.detail; }\n    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) { dy = e.detail; }\n    else if (dy == null) { dy = e.wheelDelta; }\n    return {x: dx, y: dy}\n  }\n  function wheelEventPixels(e) {\n    var delta = wheelEventDelta(e);\n    delta.x *= wheelPixelsPerUnit;\n    delta.y *= wheelPixelsPerUnit;\n    return delta\n  }\n\n  function onScrollWheel(cm, e) {\n    var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;\n\n    var display = cm.display, scroll = display.scroller;\n    // Quit if there's nothing to scroll here\n    var canScrollX = scroll.scrollWidth > scroll.clientWidth;\n    var canScrollY = scroll.scrollHeight > scroll.clientHeight;\n    if (!(dx && canScrollX || dy && canScrollY)) { return }\n\n    // Webkit browsers on OS X abort momentum scrolls when the target\n    // of the scroll event is removed from the scrollable element.\n    // This hack (see related code in patchDisplay) makes sure the\n    // element is kept around.\n    if (dy && mac && webkit) {\n      outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {\n        for (var i = 0; i < view.length; i++) {\n          if (view[i].node == cur) {\n            cm.display.currentWheelTarget = cur;\n            break outer\n          }\n        }\n      }\n    }\n\n    // On some browsers, horizontal scrolling will cause redraws to\n    // happen before the gutter has been realigned, causing it to\n    // wriggle around in a most unseemly way. When we have an\n    // estimated pixels/delta value, we just handle horizontal\n    // scrolling entirely here. It'll be slightly off from native, but\n    // better than glitching out.\n    if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {\n      if (dy && canScrollY)\n        { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); }\n      setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit));\n      // Only prevent default scrolling if vertical scrolling is\n      // actually possible. Otherwise, it causes vertical scroll\n      // jitter on OSX trackpads when deltaX is small and deltaY\n      // is large (issue #3579)\n      if (!dy || (dy && canScrollY))\n        { e_preventDefault(e); }\n      display.wheelStartX = null; // Abort measurement, if in progress\n      return\n    }\n\n    // 'Project' the visible viewport to cover the area that is being\n    // scrolled into view (if we know enough to estimate it).\n    if (dy && wheelPixelsPerUnit != null) {\n      var pixels = dy * wheelPixelsPerUnit;\n      var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;\n      if (pixels < 0) { top = Math.max(0, top + pixels - 50); }\n      else { bot = Math.min(cm.doc.height, bot + pixels + 50); }\n      updateDisplaySimple(cm, {top: top, bottom: bot});\n    }\n\n    if (wheelSamples < 20) {\n      if (display.wheelStartX == null) {\n        display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;\n        display.wheelDX = dx; display.wheelDY = dy;\n        setTimeout(function () {\n          if (display.wheelStartX == null) { return }\n          var movedX = scroll.scrollLeft - display.wheelStartX;\n          var movedY = scroll.scrollTop - display.wheelStartY;\n          var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||\n            (movedX && display.wheelDX && movedX / display.wheelDX);\n          display.wheelStartX = display.wheelStartY = null;\n          if (!sample) { return }\n          wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);\n          ++wheelSamples;\n        }, 200);\n      } else {\n        display.wheelDX += dx; display.wheelDY += dy;\n      }\n    }\n  }\n\n  // Selection objects are immutable. A new one is created every time\n  // the selection changes. A selection is one or more non-overlapping\n  // (and non-touching) ranges, sorted, and an integer that indicates\n  // which one is the primary selection (the one that's scrolled into\n  // view, that getCursor returns, etc).\n  var Selection = function(ranges, primIndex) {\n    this.ranges = ranges;\n    this.primIndex = primIndex;\n  };\n\n  Selection.prototype.primary = function () { return this.ranges[this.primIndex] };\n\n  Selection.prototype.equals = function (other) {\n    if (other == this) { return true }\n    if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) { return false }\n    for (var i = 0; i < this.ranges.length; i++) {\n      var here = this.ranges[i], there = other.ranges[i];\n      if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }\n    }\n    return true\n  };\n\n  Selection.prototype.deepCopy = function () {\n    var out = [];\n    for (var i = 0; i < this.ranges.length; i++)\n      { out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); }\n    return new Selection(out, this.primIndex)\n  };\n\n  Selection.prototype.somethingSelected = function () {\n    for (var i = 0; i < this.ranges.length; i++)\n      { if (!this.ranges[i].empty()) { return true } }\n    return false\n  };\n\n  Selection.prototype.contains = function (pos, end) {\n    if (!end) { end = pos; }\n    for (var i = 0; i < this.ranges.length; i++) {\n      var range = this.ranges[i];\n      if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)\n        { return i }\n    }\n    return -1\n  };\n\n  var Range = function(anchor, head) {\n    this.anchor = anchor; this.head = head;\n  };\n\n  Range.prototype.from = function () { return minPos(this.anchor, this.head) };\n  Range.prototype.to = function () { return maxPos(this.anchor, this.head) };\n  Range.prototype.empty = function () { return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch };\n\n  // Take an unsorted, potentially overlapping set of ranges, and\n  // build a selection out of it. 'Consumes' ranges array (modifying\n  // it).\n  function normalizeSelection(cm, ranges, primIndex) {\n    var mayTouch = cm && cm.options.selectionsMayTouch;\n    var prim = ranges[primIndex];\n    ranges.sort(function (a, b) { return cmp(a.from(), b.from()); });\n    primIndex = indexOf(ranges, prim);\n    for (var i = 1; i < ranges.length; i++) {\n      var cur = ranges[i], prev = ranges[i - 1];\n      var diff = cmp(prev.to(), cur.from());\n      if (mayTouch && !cur.empty() ? diff > 0 : diff >= 0) {\n        var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to());\n        var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head;\n        if (i <= primIndex) { --primIndex; }\n        ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to));\n      }\n    }\n    return new Selection(ranges, primIndex)\n  }\n\n  function simpleSelection(anchor, head) {\n    return new Selection([new Range(anchor, head || anchor)], 0)\n  }\n\n  // Compute the position of the end of a change (its 'to' property\n  // refers to the pre-change end).\n  function changeEnd(change) {\n    if (!change.text) { return change.to }\n    return Pos(change.from.line + change.text.length - 1,\n               lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0))\n  }\n\n  // Adjust a position to refer to the post-change position of the\n  // same text, or the end of the change if the change covers it.\n  function adjustForChange(pos, change) {\n    if (cmp(pos, change.from) < 0) { return pos }\n    if (cmp(pos, change.to) <= 0) { return changeEnd(change) }\n\n    var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;\n    if (pos.line == change.to.line) { ch += changeEnd(change).ch - change.to.ch; }\n    return Pos(line, ch)\n  }\n\n  function computeSelAfterChange(doc, change) {\n    var out = [];\n    for (var i = 0; i < doc.sel.ranges.length; i++) {\n      var range = doc.sel.ranges[i];\n      out.push(new Range(adjustForChange(range.anchor, change),\n                         adjustForChange(range.head, change)));\n    }\n    return normalizeSelection(doc.cm, out, doc.sel.primIndex)\n  }\n\n  function offsetPos(pos, old, nw) {\n    if (pos.line == old.line)\n      { return Pos(nw.line, pos.ch - old.ch + nw.ch) }\n    else\n      { return Pos(nw.line + (pos.line - old.line), pos.ch) }\n  }\n\n  // Used by replaceSelections to allow moving the selection to the\n  // start or around the replaced test. Hint may be \"start\" or \"around\".\n  function computeReplacedSel(doc, changes, hint) {\n    var out = [];\n    var oldPrev = Pos(doc.first, 0), newPrev = oldPrev;\n    for (var i = 0; i < changes.length; i++) {\n      var change = changes[i];\n      var from = offsetPos(change.from, oldPrev, newPrev);\n      var to = offsetPos(changeEnd(change), oldPrev, newPrev);\n      oldPrev = change.to;\n      newPrev = to;\n      if (hint == \"around\") {\n        var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0;\n        out[i] = new Range(inv ? to : from, inv ? from : to);\n      } else {\n        out[i] = new Range(from, from);\n      }\n    }\n    return new Selection(out, doc.sel.primIndex)\n  }\n\n  // Used to get the editor into a consistent state again when options change.\n\n  function loadMode(cm) {\n    cm.doc.mode = getMode(cm.options, cm.doc.modeOption);\n    resetModeState(cm);\n  }\n\n  function resetModeState(cm) {\n    cm.doc.iter(function (line) {\n      if (line.stateAfter) { line.stateAfter = null; }\n      if (line.styles) { line.styles = null; }\n    });\n    cm.doc.modeFrontier = cm.doc.highlightFrontier = cm.doc.first;\n    startWorker(cm, 100);\n    cm.state.modeGen++;\n    if (cm.curOp) { regChange(cm); }\n  }\n\n  // DOCUMENT DATA STRUCTURE\n\n  // By default, updates that start and end at the beginning of a line\n  // are treated specially, in order to make the association of line\n  // widgets and marker elements with the text behave more intuitive.\n  function isWholeLineUpdate(doc, change) {\n    return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == \"\" &&\n      (!doc.cm || doc.cm.options.wholeLineUpdateBefore)\n  }\n\n  // Perform a change on the document data structure.\n  function updateDoc(doc, change, markedSpans, estimateHeight) {\n    function spansFor(n) {return markedSpans ? markedSpans[n] : null}\n    function update(line, text, spans) {\n      updateLine(line, text, spans, estimateHeight);\n      signalLater(line, \"change\", line, change);\n    }\n    function linesFor(start, end) {\n      var result = [];\n      for (var i = start; i < end; ++i)\n        { result.push(new Line(text[i], spansFor(i), estimateHeight)); }\n      return result\n    }\n\n    var from = change.from, to = change.to, text = change.text;\n    var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);\n    var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;\n\n    // Adjust the line structure\n    if (change.full) {\n      doc.insert(0, linesFor(0, text.length));\n      doc.remove(text.length, doc.size - text.length);\n    } else if (isWholeLineUpdate(doc, change)) {\n      // This is a whole-line replace. Treated specially to make\n      // sure line objects move the way they are supposed to.\n      var added = linesFor(0, text.length - 1);\n      update(lastLine, lastLine.text, lastSpans);\n      if (nlines) { doc.remove(from.line, nlines); }\n      if (added.length) { doc.insert(from.line, added); }\n    } else if (firstLine == lastLine) {\n      if (text.length == 1) {\n        update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);\n      } else {\n        var added$1 = linesFor(1, text.length - 1);\n        added$1.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));\n        update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n        doc.insert(from.line + 1, added$1);\n      }\n    } else if (text.length == 1) {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0));\n      doc.remove(from.line + 1, nlines);\n    } else {\n      update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));\n      update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);\n      var added$2 = linesFor(1, text.length - 1);\n      if (nlines > 1) { doc.remove(from.line + 1, nlines - 1); }\n      doc.insert(from.line + 1, added$2);\n    }\n\n    signalLater(doc, \"change\", doc, change);\n  }\n\n  // Call f for all linked documents.\n  function linkedDocs(doc, f, sharedHistOnly) {\n    function propagate(doc, skip, sharedHist) {\n      if (doc.linked) { for (var i = 0; i < doc.linked.length; ++i) {\n        var rel = doc.linked[i];\n        if (rel.doc == skip) { continue }\n        var shared = sharedHist && rel.sharedHist;\n        if (sharedHistOnly && !shared) { continue }\n        f(rel.doc, shared);\n        propagate(rel.doc, doc, shared);\n      } }\n    }\n    propagate(doc, null, true);\n  }\n\n  // Attach a document to an editor.\n  function attachDoc(cm, doc) {\n    if (doc.cm) { throw new Error(\"This document is already in use.\") }\n    cm.doc = doc;\n    doc.cm = cm;\n    estimateLineHeights(cm);\n    loadMode(cm);\n    setDirectionClass(cm);\n    if (!cm.options.lineWrapping) { findMaxLine(cm); }\n    cm.options.mode = doc.modeOption;\n    regChange(cm);\n  }\n\n  function setDirectionClass(cm) {\n  (cm.doc.direction == \"rtl\" ? addClass : rmClass)(cm.display.lineDiv, \"CodeMirror-rtl\");\n  }\n\n  function directionChanged(cm) {\n    runInOp(cm, function () {\n      setDirectionClass(cm);\n      regChange(cm);\n    });\n  }\n\n  function History(startGen) {\n    // Arrays of change events and selections. Doing something adds an\n    // event to done and clears undo. Undoing moves events from done\n    // to undone, redoing moves them in the other direction.\n    this.done = []; this.undone = [];\n    this.undoDepth = Infinity;\n    // Used to track when changes can be merged into a single undo\n    // event\n    this.lastModTime = this.lastSelTime = 0;\n    this.lastOp = this.lastSelOp = null;\n    this.lastOrigin = this.lastSelOrigin = null;\n    // Used by the isClean() method\n    this.generation = this.maxGeneration = startGen || 1;\n  }\n\n  // Create a history change event from an updateDoc-style change\n  // object.\n  function historyChangeFromChange(doc, change) {\n    var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)};\n    attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);\n    linkedDocs(doc, function (doc) { return attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); }, true);\n    return histChange\n  }\n\n  // Pop all selection events off the end of a history array. Stop at\n  // a change event.\n  function clearSelectionEvents(array) {\n    while (array.length) {\n      var last = lst(array);\n      if (last.ranges) { array.pop(); }\n      else { break }\n    }\n  }\n\n  // Find the top change event in the history. Pop off selection\n  // events that are in the way.\n  function lastChangeEvent(hist, force) {\n    if (force) {\n      clearSelectionEvents(hist.done);\n      return lst(hist.done)\n    } else if (hist.done.length && !lst(hist.done).ranges) {\n      return lst(hist.done)\n    } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) {\n      hist.done.pop();\n      return lst(hist.done)\n    }\n  }\n\n  // Register a change in the history. Merges changes that are within\n  // a single operation, or are close together with an origin that\n  // allows merging (starting with \"+\") into a single event.\n  function addChangeToHistory(doc, change, selAfter, opId) {\n    var hist = doc.history;\n    hist.undone.length = 0;\n    var time = +new Date, cur;\n    var last;\n\n    if ((hist.lastOp == opId ||\n         hist.lastOrigin == change.origin && change.origin &&\n         ((change.origin.charAt(0) == \"+\" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||\n          change.origin.charAt(0) == \"*\")) &&\n        (cur = lastChangeEvent(hist, hist.lastOp == opId))) {\n      // Merge this change into the last event\n      last = lst(cur.changes);\n      if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) {\n        // Optimized case for simple insertion -- don't want to add\n        // new changesets for every character typed\n        last.to = changeEnd(change);\n      } else {\n        // Add new sub-event\n        cur.changes.push(historyChangeFromChange(doc, change));\n      }\n    } else {\n      // Can not be merged, start a new event.\n      var before = lst(hist.done);\n      if (!before || !before.ranges)\n        { pushSelectionToHistory(doc.sel, hist.done); }\n      cur = {changes: [historyChangeFromChange(doc, change)],\n             generation: hist.generation};\n      hist.done.push(cur);\n      while (hist.done.length > hist.undoDepth) {\n        hist.done.shift();\n        if (!hist.done[0].ranges) { hist.done.shift(); }\n      }\n    }\n    hist.done.push(selAfter);\n    hist.generation = ++hist.maxGeneration;\n    hist.lastModTime = hist.lastSelTime = time;\n    hist.lastOp = hist.lastSelOp = opId;\n    hist.lastOrigin = hist.lastSelOrigin = change.origin;\n\n    if (!last) { signal(doc, \"historyAdded\"); }\n  }\n\n  function selectionEventCanBeMerged(doc, origin, prev, sel) {\n    var ch = origin.charAt(0);\n    return ch == \"*\" ||\n      ch == \"+\" &&\n      prev.ranges.length == sel.ranges.length &&\n      prev.somethingSelected() == sel.somethingSelected() &&\n      new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500)\n  }\n\n  // Called whenever the selection changes, sets the new selection as\n  // the pending selection in the history, and pushes the old pending\n  // selection into the 'done' array when it was significantly\n  // different (in number of selected ranges, emptiness, or time).\n  function addSelectionToHistory(doc, sel, opId, options) {\n    var hist = doc.history, origin = options && options.origin;\n\n    // A new event is started when the previous origin does not match\n    // the current, or the origins don't allow matching. Origins\n    // starting with * are always merged, those starting with + are\n    // merged when similar and close together in time.\n    if (opId == hist.lastSelOp ||\n        (origin && hist.lastSelOrigin == origin &&\n         (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin ||\n          selectionEventCanBeMerged(doc, origin, lst(hist.done), sel))))\n      { hist.done[hist.done.length - 1] = sel; }\n    else\n      { pushSelectionToHistory(sel, hist.done); }\n\n    hist.lastSelTime = +new Date;\n    hist.lastSelOrigin = origin;\n    hist.lastSelOp = opId;\n    if (options && options.clearRedo !== false)\n      { clearSelectionEvents(hist.undone); }\n  }\n\n  function pushSelectionToHistory(sel, dest) {\n    var top = lst(dest);\n    if (!(top && top.ranges && top.equals(sel)))\n      { dest.push(sel); }\n  }\n\n  // Used to store marked span information in the history.\n  function attachLocalSpans(doc, change, from, to) {\n    var existing = change[\"spans_\" + doc.id], n = 0;\n    doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) {\n      if (line.markedSpans)\n        { (existing || (existing = change[\"spans_\" + doc.id] = {}))[n] = line.markedSpans; }\n      ++n;\n    });\n  }\n\n  // When un/re-doing restores text containing marked spans, those\n  // that have been explicitly cleared should not be restored.\n  function removeClearedSpans(spans) {\n    if (!spans) { return null }\n    var out;\n    for (var i = 0; i < spans.length; ++i) {\n      if (spans[i].marker.explicitlyCleared) { if (!out) { out = spans.slice(0, i); } }\n      else if (out) { out.push(spans[i]); }\n    }\n    return !out ? spans : out.length ? out : null\n  }\n\n  // Retrieve and filter the old marked spans stored in a change event.\n  function getOldSpans(doc, change) {\n    var found = change[\"spans_\" + doc.id];\n    if (!found) { return null }\n    var nw = [];\n    for (var i = 0; i < change.text.length; ++i)\n      { nw.push(removeClearedSpans(found[i])); }\n    return nw\n  }\n\n  // Used for un/re-doing changes from the history. Combines the\n  // result of computing the existing spans with the set of spans that\n  // existed in the history (so that deleting around a span and then\n  // undoing brings back the span).\n  function mergeOldSpans(doc, change) {\n    var old = getOldSpans(doc, change);\n    var stretched = stretchSpansOverChange(doc, change);\n    if (!old) { return stretched }\n    if (!stretched) { return old }\n\n    for (var i = 0; i < old.length; ++i) {\n      var oldCur = old[i], stretchCur = stretched[i];\n      if (oldCur && stretchCur) {\n        spans: for (var j = 0; j < stretchCur.length; ++j) {\n          var span = stretchCur[j];\n          for (var k = 0; k < oldCur.length; ++k)\n            { if (oldCur[k].marker == span.marker) { continue spans } }\n          oldCur.push(span);\n        }\n      } else if (stretchCur) {\n        old[i] = stretchCur;\n      }\n    }\n    return old\n  }\n\n  // Used both to provide a JSON-safe object in .getHistory, and, when\n  // detaching a document, to split the history in two\n  function copyHistoryArray(events, newGroup, instantiateSel) {\n    var copy = [];\n    for (var i = 0; i < events.length; ++i) {\n      var event = events[i];\n      if (event.ranges) {\n        copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event);\n        continue\n      }\n      var changes = event.changes, newChanges = [];\n      copy.push({changes: newChanges});\n      for (var j = 0; j < changes.length; ++j) {\n        var change = changes[j], m = (void 0);\n        newChanges.push({from: change.from, to: change.to, text: change.text});\n        if (newGroup) { for (var prop in change) { if (m = prop.match(/^spans_(\\d+)$/)) {\n          if (indexOf(newGroup, Number(m[1])) > -1) {\n            lst(newChanges)[prop] = change[prop];\n            delete change[prop];\n          }\n        } } }\n      }\n    }\n    return copy\n  }\n\n  // The 'scroll' parameter given to many of these indicated whether\n  // the new cursor position should be scrolled into view after\n  // modifying the selection.\n\n  // If shift is held or the extend flag is set, extends a range to\n  // include a given position (and optionally a second position).\n  // Otherwise, simply returns the range between the given positions.\n  // Used for cursor motion and such.\n  function extendRange(range, head, other, extend) {\n    if (extend) {\n      var anchor = range.anchor;\n      if (other) {\n        var posBefore = cmp(head, anchor) < 0;\n        if (posBefore != (cmp(other, anchor) < 0)) {\n          anchor = head;\n          head = other;\n        } else if (posBefore != (cmp(head, other) < 0)) {\n          head = other;\n        }\n      }\n      return new Range(anchor, head)\n    } else {\n      return new Range(other || head, head)\n    }\n  }\n\n  // Extend the primary selection range, discard the rest.\n  function extendSelection(doc, head, other, options, extend) {\n    if (extend == null) { extend = doc.cm && (doc.cm.display.shift || doc.extend); }\n    setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options);\n  }\n\n  // Extend all selections (pos is an array of selections with length\n  // equal the number of selections)\n  function extendSelections(doc, heads, options) {\n    var out = [];\n    var extend = doc.cm && (doc.cm.display.shift || doc.extend);\n    for (var i = 0; i < doc.sel.ranges.length; i++)\n      { out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend); }\n    var newSel = normalizeSelection(doc.cm, out, doc.sel.primIndex);\n    setSelection(doc, newSel, options);\n  }\n\n  // Updates a single range in the selection.\n  function replaceOneSelection(doc, i, range, options) {\n    var ranges = doc.sel.ranges.slice(0);\n    ranges[i] = range;\n    setSelection(doc, normalizeSelection(doc.cm, ranges, doc.sel.primIndex), options);\n  }\n\n  // Reset the selection to a single range.\n  function setSimpleSelection(doc, anchor, head, options) {\n    setSelection(doc, simpleSelection(anchor, head), options);\n  }\n\n  // Give beforeSelectionChange handlers a change to influence a\n  // selection update.\n  function filterSelectionChange(doc, sel, options) {\n    var obj = {\n      ranges: sel.ranges,\n      update: function(ranges) {\n        this.ranges = [];\n        for (var i = 0; i < ranges.length; i++)\n          { this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),\n                                     clipPos(doc, ranges[i].head)); }\n      },\n      origin: options && options.origin\n    };\n    signal(doc, \"beforeSelectionChange\", doc, obj);\n    if (doc.cm) { signal(doc.cm, \"beforeSelectionChange\", doc.cm, obj); }\n    if (obj.ranges != sel.ranges) { return normalizeSelection(doc.cm, obj.ranges, obj.ranges.length - 1) }\n    else { return sel }\n  }\n\n  function setSelectionReplaceHistory(doc, sel, options) {\n    var done = doc.history.done, last = lst(done);\n    if (last && last.ranges) {\n      done[done.length - 1] = sel;\n      setSelectionNoUndo(doc, sel, options);\n    } else {\n      setSelection(doc, sel, options);\n    }\n  }\n\n  // Set a new selection.\n  function setSelection(doc, sel, options) {\n    setSelectionNoUndo(doc, sel, options);\n    addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options);\n  }\n\n  function setSelectionNoUndo(doc, sel, options) {\n    if (hasHandler(doc, \"beforeSelectionChange\") || doc.cm && hasHandler(doc.cm, \"beforeSelectionChange\"))\n      { sel = filterSelectionChange(doc, sel, options); }\n\n    var bias = options && options.bias ||\n      (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);\n    setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));\n\n    if (!(options && options.scroll === false) && doc.cm)\n      { ensureCursorVisible(doc.cm); }\n  }\n\n  function setSelectionInner(doc, sel) {\n    if (sel.equals(doc.sel)) { return }\n\n    doc.sel = sel;\n\n    if (doc.cm) {\n      doc.cm.curOp.updateInput = 1;\n      doc.cm.curOp.selectionChanged = true;\n      signalCursorActivity(doc.cm);\n    }\n    signalLater(doc, \"cursorActivity\", doc);\n  }\n\n  // Verify that the selection does not partially select any atomic\n  // marked ranges.\n  function reCheckSelection(doc) {\n    setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false));\n  }\n\n  // Return a selection that does not partially select any atomic\n  // ranges.\n  function skipAtomicInSelection(doc, sel, bias, mayClear) {\n    var out;\n    for (var i = 0; i < sel.ranges.length; i++) {\n      var range = sel.ranges[i];\n      var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];\n      var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);\n      var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);\n      if (out || newAnchor != range.anchor || newHead != range.head) {\n        if (!out) { out = sel.ranges.slice(0, i); }\n        out[i] = new Range(newAnchor, newHead);\n      }\n    }\n    return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel\n  }\n\n  function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {\n    var line = getLine(doc, pos.line);\n    if (line.markedSpans) { for (var i = 0; i < line.markedSpans.length; ++i) {\n      var sp = line.markedSpans[i], m = sp.marker;\n\n      // Determine if we should prevent the cursor being placed to the left/right of an atomic marker\n      // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it\n      // is with selectLeft/Right\n      var preventCursorLeft = (\"selectLeft\" in m) ? !m.selectLeft : m.inclusiveLeft;\n      var preventCursorRight = (\"selectRight\" in m) ? !m.selectRight : m.inclusiveRight;\n\n      if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&\n          (sp.to == null || (preventCursorRight ? sp.to >= pos.ch : sp.to > pos.ch))) {\n        if (mayClear) {\n          signal(m, \"beforeCursorEnter\");\n          if (m.explicitlyCleared) {\n            if (!line.markedSpans) { break }\n            else {--i; continue}\n          }\n        }\n        if (!m.atomic) { continue }\n\n        if (oldPos) {\n          var near = m.find(dir < 0 ? 1 : -1), diff = (void 0);\n          if (dir < 0 ? preventCursorRight : preventCursorLeft)\n            { near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null); }\n          if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))\n            { return skipAtomicInner(doc, near, pos, dir, mayClear) }\n        }\n\n        var far = m.find(dir < 0 ? -1 : 1);\n        if (dir < 0 ? preventCursorLeft : preventCursorRight)\n          { far = movePos(doc, far, dir, far.line == pos.line ? line : null); }\n        return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null\n      }\n    } }\n    return pos\n  }\n\n  // Ensure a given position is not inside an atomic range.\n  function skipAtomic(doc, pos, oldPos, bias, mayClear) {\n    var dir = bias || 1;\n    var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||\n        (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||\n        skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||\n        (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));\n    if (!found) {\n      doc.cantEdit = true;\n      return Pos(doc.first, 0)\n    }\n    return found\n  }\n\n  function movePos(doc, pos, dir, line) {\n    if (dir < 0 && pos.ch == 0) {\n      if (pos.line > doc.first) { return clipPos(doc, Pos(pos.line - 1)) }\n      else { return null }\n    } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {\n      if (pos.line < doc.first + doc.size - 1) { return Pos(pos.line + 1, 0) }\n      else { return null }\n    } else {\n      return new Pos(pos.line, pos.ch + dir)\n    }\n  }\n\n  function selectAll(cm) {\n    cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);\n  }\n\n  // UPDATING\n\n  // Allow \"beforeChange\" event handlers to influence a change\n  function filterChange(doc, change, update) {\n    var obj = {\n      canceled: false,\n      from: change.from,\n      to: change.to,\n      text: change.text,\n      origin: change.origin,\n      cancel: function () { return obj.canceled = true; }\n    };\n    if (update) { obj.update = function (from, to, text, origin) {\n      if (from) { obj.from = clipPos(doc, from); }\n      if (to) { obj.to = clipPos(doc, to); }\n      if (text) { obj.text = text; }\n      if (origin !== undefined) { obj.origin = origin; }\n    }; }\n    signal(doc, \"beforeChange\", doc, obj);\n    if (doc.cm) { signal(doc.cm, \"beforeChange\", doc.cm, obj); }\n\n    if (obj.canceled) {\n      if (doc.cm) { doc.cm.curOp.updateInput = 2; }\n      return null\n    }\n    return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}\n  }\n\n  // Apply a change to a document, and add it to the document's\n  // history, and propagating it to all linked documents.\n  function makeChange(doc, change, ignoreReadOnly) {\n    if (doc.cm) {\n      if (!doc.cm.curOp) { return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly) }\n      if (doc.cm.state.suppressEdits) { return }\n    }\n\n    if (hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\")) {\n      change = filterChange(doc, change, true);\n      if (!change) { return }\n    }\n\n    // Possibly split or suppress the update based on the presence\n    // of read-only spans in its range.\n    var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);\n    if (split) {\n      for (var i = split.length - 1; i >= 0; --i)\n        { makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [\"\"] : change.text, origin: change.origin}); }\n    } else {\n      makeChangeInner(doc, change);\n    }\n  }\n\n  function makeChangeInner(doc, change) {\n    if (change.text.length == 1 && change.text[0] == \"\" && cmp(change.from, change.to) == 0) { return }\n    var selAfter = computeSelAfterChange(doc, change);\n    addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);\n\n    makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));\n    var rebased = [];\n\n    linkedDocs(doc, function (doc, sharedHist) {\n      if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n        rebaseHist(doc.history, change);\n        rebased.push(doc.history);\n      }\n      makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));\n    });\n  }\n\n  // Revert a change stored in a document's history.\n  function makeChangeFromHistory(doc, type, allowSelectionOnly) {\n    var suppress = doc.cm && doc.cm.state.suppressEdits;\n    if (suppress && !allowSelectionOnly) { return }\n\n    var hist = doc.history, event, selAfter = doc.sel;\n    var source = type == \"undo\" ? hist.done : hist.undone, dest = type == \"undo\" ? hist.undone : hist.done;\n\n    // Verify that there is a useable event (so that ctrl-z won't\n    // needlessly clear selection events)\n    var i = 0;\n    for (; i < source.length; i++) {\n      event = source[i];\n      if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)\n        { break }\n    }\n    if (i == source.length) { return }\n    hist.lastOrigin = hist.lastSelOrigin = null;\n\n    for (;;) {\n      event = source.pop();\n      if (event.ranges) {\n        pushSelectionToHistory(event, dest);\n        if (allowSelectionOnly && !event.equals(doc.sel)) {\n          setSelection(doc, event, {clearRedo: false});\n          return\n        }\n        selAfter = event;\n      } else if (suppress) {\n        source.push(event);\n        return\n      } else { break }\n    }\n\n    // Build up a reverse change object to add to the opposite history\n    // stack (redo when undoing, and vice versa).\n    var antiChanges = [];\n    pushSelectionToHistory(selAfter, dest);\n    dest.push({changes: antiChanges, generation: hist.generation});\n    hist.generation = event.generation || ++hist.maxGeneration;\n\n    var filter = hasHandler(doc, \"beforeChange\") || doc.cm && hasHandler(doc.cm, \"beforeChange\");\n\n    var loop = function ( i ) {\n      var change = event.changes[i];\n      change.origin = type;\n      if (filter && !filterChange(doc, change, false)) {\n        source.length = 0;\n        return {}\n      }\n\n      antiChanges.push(historyChangeFromChange(doc, change));\n\n      var after = i ? computeSelAfterChange(doc, change) : lst(source);\n      makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));\n      if (!i && doc.cm) { doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); }\n      var rebased = [];\n\n      // Propagate to the linked documents\n      linkedDocs(doc, function (doc, sharedHist) {\n        if (!sharedHist && indexOf(rebased, doc.history) == -1) {\n          rebaseHist(doc.history, change);\n          rebased.push(doc.history);\n        }\n        makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));\n      });\n    };\n\n    for (var i$1 = event.changes.length - 1; i$1 >= 0; --i$1) {\n      var returned = loop( i$1 );\n\n      if ( returned ) return returned.v;\n    }\n  }\n\n  // Sub-views need their line numbers shifted when text is added\n  // above or below them in the parent document.\n  function shiftDoc(doc, distance) {\n    if (distance == 0) { return }\n    doc.first += distance;\n    doc.sel = new Selection(map(doc.sel.ranges, function (range) { return new Range(\n      Pos(range.anchor.line + distance, range.anchor.ch),\n      Pos(range.head.line + distance, range.head.ch)\n    ); }), doc.sel.primIndex);\n    if (doc.cm) {\n      regChange(doc.cm, doc.first, doc.first - distance, distance);\n      for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++)\n        { regLineChange(doc.cm, l, \"gutter\"); }\n    }\n  }\n\n  // More lower-level change function, handling only a single document\n  // (not linked ones).\n  function makeChangeSingleDoc(doc, change, selAfter, spans) {\n    if (doc.cm && !doc.cm.curOp)\n      { return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans) }\n\n    if (change.to.line < doc.first) {\n      shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));\n      return\n    }\n    if (change.from.line > doc.lastLine()) { return }\n\n    // Clip the change to the size of this doc\n    if (change.from.line < doc.first) {\n      var shift = change.text.length - 1 - (doc.first - change.from.line);\n      shiftDoc(doc, shift);\n      change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),\n                text: [lst(change.text)], origin: change.origin};\n    }\n    var last = doc.lastLine();\n    if (change.to.line > last) {\n      change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),\n                text: [change.text[0]], origin: change.origin};\n    }\n\n    change.removed = getBetween(doc, change.from, change.to);\n\n    if (!selAfter) { selAfter = computeSelAfterChange(doc, change); }\n    if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); }\n    else { updateDoc(doc, change, spans); }\n    setSelectionNoUndo(doc, selAfter, sel_dontScroll);\n\n    if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0)))\n      { doc.cantEdit = false; }\n  }\n\n  // Handle the interaction of a change to a document with the editor\n  // that this document is part of.\n  function makeChangeSingleDocInEditor(cm, change, spans) {\n    var doc = cm.doc, display = cm.display, from = change.from, to = change.to;\n\n    var recomputeMaxLength = false, checkWidthStart = from.line;\n    if (!cm.options.lineWrapping) {\n      checkWidthStart = lineNo(visualLine(getLine(doc, from.line)));\n      doc.iter(checkWidthStart, to.line + 1, function (line) {\n        if (line == display.maxLine) {\n          recomputeMaxLength = true;\n          return true\n        }\n      });\n    }\n\n    if (doc.sel.contains(change.from, change.to) > -1)\n      { signalCursorActivity(cm); }\n\n    updateDoc(doc, change, spans, estimateHeight(cm));\n\n    if (!cm.options.lineWrapping) {\n      doc.iter(checkWidthStart, from.line + change.text.length, function (line) {\n        var len = lineLength(line);\n        if (len > display.maxLineLength) {\n          display.maxLine = line;\n          display.maxLineLength = len;\n          display.maxLineChanged = true;\n          recomputeMaxLength = false;\n        }\n      });\n      if (recomputeMaxLength) { cm.curOp.updateMaxLine = true; }\n    }\n\n    retreatFrontier(doc, from.line);\n    startWorker(cm, 400);\n\n    var lendiff = change.text.length - (to.line - from.line) - 1;\n    // Remember that these lines changed, for updating the display\n    if (change.full)\n      { regChange(cm); }\n    else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))\n      { regLineChange(cm, from.line, \"text\"); }\n    else\n      { regChange(cm, from.line, to.line + 1, lendiff); }\n\n    var changesHandler = hasHandler(cm, \"changes\"), changeHandler = hasHandler(cm, \"change\");\n    if (changeHandler || changesHandler) {\n      var obj = {\n        from: from, to: to,\n        text: change.text,\n        removed: change.removed,\n        origin: change.origin\n      };\n      if (changeHandler) { signalLater(cm, \"change\", cm, obj); }\n      if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); }\n    }\n    cm.display.selForContextMenu = null;\n  }\n\n  function replaceRange(doc, code, from, to, origin) {\n    var assign;\n\n    if (!to) { to = from; }\n    if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); }\n    if (typeof code == \"string\") { code = doc.splitLines(code); }\n    makeChange(doc, {from: from, to: to, text: code, origin: origin});\n  }\n\n  // Rebasing/resetting history to deal with externally-sourced changes\n\n  function rebaseHistSelSingle(pos, from, to, diff) {\n    if (to < pos.line) {\n      pos.line += diff;\n    } else if (from < pos.line) {\n      pos.line = from;\n      pos.ch = 0;\n    }\n  }\n\n  // Tries to rebase an array of history events given a change in the\n  // document. If the change touches the same lines as the event, the\n  // event, and everything 'behind' it, is discarded. If the change is\n  // before the event, the event's positions are updated. Uses a\n  // copy-on-write scheme for the positions, to avoid having to\n  // reallocate them all on every rebase, but also avoid problems with\n  // shared position objects being unsafely updated.\n  function rebaseHistArray(array, from, to, diff) {\n    for (var i = 0; i < array.length; ++i) {\n      var sub = array[i], ok = true;\n      if (sub.ranges) {\n        if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; }\n        for (var j = 0; j < sub.ranges.length; j++) {\n          rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff);\n          rebaseHistSelSingle(sub.ranges[j].head, from, to, diff);\n        }\n        continue\n      }\n      for (var j$1 = 0; j$1 < sub.changes.length; ++j$1) {\n        var cur = sub.changes[j$1];\n        if (to < cur.from.line) {\n          cur.from = Pos(cur.from.line + diff, cur.from.ch);\n          cur.to = Pos(cur.to.line + diff, cur.to.ch);\n        } else if (from <= cur.to.line) {\n          ok = false;\n          break\n        }\n      }\n      if (!ok) {\n        array.splice(0, i + 1);\n        i = 0;\n      }\n    }\n  }\n\n  function rebaseHist(hist, change) {\n    var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;\n    rebaseHistArray(hist.done, from, to, diff);\n    rebaseHistArray(hist.undone, from, to, diff);\n  }\n\n  // Utility for applying a change to a line by handle or number,\n  // returning the number and optionally registering the line as\n  // changed.\n  function changeLine(doc, handle, changeType, op) {\n    var no = handle, line = handle;\n    if (typeof handle == \"number\") { line = getLine(doc, clipLine(doc, handle)); }\n    else { no = lineNo(handle); }\n    if (no == null) { return null }\n    if (op(line, no) && doc.cm) { regLineChange(doc.cm, no, changeType); }\n    return line\n  }\n\n  // The document is represented as a BTree consisting of leaves, with\n  // chunk of lines in them, and branches, with up to ten leaves or\n  // other branch nodes below them. The top node is always a branch\n  // node, and is the document object itself (meaning it has\n  // additional methods and properties).\n  //\n  // All nodes have parent links. The tree is used both to go from\n  // line numbers to line objects, and to go from objects to numbers.\n  // It also indexes by height, and is used to convert between height\n  // and line object, and to find the total height of the document.\n  //\n  // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html\n\n  function LeafChunk(lines) {\n    this.lines = lines;\n    this.parent = null;\n    var height = 0;\n    for (var i = 0; i < lines.length; ++i) {\n      lines[i].parent = this;\n      height += lines[i].height;\n    }\n    this.height = height;\n  }\n\n  LeafChunk.prototype = {\n    chunkSize: function() { return this.lines.length },\n\n    // Remove the n lines at offset 'at'.\n    removeInner: function(at, n) {\n      for (var i = at, e = at + n; i < e; ++i) {\n        var line = this.lines[i];\n        this.height -= line.height;\n        cleanUpLine(line);\n        signalLater(line, \"delete\");\n      }\n      this.lines.splice(at, n);\n    },\n\n    // Helper used to collapse a small branch into a single leaf.\n    collapse: function(lines) {\n      lines.push.apply(lines, this.lines);\n    },\n\n    // Insert the given array of lines at offset 'at', count them as\n    // having the given height.\n    insertInner: function(at, lines, height) {\n      this.height += height;\n      this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));\n      for (var i = 0; i < lines.length; ++i) { lines[i].parent = this; }\n    },\n\n    // Used to iterate over a part of the tree.\n    iterN: function(at, n, op) {\n      for (var e = at + n; at < e; ++at)\n        { if (op(this.lines[at])) { return true } }\n    }\n  };\n\n  function BranchChunk(children) {\n    this.children = children;\n    var size = 0, height = 0;\n    for (var i = 0; i < children.length; ++i) {\n      var ch = children[i];\n      size += ch.chunkSize(); height += ch.height;\n      ch.parent = this;\n    }\n    this.size = size;\n    this.height = height;\n    this.parent = null;\n  }\n\n  BranchChunk.prototype = {\n    chunkSize: function() { return this.size },\n\n    removeInner: function(at, n) {\n      this.size -= n;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var rm = Math.min(n, sz - at), oldHeight = child.height;\n          child.removeInner(at, rm);\n          this.height -= oldHeight - child.height;\n          if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }\n          if ((n -= rm) == 0) { break }\n          at = 0;\n        } else { at -= sz; }\n      }\n      // If the result is smaller than 25 lines, ensure that it is a\n      // single leaf node.\n      if (this.size - n < 25 &&\n          (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) {\n        var lines = [];\n        this.collapse(lines);\n        this.children = [new LeafChunk(lines)];\n        this.children[0].parent = this;\n      }\n    },\n\n    collapse: function(lines) {\n      for (var i = 0; i < this.children.length; ++i) { this.children[i].collapse(lines); }\n    },\n\n    insertInner: function(at, lines, height) {\n      this.size += lines.length;\n      this.height += height;\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at <= sz) {\n          child.insertInner(at, lines, height);\n          if (child.lines && child.lines.length > 50) {\n            // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced.\n            // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest.\n            var remaining = child.lines.length % 25 + 25;\n            for (var pos = remaining; pos < child.lines.length;) {\n              var leaf = new LeafChunk(child.lines.slice(pos, pos += 25));\n              child.height -= leaf.height;\n              this.children.splice(++i, 0, leaf);\n              leaf.parent = this;\n            }\n            child.lines = child.lines.slice(0, remaining);\n            this.maybeSpill();\n          }\n          break\n        }\n        at -= sz;\n      }\n    },\n\n    // When a node has grown, check whether it should be split.\n    maybeSpill: function() {\n      if (this.children.length <= 10) { return }\n      var me = this;\n      do {\n        var spilled = me.children.splice(me.children.length - 5, 5);\n        var sibling = new BranchChunk(spilled);\n        if (!me.parent) { // Become the parent node\n          var copy = new BranchChunk(me.children);\n          copy.parent = me;\n          me.children = [copy, sibling];\n          me = copy;\n       } else {\n          me.size -= sibling.size;\n          me.height -= sibling.height;\n          var myIndex = indexOf(me.parent.children, me);\n          me.parent.children.splice(myIndex + 1, 0, sibling);\n        }\n        sibling.parent = me.parent;\n      } while (me.children.length > 10)\n      me.parent.maybeSpill();\n    },\n\n    iterN: function(at, n, op) {\n      for (var i = 0; i < this.children.length; ++i) {\n        var child = this.children[i], sz = child.chunkSize();\n        if (at < sz) {\n          var used = Math.min(n, sz - at);\n          if (child.iterN(at, used, op)) { return true }\n          if ((n -= used) == 0) { break }\n          at = 0;\n        } else { at -= sz; }\n      }\n    }\n  };\n\n  // Line widgets are block elements displayed above or below a line.\n\n  var LineWidget = function(doc, node, options) {\n    if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))\n      { this[opt] = options[opt]; } } }\n    this.doc = doc;\n    this.node = node;\n  };\n\n  LineWidget.prototype.clear = function () {\n    var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);\n    if (no == null || !ws) { return }\n    for (var i = 0; i < ws.length; ++i) { if (ws[i] == this) { ws.splice(i--, 1); } }\n    if (!ws.length) { line.widgets = null; }\n    var height = widgetHeight(this);\n    updateLineHeight(line, Math.max(0, line.height - height));\n    if (cm) {\n      runInOp(cm, function () {\n        adjustScrollWhenAboveVisible(cm, line, -height);\n        regLineChange(cm, no, \"widget\");\n      });\n      signalLater(cm, \"lineWidgetCleared\", cm, this, no);\n    }\n  };\n\n  LineWidget.prototype.changed = function () {\n      var this$1 = this;\n\n    var oldH = this.height, cm = this.doc.cm, line = this.line;\n    this.height = null;\n    var diff = widgetHeight(this) - oldH;\n    if (!diff) { return }\n    if (!lineIsHidden(this.doc, line)) { updateLineHeight(line, line.height + diff); }\n    if (cm) {\n      runInOp(cm, function () {\n        cm.curOp.forceUpdate = true;\n        adjustScrollWhenAboveVisible(cm, line, diff);\n        signalLater(cm, \"lineWidgetChanged\", cm, this$1, lineNo(line));\n      });\n    }\n  };\n  eventMixin(LineWidget);\n\n  function adjustScrollWhenAboveVisible(cm, line, diff) {\n    if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))\n      { addToScrollTop(cm, diff); }\n  }\n\n  function addLineWidget(doc, handle, node, options) {\n    var widget = new LineWidget(doc, node, options);\n    var cm = doc.cm;\n    if (cm && widget.noHScroll) { cm.display.alignWidgets = true; }\n    changeLine(doc, handle, \"widget\", function (line) {\n      var widgets = line.widgets || (line.widgets = []);\n      if (widget.insertAt == null) { widgets.push(widget); }\n      else { widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); }\n      widget.line = line;\n      if (cm && !lineIsHidden(doc, line)) {\n        var aboveVisible = heightAtLine(line) < doc.scrollTop;\n        updateLineHeight(line, line.height + widgetHeight(widget));\n        if (aboveVisible) { addToScrollTop(cm, widget.height); }\n        cm.curOp.forceUpdate = true;\n      }\n      return true\n    });\n    if (cm) { signalLater(cm, \"lineWidgetAdded\", cm, widget, typeof handle == \"number\" ? handle : lineNo(handle)); }\n    return widget\n  }\n\n  // TEXTMARKERS\n\n  // Created with markText and setBookmark methods. A TextMarker is a\n  // handle that can be used to clear or find a marked position in the\n  // document. Line objects hold arrays (markedSpans) containing\n  // {from, to, marker} object pointing to such marker objects, and\n  // indicating that such a marker is present on that line. Multiple\n  // lines may point to the same marker when it spans across lines.\n  // The spans will have null for their from/to properties when the\n  // marker continues beyond the start/end of the line. Markers have\n  // links back to the lines they currently touch.\n\n  // Collapsed markers have unique ids, in order to be able to order\n  // them, which is needed for uniquely determining an outer marker\n  // when they overlap (they may nest, but not partially overlap).\n  var nextMarkerId = 0;\n\n  var TextMarker = function(doc, type) {\n    this.lines = [];\n    this.type = type;\n    this.doc = doc;\n    this.id = ++nextMarkerId;\n  };\n\n  // Clear the marker.\n  TextMarker.prototype.clear = function () {\n    if (this.explicitlyCleared) { return }\n    var cm = this.doc.cm, withOp = cm && !cm.curOp;\n    if (withOp) { startOperation(cm); }\n    if (hasHandler(this, \"clear\")) {\n      var found = this.find();\n      if (found) { signalLater(this, \"clear\", found.from, found.to); }\n    }\n    var min = null, max = null;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this);\n      if (cm && !this.collapsed) { regLineChange(cm, lineNo(line), \"text\"); }\n      else if (cm) {\n        if (span.to != null) { max = lineNo(line); }\n        if (span.from != null) { min = lineNo(line); }\n      }\n      line.markedSpans = removeMarkedSpan(line.markedSpans, span);\n      if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm)\n        { updateLineHeight(line, textHeight(cm.display)); }\n    }\n    if (cm && this.collapsed && !cm.options.lineWrapping) { for (var i$1 = 0; i$1 < this.lines.length; ++i$1) {\n      var visual = visualLine(this.lines[i$1]), len = lineLength(visual);\n      if (len > cm.display.maxLineLength) {\n        cm.display.maxLine = visual;\n        cm.display.maxLineLength = len;\n        cm.display.maxLineChanged = true;\n      }\n    } }\n\n    if (min != null && cm && this.collapsed) { regChange(cm, min, max + 1); }\n    this.lines.length = 0;\n    this.explicitlyCleared = true;\n    if (this.atomic && this.doc.cantEdit) {\n      this.doc.cantEdit = false;\n      if (cm) { reCheckSelection(cm.doc); }\n    }\n    if (cm) { signalLater(cm, \"markerCleared\", cm, this, min, max); }\n    if (withOp) { endOperation(cm); }\n    if (this.parent) { this.parent.clear(); }\n  };\n\n  // Find the position of the marker in the document. Returns a {from,\n  // to} object by default. Side can be passed to get a specific side\n  // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the\n  // Pos objects returned contain a line object, rather than a line\n  // number (used to prevent looking up the same line twice).\n  TextMarker.prototype.find = function (side, lineObj) {\n    if (side == null && this.type == \"bookmark\") { side = 1; }\n    var from, to;\n    for (var i = 0; i < this.lines.length; ++i) {\n      var line = this.lines[i];\n      var span = getMarkedSpanFor(line.markedSpans, this);\n      if (span.from != null) {\n        from = Pos(lineObj ? line : lineNo(line), span.from);\n        if (side == -1) { return from }\n      }\n      if (span.to != null) {\n        to = Pos(lineObj ? line : lineNo(line), span.to);\n        if (side == 1) { return to }\n      }\n    }\n    return from && {from: from, to: to}\n  };\n\n  // Signals that the marker's widget changed, and surrounding layout\n  // should be recomputed.\n  TextMarker.prototype.changed = function () {\n      var this$1 = this;\n\n    var pos = this.find(-1, true), widget = this, cm = this.doc.cm;\n    if (!pos || !cm) { return }\n    runInOp(cm, function () {\n      var line = pos.line, lineN = lineNo(pos.line);\n      var view = findViewForLine(cm, lineN);\n      if (view) {\n        clearLineMeasurementCacheFor(view);\n        cm.curOp.selectionChanged = cm.curOp.forceUpdate = true;\n      }\n      cm.curOp.updateMaxLine = true;\n      if (!lineIsHidden(widget.doc, line) && widget.height != null) {\n        var oldHeight = widget.height;\n        widget.height = null;\n        var dHeight = widgetHeight(widget) - oldHeight;\n        if (dHeight)\n          { updateLineHeight(line, line.height + dHeight); }\n      }\n      signalLater(cm, \"markerChanged\", cm, this$1);\n    });\n  };\n\n  TextMarker.prototype.attachLine = function (line) {\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp;\n      if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)\n        { (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); }\n    }\n    this.lines.push(line);\n  };\n\n  TextMarker.prototype.detachLine = function (line) {\n    this.lines.splice(indexOf(this.lines, line), 1);\n    if (!this.lines.length && this.doc.cm) {\n      var op = this.doc.cm.curOp\n      ;(op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);\n    }\n  };\n  eventMixin(TextMarker);\n\n  // Create a marker, wire it up to the right lines, and\n  function markText(doc, from, to, options, type) {\n    // Shared markers (across linked documents) are handled separately\n    // (markTextShared will call out to this again, once per\n    // document).\n    if (options && options.shared) { return markTextShared(doc, from, to, options, type) }\n    // Ensure we are in an operation.\n    if (doc.cm && !doc.cm.curOp) { return operation(doc.cm, markText)(doc, from, to, options, type) }\n\n    var marker = new TextMarker(doc, type), diff = cmp(from, to);\n    if (options) { copyObj(options, marker, false); }\n    // Don't connect empty markers unless clearWhenEmpty is false\n    if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false)\n      { return marker }\n    if (marker.replacedWith) {\n      // Showing up as a widget implies collapsed (widget replaces text)\n      marker.collapsed = true;\n      marker.widgetNode = eltP(\"span\", [marker.replacedWith], \"CodeMirror-widget\");\n      if (!options.handleMouseEvents) { marker.widgetNode.setAttribute(\"cm-ignore-events\", \"true\"); }\n      if (options.insertLeft) { marker.widgetNode.insertLeft = true; }\n    }\n    if (marker.collapsed) {\n      if (conflictingCollapsedRange(doc, from.line, from, to, marker) ||\n          from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker))\n        { throw new Error(\"Inserting collapsed marker partially overlapping an existing one\") }\n      seeCollapsedSpans();\n    }\n\n    if (marker.addToHistory)\n      { addChangeToHistory(doc, {from: from, to: to, origin: \"markText\"}, doc.sel, NaN); }\n\n    var curLine = from.line, cm = doc.cm, updateMaxLine;\n    doc.iter(curLine, to.line + 1, function (line) {\n      if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine)\n        { updateMaxLine = true; }\n      if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); }\n      addMarkedSpan(line, new MarkedSpan(marker,\n                                         curLine == from.line ? from.ch : null,\n                                         curLine == to.line ? to.ch : null));\n      ++curLine;\n    });\n    // lineIsHidden depends on the presence of the spans, so needs a second pass\n    if (marker.collapsed) { doc.iter(from.line, to.line + 1, function (line) {\n      if (lineIsHidden(doc, line)) { updateLineHeight(line, 0); }\n    }); }\n\n    if (marker.clearOnEnter) { on(marker, \"beforeCursorEnter\", function () { return marker.clear(); }); }\n\n    if (marker.readOnly) {\n      seeReadOnlySpans();\n      if (doc.history.done.length || doc.history.undone.length)\n        { doc.clearHistory(); }\n    }\n    if (marker.collapsed) {\n      marker.id = ++nextMarkerId;\n      marker.atomic = true;\n    }\n    if (cm) {\n      // Sync editor state\n      if (updateMaxLine) { cm.curOp.updateMaxLine = true; }\n      if (marker.collapsed)\n        { regChange(cm, from.line, to.line + 1); }\n      else if (marker.className || marker.startStyle || marker.endStyle || marker.css ||\n               marker.attributes || marker.title)\n        { for (var i = from.line; i <= to.line; i++) { regLineChange(cm, i, \"text\"); } }\n      if (marker.atomic) { reCheckSelection(cm.doc); }\n      signalLater(cm, \"markerAdded\", cm, marker);\n    }\n    return marker\n  }\n\n  // SHARED TEXTMARKERS\n\n  // A shared marker spans multiple linked documents. It is\n  // implemented as a meta-marker-object controlling multiple normal\n  // markers.\n  var SharedTextMarker = function(markers, primary) {\n    this.markers = markers;\n    this.primary = primary;\n    for (var i = 0; i < markers.length; ++i)\n      { markers[i].parent = this; }\n  };\n\n  SharedTextMarker.prototype.clear = function () {\n    if (this.explicitlyCleared) { return }\n    this.explicitlyCleared = true;\n    for (var i = 0; i < this.markers.length; ++i)\n      { this.markers[i].clear(); }\n    signalLater(this, \"clear\");\n  };\n\n  SharedTextMarker.prototype.find = function (side, lineObj) {\n    return this.primary.find(side, lineObj)\n  };\n  eventMixin(SharedTextMarker);\n\n  function markTextShared(doc, from, to, options, type) {\n    options = copyObj(options);\n    options.shared = false;\n    var markers = [markText(doc, from, to, options, type)], primary = markers[0];\n    var widget = options.widgetNode;\n    linkedDocs(doc, function (doc) {\n      if (widget) { options.widgetNode = widget.cloneNode(true); }\n      markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));\n      for (var i = 0; i < doc.linked.length; ++i)\n        { if (doc.linked[i].isParent) { return } }\n      primary = lst(markers);\n    });\n    return new SharedTextMarker(markers, primary)\n  }\n\n  function findSharedMarkers(doc) {\n    return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), function (m) { return m.parent; })\n  }\n\n  function copySharedMarkers(doc, markers) {\n    for (var i = 0; i < markers.length; i++) {\n      var marker = markers[i], pos = marker.find();\n      var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to);\n      if (cmp(mFrom, mTo)) {\n        var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type);\n        marker.markers.push(subMark);\n        subMark.parent = marker;\n      }\n    }\n  }\n\n  function detachSharedMarkers(markers) {\n    var loop = function ( i ) {\n      var marker = markers[i], linked = [marker.primary.doc];\n      linkedDocs(marker.primary.doc, function (d) { return linked.push(d); });\n      for (var j = 0; j < marker.markers.length; j++) {\n        var subMarker = marker.markers[j];\n        if (indexOf(linked, subMarker.doc) == -1) {\n          subMarker.parent = null;\n          marker.markers.splice(j--, 1);\n        }\n      }\n    };\n\n    for (var i = 0; i < markers.length; i++) loop( i );\n  }\n\n  var nextDocId = 0;\n  var Doc = function(text, mode, firstLine, lineSep, direction) {\n    if (!(this instanceof Doc)) { return new Doc(text, mode, firstLine, lineSep, direction) }\n    if (firstLine == null) { firstLine = 0; }\n\n    BranchChunk.call(this, [new LeafChunk([new Line(\"\", null)])]);\n    this.first = firstLine;\n    this.scrollTop = this.scrollLeft = 0;\n    this.cantEdit = false;\n    this.cleanGeneration = 1;\n    this.modeFrontier = this.highlightFrontier = firstLine;\n    var start = Pos(firstLine, 0);\n    this.sel = simpleSelection(start);\n    this.history = new History(null);\n    this.id = ++nextDocId;\n    this.modeOption = mode;\n    this.lineSep = lineSep;\n    this.direction = (direction == \"rtl\") ? \"rtl\" : \"ltr\";\n    this.extend = false;\n\n    if (typeof text == \"string\") { text = this.splitLines(text); }\n    updateDoc(this, {from: start, to: start, text: text});\n    setSelection(this, simpleSelection(start), sel_dontScroll);\n  };\n\n  Doc.prototype = createObj(BranchChunk.prototype, {\n    constructor: Doc,\n    // Iterate over the document. Supports two forms -- with only one\n    // argument, it calls that for each line in the document. With\n    // three, it iterates over the range given by the first two (with\n    // the second being non-inclusive).\n    iter: function(from, to, op) {\n      if (op) { this.iterN(from - this.first, to - from, op); }\n      else { this.iterN(this.first, this.first + this.size, from); }\n    },\n\n    // Non-public interface for adding and removing lines.\n    insert: function(at, lines) {\n      var height = 0;\n      for (var i = 0; i < lines.length; ++i) { height += lines[i].height; }\n      this.insertInner(at - this.first, lines, height);\n    },\n    remove: function(at, n) { this.removeInner(at - this.first, n); },\n\n    // From here, the methods are part of the public interface. Most\n    // are also available from CodeMirror (editor) instances.\n\n    getValue: function(lineSep) {\n      var lines = getLines(this, this.first, this.first + this.size);\n      if (lineSep === false) { return lines }\n      return lines.join(lineSep || this.lineSeparator())\n    },\n    setValue: docMethodOp(function(code) {\n      var top = Pos(this.first, 0), last = this.first + this.size - 1;\n      makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),\n                        text: this.splitLines(code), origin: \"setValue\", full: true}, true);\n      if (this.cm) { scrollToCoords(this.cm, 0, 0); }\n      setSelection(this, simpleSelection(top), sel_dontScroll);\n    }),\n    replaceRange: function(code, from, to, origin) {\n      from = clipPos(this, from);\n      to = to ? clipPos(this, to) : from;\n      replaceRange(this, code, from, to, origin);\n    },\n    getRange: function(from, to, lineSep) {\n      var lines = getBetween(this, clipPos(this, from), clipPos(this, to));\n      if (lineSep === false) { return lines }\n      return lines.join(lineSep || this.lineSeparator())\n    },\n\n    getLine: function(line) {var l = this.getLineHandle(line); return l && l.text},\n\n    getLineHandle: function(line) {if (isLine(this, line)) { return getLine(this, line) }},\n    getLineNumber: function(line) {return lineNo(line)},\n\n    getLineHandleVisualStart: function(line) {\n      if (typeof line == \"number\") { line = getLine(this, line); }\n      return visualLine(line)\n    },\n\n    lineCount: function() {return this.size},\n    firstLine: function() {return this.first},\n    lastLine: function() {return this.first + this.size - 1},\n\n    clipPos: function(pos) {return clipPos(this, pos)},\n\n    getCursor: function(start) {\n      var range = this.sel.primary(), pos;\n      if (start == null || start == \"head\") { pos = range.head; }\n      else if (start == \"anchor\") { pos = range.anchor; }\n      else if (start == \"end\" || start == \"to\" || start === false) { pos = range.to(); }\n      else { pos = range.from(); }\n      return pos\n    },\n    listSelections: function() { return this.sel.ranges },\n    somethingSelected: function() {return this.sel.somethingSelected()},\n\n    setCursor: docMethodOp(function(line, ch, options) {\n      setSimpleSelection(this, clipPos(this, typeof line == \"number\" ? Pos(line, ch || 0) : line), null, options);\n    }),\n    setSelection: docMethodOp(function(anchor, head, options) {\n      setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options);\n    }),\n    extendSelection: docMethodOp(function(head, other, options) {\n      extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);\n    }),\n    extendSelections: docMethodOp(function(heads, options) {\n      extendSelections(this, clipPosArray(this, heads), options);\n    }),\n    extendSelectionsBy: docMethodOp(function(f, options) {\n      var heads = map(this.sel.ranges, f);\n      extendSelections(this, clipPosArray(this, heads), options);\n    }),\n    setSelections: docMethodOp(function(ranges, primary, options) {\n      if (!ranges.length) { return }\n      var out = [];\n      for (var i = 0; i < ranges.length; i++)\n        { out[i] = new Range(clipPos(this, ranges[i].anchor),\n                           clipPos(this, ranges[i].head)); }\n      if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }\n      setSelection(this, normalizeSelection(this.cm, out, primary), options);\n    }),\n    addSelection: docMethodOp(function(anchor, head, options) {\n      var ranges = this.sel.ranges.slice(0);\n      ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor)));\n      setSelection(this, normalizeSelection(this.cm, ranges, ranges.length - 1), options);\n    }),\n\n    getSelection: function(lineSep) {\n      var ranges = this.sel.ranges, lines;\n      for (var i = 0; i < ranges.length; i++) {\n        var sel = getBetween(this, ranges[i].from(), ranges[i].to());\n        lines = lines ? lines.concat(sel) : sel;\n      }\n      if (lineSep === false) { return lines }\n      else { return lines.join(lineSep || this.lineSeparator()) }\n    },\n    getSelections: function(lineSep) {\n      var parts = [], ranges = this.sel.ranges;\n      for (var i = 0; i < ranges.length; i++) {\n        var sel = getBetween(this, ranges[i].from(), ranges[i].to());\n        if (lineSep !== false) { sel = sel.join(lineSep || this.lineSeparator()); }\n        parts[i] = sel;\n      }\n      return parts\n    },\n    replaceSelection: function(code, collapse, origin) {\n      var dup = [];\n      for (var i = 0; i < this.sel.ranges.length; i++)\n        { dup[i] = code; }\n      this.replaceSelections(dup, collapse, origin || \"+input\");\n    },\n    replaceSelections: docMethodOp(function(code, collapse, origin) {\n      var changes = [], sel = this.sel;\n      for (var i = 0; i < sel.ranges.length; i++) {\n        var range = sel.ranges[i];\n        changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin};\n      }\n      var newSel = collapse && collapse != \"end\" && computeReplacedSel(this, changes, collapse);\n      for (var i$1 = changes.length - 1; i$1 >= 0; i$1--)\n        { makeChange(this, changes[i$1]); }\n      if (newSel) { setSelectionReplaceHistory(this, newSel); }\n      else if (this.cm) { ensureCursorVisible(this.cm); }\n    }),\n    undo: docMethodOp(function() {makeChangeFromHistory(this, \"undo\");}),\n    redo: docMethodOp(function() {makeChangeFromHistory(this, \"redo\");}),\n    undoSelection: docMethodOp(function() {makeChangeFromHistory(this, \"undo\", true);}),\n    redoSelection: docMethodOp(function() {makeChangeFromHistory(this, \"redo\", true);}),\n\n    setExtending: function(val) {this.extend = val;},\n    getExtending: function() {return this.extend},\n\n    historySize: function() {\n      var hist = this.history, done = 0, undone = 0;\n      for (var i = 0; i < hist.done.length; i++) { if (!hist.done[i].ranges) { ++done; } }\n      for (var i$1 = 0; i$1 < hist.undone.length; i$1++) { if (!hist.undone[i$1].ranges) { ++undone; } }\n      return {undo: done, redo: undone}\n    },\n    clearHistory: function() {\n      var this$1 = this;\n\n      this.history = new History(this.history.maxGeneration);\n      linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true);\n    },\n\n    markClean: function() {\n      this.cleanGeneration = this.changeGeneration(true);\n    },\n    changeGeneration: function(forceSplit) {\n      if (forceSplit)\n        { this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; }\n      return this.history.generation\n    },\n    isClean: function (gen) {\n      return this.history.generation == (gen || this.cleanGeneration)\n    },\n\n    getHistory: function() {\n      return {done: copyHistoryArray(this.history.done),\n              undone: copyHistoryArray(this.history.undone)}\n    },\n    setHistory: function(histData) {\n      var hist = this.history = new History(this.history.maxGeneration);\n      hist.done = copyHistoryArray(histData.done.slice(0), null, true);\n      hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);\n    },\n\n    setGutterMarker: docMethodOp(function(line, gutterID, value) {\n      return changeLine(this, line, \"gutter\", function (line) {\n        var markers = line.gutterMarkers || (line.gutterMarkers = {});\n        markers[gutterID] = value;\n        if (!value && isEmpty(markers)) { line.gutterMarkers = null; }\n        return true\n      })\n    }),\n\n    clearGutter: docMethodOp(function(gutterID) {\n      var this$1 = this;\n\n      this.iter(function (line) {\n        if (line.gutterMarkers && line.gutterMarkers[gutterID]) {\n          changeLine(this$1, line, \"gutter\", function () {\n            line.gutterMarkers[gutterID] = null;\n            if (isEmpty(line.gutterMarkers)) { line.gutterMarkers = null; }\n            return true\n          });\n        }\n      });\n    }),\n\n    lineInfo: function(line) {\n      var n;\n      if (typeof line == \"number\") {\n        if (!isLine(this, line)) { return null }\n        n = line;\n        line = getLine(this, line);\n        if (!line) { return null }\n      } else {\n        n = lineNo(line);\n        if (n == null) { return null }\n      }\n      return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,\n              textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,\n              widgets: line.widgets}\n    },\n\n    addLineClass: docMethodOp(function(handle, where, cls) {\n      return changeLine(this, handle, where == \"gutter\" ? \"gutter\" : \"class\", function (line) {\n        var prop = where == \"text\" ? \"textClass\"\n                 : where == \"background\" ? \"bgClass\"\n                 : where == \"gutter\" ? \"gutterClass\" : \"wrapClass\";\n        if (!line[prop]) { line[prop] = cls; }\n        else if (classTest(cls).test(line[prop])) { return false }\n        else { line[prop] += \" \" + cls; }\n        return true\n      })\n    }),\n    removeLineClass: docMethodOp(function(handle, where, cls) {\n      return changeLine(this, handle, where == \"gutter\" ? \"gutter\" : \"class\", function (line) {\n        var prop = where == \"text\" ? \"textClass\"\n                 : where == \"background\" ? \"bgClass\"\n                 : where == \"gutter\" ? \"gutterClass\" : \"wrapClass\";\n        var cur = line[prop];\n        if (!cur) { return false }\n        else if (cls == null) { line[prop] = null; }\n        else {\n          var found = cur.match(classTest(cls));\n          if (!found) { return false }\n          var end = found.index + found[0].length;\n          line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? \"\" : \" \") + cur.slice(end) || null;\n        }\n        return true\n      })\n    }),\n\n    addLineWidget: docMethodOp(function(handle, node, options) {\n      return addLineWidget(this, handle, node, options)\n    }),\n    removeLineWidget: function(widget) { widget.clear(); },\n\n    markText: function(from, to, options) {\n      return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || \"range\")\n    },\n    setBookmark: function(pos, options) {\n      var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),\n                      insertLeft: options && options.insertLeft,\n                      clearWhenEmpty: false, shared: options && options.shared,\n                      handleMouseEvents: options && options.handleMouseEvents};\n      pos = clipPos(this, pos);\n      return markText(this, pos, pos, realOpts, \"bookmark\")\n    },\n    findMarksAt: function(pos) {\n      pos = clipPos(this, pos);\n      var markers = [], spans = getLine(this, pos.line).markedSpans;\n      if (spans) { for (var i = 0; i < spans.length; ++i) {\n        var span = spans[i];\n        if ((span.from == null || span.from <= pos.ch) &&\n            (span.to == null || span.to >= pos.ch))\n          { markers.push(span.marker.parent || span.marker); }\n      } }\n      return markers\n    },\n    findMarks: function(from, to, filter) {\n      from = clipPos(this, from); to = clipPos(this, to);\n      var found = [], lineNo = from.line;\n      this.iter(from.line, to.line + 1, function (line) {\n        var spans = line.markedSpans;\n        if (spans) { for (var i = 0; i < spans.length; i++) {\n          var span = spans[i];\n          if (!(span.to != null && lineNo == from.line && from.ch >= span.to ||\n                span.from == null && lineNo != from.line ||\n                span.from != null && lineNo == to.line && span.from >= to.ch) &&\n              (!filter || filter(span.marker)))\n            { found.push(span.marker.parent || span.marker); }\n        } }\n        ++lineNo;\n      });\n      return found\n    },\n    getAllMarks: function() {\n      var markers = [];\n      this.iter(function (line) {\n        var sps = line.markedSpans;\n        if (sps) { for (var i = 0; i < sps.length; ++i)\n          { if (sps[i].from != null) { markers.push(sps[i].marker); } } }\n      });\n      return markers\n    },\n\n    posFromIndex: function(off) {\n      var ch, lineNo = this.first, sepSize = this.lineSeparator().length;\n      this.iter(function (line) {\n        var sz = line.text.length + sepSize;\n        if (sz > off) { ch = off; return true }\n        off -= sz;\n        ++lineNo;\n      });\n      return clipPos(this, Pos(lineNo, ch))\n    },\n    indexFromPos: function (coords) {\n      coords = clipPos(this, coords);\n      var index = coords.ch;\n      if (coords.line < this.first || coords.ch < 0) { return 0 }\n      var sepSize = this.lineSeparator().length;\n      this.iter(this.first, coords.line, function (line) { // iter aborts when callback returns a truthy value\n        index += line.text.length + sepSize;\n      });\n      return index\n    },\n\n    copy: function(copyHistory) {\n      var doc = new Doc(getLines(this, this.first, this.first + this.size),\n                        this.modeOption, this.first, this.lineSep, this.direction);\n      doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;\n      doc.sel = this.sel;\n      doc.extend = false;\n      if (copyHistory) {\n        doc.history.undoDepth = this.history.undoDepth;\n        doc.setHistory(this.getHistory());\n      }\n      return doc\n    },\n\n    linkedDoc: function(options) {\n      if (!options) { options = {}; }\n      var from = this.first, to = this.first + this.size;\n      if (options.from != null && options.from > from) { from = options.from; }\n      if (options.to != null && options.to < to) { to = options.to; }\n      var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction);\n      if (options.sharedHist) { copy.history = this.history\n      ; }(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});\n      copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];\n      copySharedMarkers(copy, findSharedMarkers(this));\n      return copy\n    },\n    unlinkDoc: function(other) {\n      if (other instanceof CodeMirror) { other = other.doc; }\n      if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {\n        var link = this.linked[i];\n        if (link.doc != other) { continue }\n        this.linked.splice(i, 1);\n        other.unlinkDoc(this);\n        detachSharedMarkers(findSharedMarkers(this));\n        break\n      } }\n      // If the histories were shared, split them again\n      if (other.history == this.history) {\n        var splitIds = [other.id];\n        linkedDocs(other, function (doc) { return splitIds.push(doc.id); }, true);\n        other.history = new History(null);\n        other.history.done = copyHistoryArray(this.history.done, splitIds);\n        other.history.undone = copyHistoryArray(this.history.undone, splitIds);\n      }\n    },\n    iterLinkedDocs: function(f) {linkedDocs(this, f);},\n\n    getMode: function() {return this.mode},\n    getEditor: function() {return this.cm},\n\n    splitLines: function(str) {\n      if (this.lineSep) { return str.split(this.lineSep) }\n      return splitLinesAuto(str)\n    },\n    lineSeparator: function() { return this.lineSep || \"\\n\" },\n\n    setDirection: docMethodOp(function (dir) {\n      if (dir != \"rtl\") { dir = \"ltr\"; }\n      if (dir == this.direction) { return }\n      this.direction = dir;\n      this.iter(function (line) { return line.order = null; });\n      if (this.cm) { directionChanged(this.cm); }\n    })\n  });\n\n  // Public alias.\n  Doc.prototype.eachLine = Doc.prototype.iter;\n\n  // Kludge to work around strange IE behavior where it'll sometimes\n  // re-fire a series of drag-related events right after the drop (#1551)\n  var lastDrop = 0;\n\n  function onDrop(e) {\n    var cm = this;\n    clearDragCursor(cm);\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))\n      { return }\n    e_preventDefault(e);\n    if (ie) { lastDrop = +new Date; }\n    var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;\n    if (!pos || cm.isReadOnly()) { return }\n    // Might be a file drop, in which case we simply extract the text\n    // and insert it.\n    if (files && files.length && window.FileReader && window.File) {\n      var n = files.length, text = Array(n), read = 0;\n      var markAsReadAndPasteIfAllFilesAreRead = function () {\n        if (++read == n) {\n          operation(cm, function () {\n            pos = clipPos(cm.doc, pos);\n            var change = {from: pos, to: pos,\n                          text: cm.doc.splitLines(\n                              text.filter(function (t) { return t != null; }).join(cm.doc.lineSeparator())),\n                          origin: \"paste\"};\n            makeChange(cm.doc, change);\n            setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change))));\n          })();\n        }\n      };\n      var readTextFromFile = function (file, i) {\n        if (cm.options.allowDropFileTypes &&\n            indexOf(cm.options.allowDropFileTypes, file.type) == -1) {\n          markAsReadAndPasteIfAllFilesAreRead();\n          return\n        }\n        var reader = new FileReader;\n        reader.onerror = function () { return markAsReadAndPasteIfAllFilesAreRead(); };\n        reader.onload = function () {\n          var content = reader.result;\n          if (/[\\x00-\\x08\\x0e-\\x1f]{2}/.test(content)) {\n            markAsReadAndPasteIfAllFilesAreRead();\n            return\n          }\n          text[i] = content;\n          markAsReadAndPasteIfAllFilesAreRead();\n        };\n        reader.readAsText(file);\n      };\n      for (var i = 0; i < files.length; i++) { readTextFromFile(files[i], i); }\n    } else { // Normal drop\n      // Don't do a replace if the drop happened inside of the selected text.\n      if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {\n        cm.state.draggingText(e);\n        // Ensure the editor is re-focused\n        setTimeout(function () { return cm.display.input.focus(); }, 20);\n        return\n      }\n      try {\n        var text$1 = e.dataTransfer.getData(\"Text\");\n        if (text$1) {\n          var selected;\n          if (cm.state.draggingText && !cm.state.draggingText.copy)\n            { selected = cm.listSelections(); }\n          setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));\n          if (selected) { for (var i$1 = 0; i$1 < selected.length; ++i$1)\n            { replaceRange(cm.doc, \"\", selected[i$1].anchor, selected[i$1].head, \"drag\"); } }\n          cm.replaceSelection(text$1, \"around\", \"paste\");\n          cm.display.input.focus();\n        }\n      }\n      catch(e$1){}\n    }\n  }\n\n  function onDragStart(cm, e) {\n    if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return }\n    if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) { return }\n\n    e.dataTransfer.setData(\"Text\", cm.getSelection());\n    e.dataTransfer.effectAllowed = \"copyMove\";\n\n    // Use dummy image instead of default browsers image.\n    // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.\n    if (e.dataTransfer.setDragImage && !safari) {\n      var img = elt(\"img\", null, null, \"position: fixed; left: 0; top: 0;\");\n      img.src = \"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\";\n      if (presto) {\n        img.width = img.height = 1;\n        cm.display.wrapper.appendChild(img);\n        // Force a relayout, or Opera won't use our image for some obscure reason\n        img._top = img.offsetTop;\n      }\n      e.dataTransfer.setDragImage(img, 0, 0);\n      if (presto) { img.parentNode.removeChild(img); }\n    }\n  }\n\n  function onDragOver(cm, e) {\n    var pos = posFromMouse(cm, e);\n    if (!pos) { return }\n    var frag = document.createDocumentFragment();\n    drawSelectionCursor(cm, pos, frag);\n    if (!cm.display.dragCursor) {\n      cm.display.dragCursor = elt(\"div\", null, \"CodeMirror-cursors CodeMirror-dragcursors\");\n      cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);\n    }\n    removeChildrenAndAdd(cm.display.dragCursor, frag);\n  }\n\n  function clearDragCursor(cm) {\n    if (cm.display.dragCursor) {\n      cm.display.lineSpace.removeChild(cm.display.dragCursor);\n      cm.display.dragCursor = null;\n    }\n  }\n\n  // These must be handled carefully, because naively registering a\n  // handler for each editor will cause the editors to never be\n  // garbage collected.\n\n  function forEachCodeMirror(f) {\n    if (!document.getElementsByClassName) { return }\n    var byClass = document.getElementsByClassName(\"CodeMirror\"), editors = [];\n    for (var i = 0; i < byClass.length; i++) {\n      var cm = byClass[i].CodeMirror;\n      if (cm) { editors.push(cm); }\n    }\n    if (editors.length) { editors[0].operation(function () {\n      for (var i = 0; i < editors.length; i++) { f(editors[i]); }\n    }); }\n  }\n\n  var globalsRegistered = false;\n  function ensureGlobalHandlers() {\n    if (globalsRegistered) { return }\n    registerGlobalHandlers();\n    globalsRegistered = true;\n  }\n  function registerGlobalHandlers() {\n    // When the window resizes, we need to refresh active editors.\n    var resizeTimer;\n    on(window, \"resize\", function () {\n      if (resizeTimer == null) { resizeTimer = setTimeout(function () {\n        resizeTimer = null;\n        forEachCodeMirror(onResize);\n      }, 100); }\n    });\n    // When the window loses focus, we want to show the editor as blurred\n    on(window, \"blur\", function () { return forEachCodeMirror(onBlur); });\n  }\n  // Called when the window resizes\n  function onResize(cm) {\n    var d = cm.display;\n    // Might be a text scaling operation, clear size caches.\n    d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;\n    d.scrollbarsClipped = false;\n    cm.setSize();\n  }\n\n  var keyNames = {\n    3: \"Pause\", 8: \"Backspace\", 9: \"Tab\", 13: \"Enter\", 16: \"Shift\", 17: \"Ctrl\", 18: \"Alt\",\n    19: \"Pause\", 20: \"CapsLock\", 27: \"Esc\", 32: \"Space\", 33: \"PageUp\", 34: \"PageDown\", 35: \"End\",\n    36: \"Home\", 37: \"Left\", 38: \"Up\", 39: \"Right\", 40: \"Down\", 44: \"PrintScrn\", 45: \"Insert\",\n    46: \"Delete\", 59: \";\", 61: \"=\", 91: \"Mod\", 92: \"Mod\", 93: \"Mod\",\n    106: \"*\", 107: \"=\", 109: \"-\", 110: \".\", 111: \"/\", 145: \"ScrollLock\",\n    173: \"-\", 186: \";\", 187: \"=\", 188: \",\", 189: \"-\", 190: \".\", 191: \"/\", 192: \"`\", 219: \"[\", 220: \"\\\\\",\n    221: \"]\", 222: \"'\", 63232: \"Up\", 63233: \"Down\", 63234: \"Left\", 63235: \"Right\", 63272: \"Delete\",\n    63273: \"Home\", 63275: \"End\", 63276: \"PageUp\", 63277: \"PageDown\", 63302: \"Insert\"\n  };\n\n  // Number keys\n  for (var i = 0; i < 10; i++) { keyNames[i + 48] = keyNames[i + 96] = String(i); }\n  // Alphabetic keys\n  for (var i$1 = 65; i$1 <= 90; i$1++) { keyNames[i$1] = String.fromCharCode(i$1); }\n  // Function keys\n  for (var i$2 = 1; i$2 <= 12; i$2++) { keyNames[i$2 + 111] = keyNames[i$2 + 63235] = \"F\" + i$2; }\n\n  var keyMap = {};\n\n  keyMap.basic = {\n    \"Left\": \"goCharLeft\", \"Right\": \"goCharRight\", \"Up\": \"goLineUp\", \"Down\": \"goLineDown\",\n    \"End\": \"goLineEnd\", \"Home\": \"goLineStartSmart\", \"PageUp\": \"goPageUp\", \"PageDown\": \"goPageDown\",\n    \"Delete\": \"delCharAfter\", \"Backspace\": \"delCharBefore\", \"Shift-Backspace\": \"delCharBefore\",\n    \"Tab\": \"defaultTab\", \"Shift-Tab\": \"indentAuto\",\n    \"Enter\": \"newlineAndIndent\", \"Insert\": \"toggleOverwrite\",\n    \"Esc\": \"singleSelection\"\n  };\n  // Note that the save and find-related commands aren't defined by\n  // default. User code or addons can define them. Unknown commands\n  // are simply ignored.\n  keyMap.pcDefault = {\n    \"Ctrl-A\": \"selectAll\", \"Ctrl-D\": \"deleteLine\", \"Ctrl-Z\": \"undo\", \"Shift-Ctrl-Z\": \"redo\", \"Ctrl-Y\": \"redo\",\n    \"Ctrl-Home\": \"goDocStart\", \"Ctrl-End\": \"goDocEnd\", \"Ctrl-Up\": \"goLineUp\", \"Ctrl-Down\": \"goLineDown\",\n    \"Ctrl-Left\": \"goGroupLeft\", \"Ctrl-Right\": \"goGroupRight\", \"Alt-Left\": \"goLineStart\", \"Alt-Right\": \"goLineEnd\",\n    \"Ctrl-Backspace\": \"delGroupBefore\", \"Ctrl-Delete\": \"delGroupAfter\", \"Ctrl-S\": \"save\", \"Ctrl-F\": \"find\",\n    \"Ctrl-G\": \"findNext\", \"Shift-Ctrl-G\": \"findPrev\", \"Shift-Ctrl-F\": \"replace\", \"Shift-Ctrl-R\": \"replaceAll\",\n    \"Ctrl-[\": \"indentLess\", \"Ctrl-]\": \"indentMore\",\n    \"Ctrl-U\": \"undoSelection\", \"Shift-Ctrl-U\": \"redoSelection\", \"Alt-U\": \"redoSelection\",\n    \"fallthrough\": \"basic\"\n  };\n  // Very basic readline/emacs-style bindings, which are standard on Mac.\n  keyMap.emacsy = {\n    \"Ctrl-F\": \"goCharRight\", \"Ctrl-B\": \"goCharLeft\", \"Ctrl-P\": \"goLineUp\", \"Ctrl-N\": \"goLineDown\",\n    \"Alt-F\": \"goWordRight\", \"Alt-B\": \"goWordLeft\", \"Ctrl-A\": \"goLineStart\", \"Ctrl-E\": \"goLineEnd\",\n    \"Ctrl-V\": \"goPageDown\", \"Shift-Ctrl-V\": \"goPageUp\", \"Ctrl-D\": \"delCharAfter\", \"Ctrl-H\": \"delCharBefore\",\n    \"Alt-D\": \"delWordAfter\", \"Alt-Backspace\": \"delWordBefore\", \"Ctrl-K\": \"killLine\", \"Ctrl-T\": \"transposeChars\",\n    \"Ctrl-O\": \"openLine\"\n  };\n  keyMap.macDefault = {\n    \"Cmd-A\": \"selectAll\", \"Cmd-D\": \"deleteLine\", \"Cmd-Z\": \"undo\", \"Shift-Cmd-Z\": \"redo\", \"Cmd-Y\": \"redo\",\n    \"Cmd-Home\": \"goDocStart\", \"Cmd-Up\": \"goDocStart\", \"Cmd-End\": \"goDocEnd\", \"Cmd-Down\": \"goDocEnd\", \"Alt-Left\": \"goGroupLeft\",\n    \"Alt-Right\": \"goGroupRight\", \"Cmd-Left\": \"goLineLeft\", \"Cmd-Right\": \"goLineRight\", \"Alt-Backspace\": \"delGroupBefore\",\n    \"Ctrl-Alt-Backspace\": \"delGroupAfter\", \"Alt-Delete\": \"delGroupAfter\", \"Cmd-S\": \"save\", \"Cmd-F\": \"find\",\n    \"Cmd-G\": \"findNext\", \"Shift-Cmd-G\": \"findPrev\", \"Cmd-Alt-F\": \"replace\", \"Shift-Cmd-Alt-F\": \"replaceAll\",\n    \"Cmd-[\": \"indentLess\", \"Cmd-]\": \"indentMore\", \"Cmd-Backspace\": \"delWrappedLineLeft\", \"Cmd-Delete\": \"delWrappedLineRight\",\n    \"Cmd-U\": \"undoSelection\", \"Shift-Cmd-U\": \"redoSelection\", \"Ctrl-Up\": \"goDocStart\", \"Ctrl-Down\": \"goDocEnd\",\n    \"fallthrough\": [\"basic\", \"emacsy\"]\n  };\n  keyMap[\"default\"] = mac ? keyMap.macDefault : keyMap.pcDefault;\n\n  // KEYMAP DISPATCH\n\n  function normalizeKeyName(name) {\n    var parts = name.split(/-(?!$)/);\n    name = parts[parts.length - 1];\n    var alt, ctrl, shift, cmd;\n    for (var i = 0; i < parts.length - 1; i++) {\n      var mod = parts[i];\n      if (/^(cmd|meta|m)$/i.test(mod)) { cmd = true; }\n      else if (/^a(lt)?$/i.test(mod)) { alt = true; }\n      else if (/^(c|ctrl|control)$/i.test(mod)) { ctrl = true; }\n      else if (/^s(hift)?$/i.test(mod)) { shift = true; }\n      else { throw new Error(\"Unrecognized modifier name: \" + mod) }\n    }\n    if (alt) { name = \"Alt-\" + name; }\n    if (ctrl) { name = \"Ctrl-\" + name; }\n    if (cmd) { name = \"Cmd-\" + name; }\n    if (shift) { name = \"Shift-\" + name; }\n    return name\n  }\n\n  // This is a kludge to keep keymaps mostly working as raw objects\n  // (backwards compatibility) while at the same time support features\n  // like normalization and multi-stroke key bindings. It compiles a\n  // new normalized keymap, and then updates the old object to reflect\n  // this.\n  function normalizeKeyMap(keymap) {\n    var copy = {};\n    for (var keyname in keymap) { if (keymap.hasOwnProperty(keyname)) {\n      var value = keymap[keyname];\n      if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) { continue }\n      if (value == \"...\") { delete keymap[keyname]; continue }\n\n      var keys = map(keyname.split(\" \"), normalizeKeyName);\n      for (var i = 0; i < keys.length; i++) {\n        var val = (void 0), name = (void 0);\n        if (i == keys.length - 1) {\n          name = keys.join(\" \");\n          val = value;\n        } else {\n          name = keys.slice(0, i + 1).join(\" \");\n          val = \"...\";\n        }\n        var prev = copy[name];\n        if (!prev) { copy[name] = val; }\n        else if (prev != val) { throw new Error(\"Inconsistent bindings for \" + name) }\n      }\n      delete keymap[keyname];\n    } }\n    for (var prop in copy) { keymap[prop] = copy[prop]; }\n    return keymap\n  }\n\n  function lookupKey(key, map, handle, context) {\n    map = getKeyMap(map);\n    var found = map.call ? map.call(key, context) : map[key];\n    if (found === false) { return \"nothing\" }\n    if (found === \"...\") { return \"multi\" }\n    if (found != null && handle(found)) { return \"handled\" }\n\n    if (map.fallthrough) {\n      if (Object.prototype.toString.call(map.fallthrough) != \"[object Array]\")\n        { return lookupKey(key, map.fallthrough, handle, context) }\n      for (var i = 0; i < map.fallthrough.length; i++) {\n        var result = lookupKey(key, map.fallthrough[i], handle, context);\n        if (result) { return result }\n      }\n    }\n  }\n\n  // Modifier key presses don't count as 'real' key presses for the\n  // purpose of keymap fallthrough.\n  function isModifierKey(value) {\n    var name = typeof value == \"string\" ? value : keyNames[value.keyCode];\n    return name == \"Ctrl\" || name == \"Alt\" || name == \"Shift\" || name == \"Mod\"\n  }\n\n  function addModifierNames(name, event, noShift) {\n    var base = name;\n    if (event.altKey && base != \"Alt\") { name = \"Alt-\" + name; }\n    if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != \"Ctrl\") { name = \"Ctrl-\" + name; }\n    if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != \"Cmd\") { name = \"Cmd-\" + name; }\n    if (!noShift && event.shiftKey && base != \"Shift\") { name = \"Shift-\" + name; }\n    return name\n  }\n\n  // Look up the name of a key as indicated by an event object.\n  function keyName(event, noShift) {\n    if (presto && event.keyCode == 34 && event[\"char\"]) { return false }\n    var name = keyNames[event.keyCode];\n    if (name == null || event.altGraphKey) { return false }\n    // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,\n    // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)\n    if (event.keyCode == 3 && event.code) { name = event.code; }\n    return addModifierNames(name, event, noShift)\n  }\n\n  function getKeyMap(val) {\n    return typeof val == \"string\" ? keyMap[val] : val\n  }\n\n  // Helper for deleting text near the selection(s), used to implement\n  // backspace, delete, and similar functionality.\n  function deleteNearSelection(cm, compute) {\n    var ranges = cm.doc.sel.ranges, kill = [];\n    // Build up a set of ranges to kill first, merging overlapping\n    // ranges.\n    for (var i = 0; i < ranges.length; i++) {\n      var toKill = compute(ranges[i]);\n      while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) {\n        var replaced = kill.pop();\n        if (cmp(replaced.from, toKill.from) < 0) {\n          toKill.from = replaced.from;\n          break\n        }\n      }\n      kill.push(toKill);\n    }\n    // Next, remove those actual ranges.\n    runInOp(cm, function () {\n      for (var i = kill.length - 1; i >= 0; i--)\n        { replaceRange(cm.doc, \"\", kill[i].from, kill[i].to, \"+delete\"); }\n      ensureCursorVisible(cm);\n    });\n  }\n\n  function moveCharLogically(line, ch, dir) {\n    var target = skipExtendingChars(line.text, ch + dir, dir);\n    return target < 0 || target > line.text.length ? null : target\n  }\n\n  function moveLogically(line, start, dir) {\n    var ch = moveCharLogically(line, start.ch, dir);\n    return ch == null ? null : new Pos(start.line, ch, dir < 0 ? \"after\" : \"before\")\n  }\n\n  function endOfLine(visually, cm, lineObj, lineNo, dir) {\n    if (visually) {\n      if (cm.doc.direction == \"rtl\") { dir = -dir; }\n      var order = getOrder(lineObj, cm.doc.direction);\n      if (order) {\n        var part = dir < 0 ? lst(order) : order[0];\n        var moveInStorageOrder = (dir < 0) == (part.level == 1);\n        var sticky = moveInStorageOrder ? \"after\" : \"before\";\n        var ch;\n        // With a wrapped rtl chunk (possibly spanning multiple bidi parts),\n        // it could be that the last bidi part is not on the last visual line,\n        // since visual lines contain content order-consecutive chunks.\n        // Thus, in rtl, we are looking for the first (content-order) character\n        // in the rtl chunk that is on the last line (that is, the same line\n        // as the last (content-order) character).\n        if (part.level > 0 || cm.doc.direction == \"rtl\") {\n          var prep = prepareMeasureForLine(cm, lineObj);\n          ch = dir < 0 ? lineObj.text.length - 1 : 0;\n          var targetTop = measureCharPrepared(cm, prep, ch).top;\n          ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch);\n          if (sticky == \"before\") { ch = moveCharLogically(lineObj, ch, 1); }\n        } else { ch = dir < 0 ? part.to : part.from; }\n        return new Pos(lineNo, ch, sticky)\n      }\n    }\n    return new Pos(lineNo, dir < 0 ? lineObj.text.length : 0, dir < 0 ? \"before\" : \"after\")\n  }\n\n  function moveVisually(cm, line, start, dir) {\n    var bidi = getOrder(line, cm.doc.direction);\n    if (!bidi) { return moveLogically(line, start, dir) }\n    if (start.ch >= line.text.length) {\n      start.ch = line.text.length;\n      start.sticky = \"before\";\n    } else if (start.ch <= 0) {\n      start.ch = 0;\n      start.sticky = \"after\";\n    }\n    var partPos = getBidiPartAt(bidi, start.ch, start.sticky), part = bidi[partPos];\n    if (cm.doc.direction == \"ltr\" && part.level % 2 == 0 && (dir > 0 ? part.to > start.ch : part.from < start.ch)) {\n      // Case 1: We move within an ltr part in an ltr editor. Even with wrapped lines,\n      // nothing interesting happens.\n      return moveLogically(line, start, dir)\n    }\n\n    var mv = function (pos, dir) { return moveCharLogically(line, pos instanceof Pos ? pos.ch : pos, dir); };\n    var prep;\n    var getWrappedLineExtent = function (ch) {\n      if (!cm.options.lineWrapping) { return {begin: 0, end: line.text.length} }\n      prep = prep || prepareMeasureForLine(cm, line);\n      return wrappedLineExtentChar(cm, line, prep, ch)\n    };\n    var wrappedLineExtent = getWrappedLineExtent(start.sticky == \"before\" ? mv(start, -1) : start.ch);\n\n    if (cm.doc.direction == \"rtl\" || part.level == 1) {\n      var moveInStorageOrder = (part.level == 1) == (dir < 0);\n      var ch = mv(start, moveInStorageOrder ? 1 : -1);\n      if (ch != null && (!moveInStorageOrder ? ch >= part.from && ch >= wrappedLineExtent.begin : ch <= part.to && ch <= wrappedLineExtent.end)) {\n        // Case 2: We move within an rtl part or in an rtl editor on the same visual line\n        var sticky = moveInStorageOrder ? \"before\" : \"after\";\n        return new Pos(start.line, ch, sticky)\n      }\n    }\n\n    // Case 3: Could not move within this bidi part in this visual line, so leave\n    // the current bidi part\n\n    var searchInVisualLine = function (partPos, dir, wrappedLineExtent) {\n      var getRes = function (ch, moveInStorageOrder) { return moveInStorageOrder\n        ? new Pos(start.line, mv(ch, 1), \"before\")\n        : new Pos(start.line, ch, \"after\"); };\n\n      for (; partPos >= 0 && partPos < bidi.length; partPos += dir) {\n        var part = bidi[partPos];\n        var moveInStorageOrder = (dir > 0) == (part.level != 1);\n        var ch = moveInStorageOrder ? wrappedLineExtent.begin : mv(wrappedLineExtent.end, -1);\n        if (part.from <= ch && ch < part.to) { return getRes(ch, moveInStorageOrder) }\n        ch = moveInStorageOrder ? part.from : mv(part.to, -1);\n        if (wrappedLineExtent.begin <= ch && ch < wrappedLineExtent.end) { return getRes(ch, moveInStorageOrder) }\n      }\n    };\n\n    // Case 3a: Look for other bidi parts on the same visual line\n    var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent);\n    if (res) { return res }\n\n    // Case 3b: Look for other bidi parts on the next visual line\n    var nextCh = dir > 0 ? wrappedLineExtent.end : mv(wrappedLineExtent.begin, -1);\n    if (nextCh != null && !(dir > 0 && nextCh == line.text.length)) {\n      res = searchInVisualLine(dir > 0 ? 0 : bidi.length - 1, dir, getWrappedLineExtent(nextCh));\n      if (res) { return res }\n    }\n\n    // Case 4: Nowhere to move\n    return null\n  }\n\n  // Commands are parameter-less actions that can be performed on an\n  // editor, mostly used for keybindings.\n  var commands = {\n    selectAll: selectAll,\n    singleSelection: function (cm) { return cm.setSelection(cm.getCursor(\"anchor\"), cm.getCursor(\"head\"), sel_dontScroll); },\n    killLine: function (cm) { return deleteNearSelection(cm, function (range) {\n      if (range.empty()) {\n        var len = getLine(cm.doc, range.head.line).text.length;\n        if (range.head.ch == len && range.head.line < cm.lastLine())\n          { return {from: range.head, to: Pos(range.head.line + 1, 0)} }\n        else\n          { return {from: range.head, to: Pos(range.head.line, len)} }\n      } else {\n        return {from: range.from(), to: range.to()}\n      }\n    }); },\n    deleteLine: function (cm) { return deleteNearSelection(cm, function (range) { return ({\n      from: Pos(range.from().line, 0),\n      to: clipPos(cm.doc, Pos(range.to().line + 1, 0))\n    }); }); },\n    delLineLeft: function (cm) { return deleteNearSelection(cm, function (range) { return ({\n      from: Pos(range.from().line, 0), to: range.from()\n    }); }); },\n    delWrappedLineLeft: function (cm) { return deleteNearSelection(cm, function (range) {\n      var top = cm.charCoords(range.head, \"div\").top + 5;\n      var leftPos = cm.coordsChar({left: 0, top: top}, \"div\");\n      return {from: leftPos, to: range.from()}\n    }); },\n    delWrappedLineRight: function (cm) { return deleteNearSelection(cm, function (range) {\n      var top = cm.charCoords(range.head, \"div\").top + 5;\n      var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\");\n      return {from: range.from(), to: rightPos }\n    }); },\n    undo: function (cm) { return cm.undo(); },\n    redo: function (cm) { return cm.redo(); },\n    undoSelection: function (cm) { return cm.undoSelection(); },\n    redoSelection: function (cm) { return cm.redoSelection(); },\n    goDocStart: function (cm) { return cm.extendSelection(Pos(cm.firstLine(), 0)); },\n    goDocEnd: function (cm) { return cm.extendSelection(Pos(cm.lastLine())); },\n    goLineStart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStart(cm, range.head.line); },\n      {origin: \"+move\", bias: 1}\n    ); },\n    goLineStartSmart: function (cm) { return cm.extendSelectionsBy(function (range) { return lineStartSmart(cm, range.head); },\n      {origin: \"+move\", bias: 1}\n    ); },\n    goLineEnd: function (cm) { return cm.extendSelectionsBy(function (range) { return lineEnd(cm, range.head.line); },\n      {origin: \"+move\", bias: -1}\n    ); },\n    goLineRight: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, \"div\")\n    }, sel_move); },\n    goLineLeft: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      return cm.coordsChar({left: 0, top: top}, \"div\")\n    }, sel_move); },\n    goLineLeftSmart: function (cm) { return cm.extendSelectionsBy(function (range) {\n      var top = cm.cursorCoords(range.head, \"div\").top + 5;\n      var pos = cm.coordsChar({left: 0, top: top}, \"div\");\n      if (pos.ch < cm.getLine(pos.line).search(/\\S/)) { return lineStartSmart(cm, range.head) }\n      return pos\n    }, sel_move); },\n    goLineUp: function (cm) { return cm.moveV(-1, \"line\"); },\n    goLineDown: function (cm) { return cm.moveV(1, \"line\"); },\n    goPageUp: function (cm) { return cm.moveV(-1, \"page\"); },\n    goPageDown: function (cm) { return cm.moveV(1, \"page\"); },\n    goCharLeft: function (cm) { return cm.moveH(-1, \"char\"); },\n    goCharRight: function (cm) { return cm.moveH(1, \"char\"); },\n    goColumnLeft: function (cm) { return cm.moveH(-1, \"column\"); },\n    goColumnRight: function (cm) { return cm.moveH(1, \"column\"); },\n    goWordLeft: function (cm) { return cm.moveH(-1, \"word\"); },\n    goGroupRight: function (cm) { return cm.moveH(1, \"group\"); },\n    goGroupLeft: function (cm) { return cm.moveH(-1, \"group\"); },\n    goWordRight: function (cm) { return cm.moveH(1, \"word\"); },\n    delCharBefore: function (cm) { return cm.deleteH(-1, \"char\"); },\n    delCharAfter: function (cm) { return cm.deleteH(1, \"char\"); },\n    delWordBefore: function (cm) { return cm.deleteH(-1, \"word\"); },\n    delWordAfter: function (cm) { return cm.deleteH(1, \"word\"); },\n    delGroupBefore: function (cm) { return cm.deleteH(-1, \"group\"); },\n    delGroupAfter: function (cm) { return cm.deleteH(1, \"group\"); },\n    indentAuto: function (cm) { return cm.indentSelection(\"smart\"); },\n    indentMore: function (cm) { return cm.indentSelection(\"add\"); },\n    indentLess: function (cm) { return cm.indentSelection(\"subtract\"); },\n    insertTab: function (cm) { return cm.replaceSelection(\"\\t\"); },\n    insertSoftTab: function (cm) {\n      var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize;\n      for (var i = 0; i < ranges.length; i++) {\n        var pos = ranges[i].from();\n        var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize);\n        spaces.push(spaceStr(tabSize - col % tabSize));\n      }\n      cm.replaceSelections(spaces);\n    },\n    defaultTab: function (cm) {\n      if (cm.somethingSelected()) { cm.indentSelection(\"add\"); }\n      else { cm.execCommand(\"insertTab\"); }\n    },\n    // Swap the two chars left and right of each selection's head.\n    // Move cursor behind the two swapped characters afterwards.\n    //\n    // Doesn't consider line feeds a character.\n    // Doesn't scan more than one line above to find a character.\n    // Doesn't do anything on an empty line.\n    // Doesn't do anything with non-empty selections.\n    transposeChars: function (cm) { return runInOp(cm, function () {\n      var ranges = cm.listSelections(), newSel = [];\n      for (var i = 0; i < ranges.length; i++) {\n        if (!ranges[i].empty()) { continue }\n        var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text;\n        if (line) {\n          if (cur.ch == line.length) { cur = new Pos(cur.line, cur.ch - 1); }\n          if (cur.ch > 0) {\n            cur = new Pos(cur.line, cur.ch + 1);\n            cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2),\n                            Pos(cur.line, cur.ch - 2), cur, \"+transpose\");\n          } else if (cur.line > cm.doc.first) {\n            var prev = getLine(cm.doc, cur.line - 1).text;\n            if (prev) {\n              cur = new Pos(cur.line, 1);\n              cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +\n                              prev.charAt(prev.length - 1),\n                              Pos(cur.line - 1, prev.length - 1), cur, \"+transpose\");\n            }\n          }\n        }\n        newSel.push(new Range(cur, cur));\n      }\n      cm.setSelections(newSel);\n    }); },\n    newlineAndIndent: function (cm) { return runInOp(cm, function () {\n      var sels = cm.listSelections();\n      for (var i = sels.length - 1; i >= 0; i--)\n        { cm.replaceRange(cm.doc.lineSeparator(), sels[i].anchor, sels[i].head, \"+input\"); }\n      sels = cm.listSelections();\n      for (var i$1 = 0; i$1 < sels.length; i$1++)\n        { cm.indentLine(sels[i$1].from().line, null, true); }\n      ensureCursorVisible(cm);\n    }); },\n    openLine: function (cm) { return cm.replaceSelection(\"\\n\", \"start\"); },\n    toggleOverwrite: function (cm) { return cm.toggleOverwrite(); }\n  };\n\n\n  function lineStart(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLine(line);\n    if (visual != line) { lineN = lineNo(visual); }\n    return endOfLine(true, cm, visual, lineN, 1)\n  }\n  function lineEnd(cm, lineN) {\n    var line = getLine(cm.doc, lineN);\n    var visual = visualLineEnd(line);\n    if (visual != line) { lineN = lineNo(visual); }\n    return endOfLine(true, cm, line, lineN, -1)\n  }\n  function lineStartSmart(cm, pos) {\n    var start = lineStart(cm, pos.line);\n    var line = getLine(cm.doc, start.line);\n    var order = getOrder(line, cm.doc.direction);\n    if (!order || order[0].level == 0) {\n      var firstNonWS = Math.max(start.ch, line.text.search(/\\S/));\n      var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;\n      return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)\n    }\n    return start\n  }\n\n  // Run a handler that was bound to a key.\n  function doHandleBinding(cm, bound, dropShift) {\n    if (typeof bound == \"string\") {\n      bound = commands[bound];\n      if (!bound) { return false }\n    }\n    // Ensure previous input has been read, so that the handler sees a\n    // consistent view of the document\n    cm.display.input.ensurePolled();\n    var prevShift = cm.display.shift, done = false;\n    try {\n      if (cm.isReadOnly()) { cm.state.suppressEdits = true; }\n      if (dropShift) { cm.display.shift = false; }\n      done = bound(cm) != Pass;\n    } finally {\n      cm.display.shift = prevShift;\n      cm.state.suppressEdits = false;\n    }\n    return done\n  }\n\n  function lookupKeyForEditor(cm, name, handle) {\n    for (var i = 0; i < cm.state.keyMaps.length; i++) {\n      var result = lookupKey(name, cm.state.keyMaps[i], handle, cm);\n      if (result) { return result }\n    }\n    return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm))\n      || lookupKey(name, cm.options.keyMap, handle, cm)\n  }\n\n  // Note that, despite the name, this function is also used to check\n  // for bound mouse clicks.\n\n  var stopSeq = new Delayed;\n\n  function dispatchKey(cm, name, e, handle) {\n    var seq = cm.state.keySeq;\n    if (seq) {\n      if (isModifierKey(name)) { return \"handled\" }\n      if (/\\'$/.test(name))\n        { cm.state.keySeq = null; }\n      else\n        { stopSeq.set(50, function () {\n          if (cm.state.keySeq == seq) {\n            cm.state.keySeq = null;\n            cm.display.input.reset();\n          }\n        }); }\n      if (dispatchKeyInner(cm, seq + \" \" + name, e, handle)) { return true }\n    }\n    return dispatchKeyInner(cm, name, e, handle)\n  }\n\n  function dispatchKeyInner(cm, name, e, handle) {\n    var result = lookupKeyForEditor(cm, name, handle);\n\n    if (result == \"multi\")\n      { cm.state.keySeq = name; }\n    if (result == \"handled\")\n      { signalLater(cm, \"keyHandled\", cm, name, e); }\n\n    if (result == \"handled\" || result == \"multi\") {\n      e_preventDefault(e);\n      restartBlink(cm);\n    }\n\n    return !!result\n  }\n\n  // Handle a key from the keydown event.\n  function handleKeyBinding(cm, e) {\n    var name = keyName(e, true);\n    if (!name) { return false }\n\n    if (e.shiftKey && !cm.state.keySeq) {\n      // First try to resolve full name (including 'Shift-'). Failing\n      // that, see if there is a cursor-motion command (starting with\n      // 'go') bound to the keyname without 'Shift-'.\n      return dispatchKey(cm, \"Shift-\" + name, e, function (b) { return doHandleBinding(cm, b, true); })\n          || dispatchKey(cm, name, e, function (b) {\n               if (typeof b == \"string\" ? /^go[A-Z]/.test(b) : b.motion)\n                 { return doHandleBinding(cm, b) }\n             })\n    } else {\n      return dispatchKey(cm, name, e, function (b) { return doHandleBinding(cm, b); })\n    }\n  }\n\n  // Handle a key from the keypress event\n  function handleCharBinding(cm, e, ch) {\n    return dispatchKey(cm, \"'\" + ch + \"'\", e, function (b) { return doHandleBinding(cm, b, true); })\n  }\n\n  var lastStoppedKey = null;\n  function onKeyDown(e) {\n    var cm = this;\n    if (e.target && e.target != cm.display.input.getField()) { return }\n    cm.curOp.focus = activeElt();\n    if (signalDOMEvent(cm, e)) { return }\n    // IE does strange things with escape.\n    if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; }\n    var code = e.keyCode;\n    cm.display.shift = code == 16 || e.shiftKey;\n    var handled = handleKeyBinding(cm, e);\n    if (presto) {\n      lastStoppedKey = handled ? code : null;\n      // Opera has no cut event... we try to at least catch the key combo\n      if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey))\n        { cm.replaceSelection(\"\", null, \"cut\"); }\n    }\n    if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand)\n      { document.execCommand(\"cut\"); }\n\n    // Turn mouse into crosshair when Alt is held on Mac.\n    if (code == 18 && !/\\bCodeMirror-crosshair\\b/.test(cm.display.lineDiv.className))\n      { showCrossHair(cm); }\n  }\n\n  function showCrossHair(cm) {\n    var lineDiv = cm.display.lineDiv;\n    addClass(lineDiv, \"CodeMirror-crosshair\");\n\n    function up(e) {\n      if (e.keyCode == 18 || !e.altKey) {\n        rmClass(lineDiv, \"CodeMirror-crosshair\");\n        off(document, \"keyup\", up);\n        off(document, \"mouseover\", up);\n      }\n    }\n    on(document, \"keyup\", up);\n    on(document, \"mouseover\", up);\n  }\n\n  function onKeyUp(e) {\n    if (e.keyCode == 16) { this.doc.sel.shift = false; }\n    signalDOMEvent(this, e);\n  }\n\n  function onKeyPress(e) {\n    var cm = this;\n    if (e.target && e.target != cm.display.input.getField()) { return }\n    if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) { return }\n    var keyCode = e.keyCode, charCode = e.charCode;\n    if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return}\n    if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) { return }\n    var ch = String.fromCharCode(charCode == null ? keyCode : charCode);\n    // Some browsers fire keypress events for backspace\n    if (ch == \"\\x08\") { return }\n    if (handleCharBinding(cm, e, ch)) { return }\n    cm.display.input.onKeyPress(e);\n  }\n\n  var DOUBLECLICK_DELAY = 400;\n\n  var PastClick = function(time, pos, button) {\n    this.time = time;\n    this.pos = pos;\n    this.button = button;\n  };\n\n  PastClick.prototype.compare = function (time, pos, button) {\n    return this.time + DOUBLECLICK_DELAY > time &&\n      cmp(pos, this.pos) == 0 && button == this.button\n  };\n\n  var lastClick, lastDoubleClick;\n  function clickRepeat(pos, button) {\n    var now = +new Date;\n    if (lastDoubleClick && lastDoubleClick.compare(now, pos, button)) {\n      lastClick = lastDoubleClick = null;\n      return \"triple\"\n    } else if (lastClick && lastClick.compare(now, pos, button)) {\n      lastDoubleClick = new PastClick(now, pos, button);\n      lastClick = null;\n      return \"double\"\n    } else {\n      lastClick = new PastClick(now, pos, button);\n      lastDoubleClick = null;\n      return \"single\"\n    }\n  }\n\n  // A mouse down can be a single click, double click, triple click,\n  // start of selection drag, start of text drag, new cursor\n  // (ctrl-click), rectangle drag (alt-drag), or xwin\n  // middle-click-paste. Or it might be a click on something we should\n  // not interfere with, such as a scrollbar or widget.\n  function onMouseDown(e) {\n    var cm = this, display = cm.display;\n    if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) { return }\n    display.input.ensurePolled();\n    display.shift = e.shiftKey;\n\n    if (eventInWidget(display, e)) {\n      if (!webkit) {\n        // Briefly turn off draggability, to allow widgets to do\n        // normal dragging things.\n        display.scroller.draggable = false;\n        setTimeout(function () { return display.scroller.draggable = true; }, 100);\n      }\n      return\n    }\n    if (clickInGutter(cm, e)) { return }\n    var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : \"single\";\n    window.focus();\n\n    // #3261: make sure, that we're not starting a second selection\n    if (button == 1 && cm.state.selectingText)\n      { cm.state.selectingText(e); }\n\n    if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }\n\n    if (button == 1) {\n      if (pos) { leftButtonDown(cm, pos, repeat, e); }\n      else if (e_target(e) == display.scroller) { e_preventDefault(e); }\n    } else if (button == 2) {\n      if (pos) { extendSelection(cm.doc, pos); }\n      setTimeout(function () { return display.input.focus(); }, 20);\n    } else if (button == 3) {\n      if (captureRightClick) { cm.display.input.onContextMenu(e); }\n      else { delayBlurEvent(cm); }\n    }\n  }\n\n  function handleMappedButton(cm, button, pos, repeat, event) {\n    var name = \"Click\";\n    if (repeat == \"double\") { name = \"Double\" + name; }\n    else if (repeat == \"triple\") { name = \"Triple\" + name; }\n    name = (button == 1 ? \"Left\" : button == 2 ? \"Middle\" : \"Right\") + name;\n\n    return dispatchKey(cm,  addModifierNames(name, event), event, function (bound) {\n      if (typeof bound == \"string\") { bound = commands[bound]; }\n      if (!bound) { return false }\n      var done = false;\n      try {\n        if (cm.isReadOnly()) { cm.state.suppressEdits = true; }\n        done = bound(cm, pos) != Pass;\n      } finally {\n        cm.state.suppressEdits = false;\n      }\n      return done\n    })\n  }\n\n  function configureMouse(cm, repeat, event) {\n    var option = cm.getOption(\"configureMouse\");\n    var value = option ? option(cm, repeat, event) : {};\n    if (value.unit == null) {\n      var rect = chromeOS ? event.shiftKey && event.metaKey : event.altKey;\n      value.unit = rect ? \"rectangle\" : repeat == \"single\" ? \"char\" : repeat == \"double\" ? \"word\" : \"line\";\n    }\n    if (value.extend == null || cm.doc.extend) { value.extend = cm.doc.extend || event.shiftKey; }\n    if (value.addNew == null) { value.addNew = mac ? event.metaKey : event.ctrlKey; }\n    if (value.moveOnDrag == null) { value.moveOnDrag = !(mac ? event.altKey : event.ctrlKey); }\n    return value\n  }\n\n  function leftButtonDown(cm, pos, repeat, event) {\n    if (ie) { setTimeout(bind(ensureFocus, cm), 0); }\n    else { cm.curOp.focus = activeElt(); }\n\n    var behavior = configureMouse(cm, repeat, event);\n\n    var sel = cm.doc.sel, contained;\n    if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&\n        repeat == \"single\" && (contained = sel.contains(pos)) > -1 &&\n        (cmp((contained = sel.ranges[contained]).from(), pos) < 0 || pos.xRel > 0) &&\n        (cmp(contained.to(), pos) > 0 || pos.xRel < 0))\n      { leftButtonStartDrag(cm, event, pos, behavior); }\n    else\n      { leftButtonSelect(cm, event, pos, behavior); }\n  }\n\n  // Start a text drag. When it ends, see if any dragging actually\n  // happen, and treat as a click if it didn't.\n  function leftButtonStartDrag(cm, event, pos, behavior) {\n    var display = cm.display, moved = false;\n    var dragEnd = operation(cm, function (e) {\n      if (webkit) { display.scroller.draggable = false; }\n      cm.state.draggingText = false;\n      off(display.wrapper.ownerDocument, \"mouseup\", dragEnd);\n      off(display.wrapper.ownerDocument, \"mousemove\", mouseMove);\n      off(display.scroller, \"dragstart\", dragStart);\n      off(display.scroller, \"drop\", dragEnd);\n      if (!moved) {\n        e_preventDefault(e);\n        if (!behavior.addNew)\n          { extendSelection(cm.doc, pos, null, null, behavior.extend); }\n        // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)\n        if ((webkit && !safari) || ie && ie_version == 9)\n          { setTimeout(function () {display.wrapper.ownerDocument.body.focus({preventScroll: true}); display.input.focus();}, 20); }\n        else\n          { display.input.focus(); }\n      }\n    });\n    var mouseMove = function(e2) {\n      moved = moved || Math.abs(event.clientX - e2.clientX) + Math.abs(event.clientY - e2.clientY) >= 10;\n    };\n    var dragStart = function () { return moved = true; };\n    // Let the drag handler handle this.\n    if (webkit) { display.scroller.draggable = true; }\n    cm.state.draggingText = dragEnd;\n    dragEnd.copy = !behavior.moveOnDrag;\n    // IE's approach to draggable\n    if (display.scroller.dragDrop) { display.scroller.dragDrop(); }\n    on(display.wrapper.ownerDocument, \"mouseup\", dragEnd);\n    on(display.wrapper.ownerDocument, \"mousemove\", mouseMove);\n    on(display.scroller, \"dragstart\", dragStart);\n    on(display.scroller, \"drop\", dragEnd);\n\n    delayBlurEvent(cm);\n    setTimeout(function () { return display.input.focus(); }, 20);\n  }\n\n  function rangeForUnit(cm, pos, unit) {\n    if (unit == \"char\") { return new Range(pos, pos) }\n    if (unit == \"word\") { return cm.findWordAt(pos) }\n    if (unit == \"line\") { return new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))) }\n    var result = unit(cm, pos);\n    return new Range(result.from, result.to)\n  }\n\n  // Normal selection, as opposed to text dragging.\n  function leftButtonSelect(cm, event, start, behavior) {\n    var display = cm.display, doc = cm.doc;\n    e_preventDefault(event);\n\n    var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;\n    if (behavior.addNew && !behavior.extend) {\n      ourIndex = doc.sel.contains(start);\n      if (ourIndex > -1)\n        { ourRange = ranges[ourIndex]; }\n      else\n        { ourRange = new Range(start, start); }\n    } else {\n      ourRange = doc.sel.primary();\n      ourIndex = doc.sel.primIndex;\n    }\n\n    if (behavior.unit == \"rectangle\") {\n      if (!behavior.addNew) { ourRange = new Range(start, start); }\n      start = posFromMouse(cm, event, true, true);\n      ourIndex = -1;\n    } else {\n      var range = rangeForUnit(cm, start, behavior.unit);\n      if (behavior.extend)\n        { ourRange = extendRange(ourRange, range.anchor, range.head, behavior.extend); }\n      else\n        { ourRange = range; }\n    }\n\n    if (!behavior.addNew) {\n      ourIndex = 0;\n      setSelection(doc, new Selection([ourRange], 0), sel_mouse);\n      startSel = doc.sel;\n    } else if (ourIndex == -1) {\n      ourIndex = ranges.length;\n      setSelection(doc, normalizeSelection(cm, ranges.concat([ourRange]), ourIndex),\n                   {scroll: false, origin: \"*mouse\"});\n    } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == \"char\" && !behavior.extend) {\n      setSelection(doc, normalizeSelection(cm, ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),\n                   {scroll: false, origin: \"*mouse\"});\n      startSel = doc.sel;\n    } else {\n      replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);\n    }\n\n    var lastPos = start;\n    function extendTo(pos) {\n      if (cmp(lastPos, pos) == 0) { return }\n      lastPos = pos;\n\n      if (behavior.unit == \"rectangle\") {\n        var ranges = [], tabSize = cm.options.tabSize;\n        var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize);\n        var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize);\n        var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol);\n        for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line));\n             line <= end; line++) {\n          var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize);\n          if (left == right)\n            { ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); }\n          else if (text.length > leftPos)\n            { ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); }\n        }\n        if (!ranges.length) { ranges.push(new Range(start, start)); }\n        setSelection(doc, normalizeSelection(cm, startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex),\n                     {origin: \"*mouse\", scroll: false});\n        cm.scrollIntoView(pos);\n      } else {\n        var oldRange = ourRange;\n        var range = rangeForUnit(cm, pos, behavior.unit);\n        var anchor = oldRange.anchor, head;\n        if (cmp(range.anchor, anchor) > 0) {\n          head = range.head;\n          anchor = minPos(oldRange.from(), range.anchor);\n        } else {\n          head = range.anchor;\n          anchor = maxPos(oldRange.to(), range.head);\n        }\n        var ranges$1 = startSel.ranges.slice(0);\n        ranges$1[ourIndex] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head));\n        setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse);\n      }\n    }\n\n    var editorSize = display.wrapper.getBoundingClientRect();\n    // Used to ensure timeout re-tries don't fire when another extend\n    // happened in the meantime (clearTimeout isn't reliable -- at\n    // least on Chrome, the timeouts still happen even when cleared,\n    // if the clear happens after their scheduled firing time).\n    var counter = 0;\n\n    function extend(e) {\n      var curCount = ++counter;\n      var cur = posFromMouse(cm, e, true, behavior.unit == \"rectangle\");\n      if (!cur) { return }\n      if (cmp(cur, lastPos) != 0) {\n        cm.curOp.focus = activeElt();\n        extendTo(cur);\n        var visible = visibleLines(display, doc);\n        if (cur.line >= visible.to || cur.line < visible.from)\n          { setTimeout(operation(cm, function () {if (counter == curCount) { extend(e); }}), 150); }\n      } else {\n        var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;\n        if (outside) { setTimeout(operation(cm, function () {\n          if (counter != curCount) { return }\n          display.scroller.scrollTop += outside;\n          extend(e);\n        }), 50); }\n      }\n    }\n\n    function done(e) {\n      cm.state.selectingText = false;\n      counter = Infinity;\n      // If e is null or undefined we interpret this as someone trying\n      // to explicitly cancel the selection rather than the user\n      // letting go of the mouse button.\n      if (e) {\n        e_preventDefault(e);\n        display.input.focus();\n      }\n      off(display.wrapper.ownerDocument, \"mousemove\", move);\n      off(display.wrapper.ownerDocument, \"mouseup\", up);\n      doc.history.lastSelOrigin = null;\n    }\n\n    var move = operation(cm, function (e) {\n      if (e.buttons === 0 || !e_button(e)) { done(e); }\n      else { extend(e); }\n    });\n    var up = operation(cm, done);\n    cm.state.selectingText = up;\n    on(display.wrapper.ownerDocument, \"mousemove\", move);\n    on(display.wrapper.ownerDocument, \"mouseup\", up);\n  }\n\n  // Used when mouse-selecting to adjust the anchor to the proper side\n  // of a bidi jump depending on the visual position of the head.\n  function bidiSimplify(cm, range) {\n    var anchor = range.anchor;\n    var head = range.head;\n    var anchorLine = getLine(cm.doc, anchor.line);\n    if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range }\n    var order = getOrder(anchorLine);\n    if (!order) { return range }\n    var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index];\n    if (part.from != anchor.ch && part.to != anchor.ch) { return range }\n    var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1);\n    if (boundary == 0 || boundary == order.length) { return range }\n\n    // Compute the relative visual position of the head compared to the\n    // anchor (<0 is to the left, >0 to the right)\n    var leftSide;\n    if (head.line != anchor.line) {\n      leftSide = (head.line - anchor.line) * (cm.doc.direction == \"ltr\" ? 1 : -1) > 0;\n    } else {\n      var headIndex = getBidiPartAt(order, head.ch, head.sticky);\n      var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1);\n      if (headIndex == boundary - 1 || headIndex == boundary)\n        { leftSide = dir < 0; }\n      else\n        { leftSide = dir > 0; }\n    }\n\n    var usePart = order[boundary + (leftSide ? -1 : 0)];\n    var from = leftSide == (usePart.level == 1);\n    var ch = from ? usePart.from : usePart.to, sticky = from ? \"after\" : \"before\";\n    return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head)\n  }\n\n\n  // Determines whether an event happened in the gutter, and fires the\n  // handlers for the corresponding event.\n  function gutterEvent(cm, e, type, prevent) {\n    var mX, mY;\n    if (e.touches) {\n      mX = e.touches[0].clientX;\n      mY = e.touches[0].clientY;\n    } else {\n      try { mX = e.clientX; mY = e.clientY; }\n      catch(e$1) { return false }\n    }\n    if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }\n    if (prevent) { e_preventDefault(e); }\n\n    var display = cm.display;\n    var lineBox = display.lineDiv.getBoundingClientRect();\n\n    if (mY > lineBox.bottom || !hasHandler(cm, type)) { return e_defaultPrevented(e) }\n    mY -= lineBox.top - display.viewOffset;\n\n    for (var i = 0; i < cm.display.gutterSpecs.length; ++i) {\n      var g = display.gutters.childNodes[i];\n      if (g && g.getBoundingClientRect().right >= mX) {\n        var line = lineAtHeight(cm.doc, mY);\n        var gutter = cm.display.gutterSpecs[i];\n        signal(cm, type, cm, line, gutter.className, e);\n        return e_defaultPrevented(e)\n      }\n    }\n  }\n\n  function clickInGutter(cm, e) {\n    return gutterEvent(cm, e, \"gutterClick\", true)\n  }\n\n  // CONTEXT MENU HANDLING\n\n  // To make the context menu work, we need to briefly unhide the\n  // textarea (making it as unobtrusive as possible) to let the\n  // right-click take effect on it.\n  function onContextMenu(cm, e) {\n    if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) { return }\n    if (signalDOMEvent(cm, e, \"contextmenu\")) { return }\n    if (!captureRightClick) { cm.display.input.onContextMenu(e); }\n  }\n\n  function contextMenuInGutter(cm, e) {\n    if (!hasHandler(cm, \"gutterContextMenu\")) { return false }\n    return gutterEvent(cm, e, \"gutterContextMenu\", false)\n  }\n\n  function themeChanged(cm) {\n    cm.display.wrapper.className = cm.display.wrapper.className.replace(/\\s*cm-s-\\S+/g, \"\") +\n      cm.options.theme.replace(/(^|\\s)\\s*/g, \" cm-s-\");\n    clearCaches(cm);\n  }\n\n  var Init = {toString: function(){return \"CodeMirror.Init\"}};\n\n  var defaults = {};\n  var optionHandlers = {};\n\n  function defineOptions(CodeMirror) {\n    var optionHandlers = CodeMirror.optionHandlers;\n\n    function option(name, deflt, handle, notOnInit) {\n      CodeMirror.defaults[name] = deflt;\n      if (handle) { optionHandlers[name] =\n        notOnInit ? function (cm, val, old) {if (old != Init) { handle(cm, val, old); }} : handle; }\n    }\n\n    CodeMirror.defineOption = option;\n\n    // Passed to option handlers when there is no old value.\n    CodeMirror.Init = Init;\n\n    // These two are, on init, called from the constructor because they\n    // have to be initialized before the editor can start at all.\n    option(\"value\", \"\", function (cm, val) { return cm.setValue(val); }, true);\n    option(\"mode\", null, function (cm, val) {\n      cm.doc.modeOption = val;\n      loadMode(cm);\n    }, true);\n\n    option(\"indentUnit\", 2, loadMode, true);\n    option(\"indentWithTabs\", false);\n    option(\"smartIndent\", true);\n    option(\"tabSize\", 4, function (cm) {\n      resetModeState(cm);\n      clearCaches(cm);\n      regChange(cm);\n    }, true);\n\n    option(\"lineSeparator\", null, function (cm, val) {\n      cm.doc.lineSep = val;\n      if (!val) { return }\n      var newBreaks = [], lineNo = cm.doc.first;\n      cm.doc.iter(function (line) {\n        for (var pos = 0;;) {\n          var found = line.text.indexOf(val, pos);\n          if (found == -1) { break }\n          pos = found + val.length;\n          newBreaks.push(Pos(lineNo, found));\n        }\n        lineNo++;\n      });\n      for (var i = newBreaks.length - 1; i >= 0; i--)\n        { replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }\n    });\n    option(\"specialChars\", /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u200b-\\u200c\\u200e\\u200f\\u2028\\u2029\\ufeff\\ufff9-\\ufffc]/g, function (cm, val, old) {\n      cm.state.specialChars = new RegExp(val.source + (val.test(\"\\t\") ? \"\" : \"|\\t\"), \"g\");\n      if (old != Init) { cm.refresh(); }\n    });\n    option(\"specialCharPlaceholder\", defaultSpecialCharPlaceholder, function (cm) { return cm.refresh(); }, true);\n    option(\"electricChars\", true);\n    option(\"inputStyle\", mobile ? \"contenteditable\" : \"textarea\", function () {\n      throw new Error(\"inputStyle can not (yet) be changed in a running editor\") // FIXME\n    }, true);\n    option(\"spellcheck\", false, function (cm, val) { return cm.getInputField().spellcheck = val; }, true);\n    option(\"autocorrect\", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true);\n    option(\"autocapitalize\", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true);\n    option(\"rtlMoveVisually\", !windows);\n    option(\"wholeLineUpdateBefore\", true);\n\n    option(\"theme\", \"default\", function (cm) {\n      themeChanged(cm);\n      updateGutters(cm);\n    }, true);\n    option(\"keyMap\", \"default\", function (cm, val, old) {\n      var next = getKeyMap(val);\n      var prev = old != Init && getKeyMap(old);\n      if (prev && prev.detach) { prev.detach(cm, next); }\n      if (next.attach) { next.attach(cm, prev || null); }\n    });\n    option(\"extraKeys\", null);\n    option(\"configureMouse\", null);\n\n    option(\"lineWrapping\", false, wrappingChanged, true);\n    option(\"gutters\", [], function (cm, val) {\n      cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers);\n      updateGutters(cm);\n    }, true);\n    option(\"fixedGutter\", true, function (cm, val) {\n      cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + \"px\" : \"0\";\n      cm.refresh();\n    }, true);\n    option(\"coverGutterNextToScrollbar\", false, function (cm) { return updateScrollbars(cm); }, true);\n    option(\"scrollbarStyle\", \"native\", function (cm) {\n      initScrollbars(cm);\n      updateScrollbars(cm);\n      cm.display.scrollbars.setScrollTop(cm.doc.scrollTop);\n      cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft);\n    }, true);\n    option(\"lineNumbers\", false, function (cm, val) {\n      cm.display.gutterSpecs = getGutters(cm.options.gutters, val);\n      updateGutters(cm);\n    }, true);\n    option(\"firstLineNumber\", 1, updateGutters, true);\n    option(\"lineNumberFormatter\", function (integer) { return integer; }, updateGutters, true);\n    option(\"showCursorWhenSelecting\", false, updateSelection, true);\n\n    option(\"resetSelectionOnContextMenu\", true);\n    option(\"lineWiseCopyCut\", true);\n    option(\"pasteLinesPerSelection\", true);\n    option(\"selectionsMayTouch\", false);\n\n    option(\"readOnly\", false, function (cm, val) {\n      if (val == \"nocursor\") {\n        onBlur(cm);\n        cm.display.input.blur();\n      }\n      cm.display.input.readOnlyChanged(val);\n    });\n\n    option(\"screenReaderLabel\", null, function (cm, val) {\n      val = (val === '') ? null : val;\n      cm.display.input.screenReaderLabelChanged(val);\n    });\n\n    option(\"disableInput\", false, function (cm, val) {if (!val) { cm.display.input.reset(); }}, true);\n    option(\"dragDrop\", true, dragDropChanged);\n    option(\"allowDropFileTypes\", null);\n\n    option(\"cursorBlinkRate\", 530);\n    option(\"cursorScrollMargin\", 0);\n    option(\"cursorHeight\", 1, updateSelection, true);\n    option(\"singleCursorHeightPerLine\", true, updateSelection, true);\n    option(\"workTime\", 100);\n    option(\"workDelay\", 100);\n    option(\"flattenSpans\", true, resetModeState, true);\n    option(\"addModeClass\", false, resetModeState, true);\n    option(\"pollInterval\", 100);\n    option(\"undoDepth\", 200, function (cm, val) { return cm.doc.history.undoDepth = val; });\n    option(\"historyEventDelay\", 1250);\n    option(\"viewportMargin\", 10, function (cm) { return cm.refresh(); }, true);\n    option(\"maxHighlightLength\", 10000, resetModeState, true);\n    option(\"moveInputWithCursor\", true, function (cm, val) {\n      if (!val) { cm.display.input.resetPosition(); }\n    });\n\n    option(\"tabindex\", null, function (cm, val) { return cm.display.input.getField().tabIndex = val || \"\"; });\n    option(\"autofocus\", null);\n    option(\"direction\", \"ltr\", function (cm, val) { return cm.doc.setDirection(val); }, true);\n    option(\"phrases\", null);\n  }\n\n  function dragDropChanged(cm, value, old) {\n    var wasOn = old && old != Init;\n    if (!value != !wasOn) {\n      var funcs = cm.display.dragFunctions;\n      var toggle = value ? on : off;\n      toggle(cm.display.scroller, \"dragstart\", funcs.start);\n      toggle(cm.display.scroller, \"dragenter\", funcs.enter);\n      toggle(cm.display.scroller, \"dragover\", funcs.over);\n      toggle(cm.display.scroller, \"dragleave\", funcs.leave);\n      toggle(cm.display.scroller, \"drop\", funcs.drop);\n    }\n  }\n\n  function wrappingChanged(cm) {\n    if (cm.options.lineWrapping) {\n      addClass(cm.display.wrapper, \"CodeMirror-wrap\");\n      cm.display.sizer.style.minWidth = \"\";\n      cm.display.sizerWidth = null;\n    } else {\n      rmClass(cm.display.wrapper, \"CodeMirror-wrap\");\n      findMaxLine(cm);\n    }\n    estimateLineHeights(cm);\n    regChange(cm);\n    clearCaches(cm);\n    setTimeout(function () { return updateScrollbars(cm); }, 100);\n  }\n\n  // A CodeMirror instance represents an editor. This is the object\n  // that user code is usually dealing with.\n\n  function CodeMirror(place, options) {\n    var this$1 = this;\n\n    if (!(this instanceof CodeMirror)) { return new CodeMirror(place, options) }\n\n    this.options = options = options ? copyObj(options) : {};\n    // Determine effective options based on given values and defaults.\n    copyObj(defaults, options, false);\n\n    var doc = options.value;\n    if (typeof doc == \"string\") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); }\n    else if (options.mode) { doc.modeOption = options.mode; }\n    this.doc = doc;\n\n    var input = new CodeMirror.inputStyles[options.inputStyle](this);\n    var display = this.display = new Display(place, doc, input, options);\n    display.wrapper.CodeMirror = this;\n    themeChanged(this);\n    if (options.lineWrapping)\n      { this.display.wrapper.className += \" CodeMirror-wrap\"; }\n    initScrollbars(this);\n\n    this.state = {\n      keyMaps: [],  // stores maps added by addKeyMap\n      overlays: [], // highlighting overlays, as added by addOverlay\n      modeGen: 0,   // bumped when mode/overlay changes, used to invalidate highlighting info\n      overwrite: false,\n      delayingBlurEvent: false,\n      focused: false,\n      suppressEdits: false, // used to disable editing during key handlers when in readOnly mode\n      pasteIncoming: -1, cutIncoming: -1, // help recognize paste/cut edits in input.poll\n      selectingText: false,\n      draggingText: false,\n      highlight: new Delayed(), // stores highlight worker timeout\n      keySeq: null,  // Unfinished key sequence\n      specialChars: null\n    };\n\n    if (options.autofocus && !mobile) { display.input.focus(); }\n\n    // Override magic textarea content restore that IE sometimes does\n    // on our hidden textarea on reload\n    if (ie && ie_version < 11) { setTimeout(function () { return this$1.display.input.reset(true); }, 20); }\n\n    registerEventHandlers(this);\n    ensureGlobalHandlers();\n\n    startOperation(this);\n    this.curOp.forceUpdate = true;\n    attachDoc(this, doc);\n\n    if ((options.autofocus && !mobile) || this.hasFocus())\n      { setTimeout(bind(onFocus, this), 20); }\n    else\n      { onBlur(this); }\n\n    for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))\n      { optionHandlers[opt](this, options[opt], Init); } }\n    maybeUpdateLineNumberWidth(this);\n    if (options.finishInit) { options.finishInit(this); }\n    for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this); }\n    endOperation(this);\n    // Suppress optimizelegibility in Webkit, since it breaks text\n    // measuring on line wrapping boundaries.\n    if (webkit && options.lineWrapping &&\n        getComputedStyle(display.lineDiv).textRendering == \"optimizelegibility\")\n      { display.lineDiv.style.textRendering = \"auto\"; }\n  }\n\n  // The default configuration options.\n  CodeMirror.defaults = defaults;\n  // Functions to run when options are changed.\n  CodeMirror.optionHandlers = optionHandlers;\n\n  // Attach the necessary event handlers when initializing the editor\n  function registerEventHandlers(cm) {\n    var d = cm.display;\n    on(d.scroller, \"mousedown\", operation(cm, onMouseDown));\n    // Older IE's will not fire a second mousedown for a double click\n    if (ie && ie_version < 11)\n      { on(d.scroller, \"dblclick\", operation(cm, function (e) {\n        if (signalDOMEvent(cm, e)) { return }\n        var pos = posFromMouse(cm, e);\n        if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) { return }\n        e_preventDefault(e);\n        var word = cm.findWordAt(pos);\n        extendSelection(cm.doc, word.anchor, word.head);\n      })); }\n    else\n      { on(d.scroller, \"dblclick\", function (e) { return signalDOMEvent(cm, e) || e_preventDefault(e); }); }\n    // Some browsers fire contextmenu *after* opening the menu, at\n    // which point we can't mess with it anymore. Context menu is\n    // handled in onMouseDown for these browsers.\n    on(d.scroller, \"contextmenu\", function (e) { return onContextMenu(cm, e); });\n    on(d.input.getField(), \"contextmenu\", function (e) {\n      if (!d.scroller.contains(e.target)) { onContextMenu(cm, e); }\n    });\n\n    // Used to suppress mouse event handling when a touch happens\n    var touchFinished, prevTouch = {end: 0};\n    function finishTouch() {\n      if (d.activeTouch) {\n        touchFinished = setTimeout(function () { return d.activeTouch = null; }, 1000);\n        prevTouch = d.activeTouch;\n        prevTouch.end = +new Date;\n      }\n    }\n    function isMouseLikeTouchEvent(e) {\n      if (e.touches.length != 1) { return false }\n      var touch = e.touches[0];\n      return touch.radiusX <= 1 && touch.radiusY <= 1\n    }\n    function farAway(touch, other) {\n      if (other.left == null) { return true }\n      var dx = other.left - touch.left, dy = other.top - touch.top;\n      return dx * dx + dy * dy > 20 * 20\n    }\n    on(d.scroller, \"touchstart\", function (e) {\n      if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e) && !clickInGutter(cm, e)) {\n        d.input.ensurePolled();\n        clearTimeout(touchFinished);\n        var now = +new Date;\n        d.activeTouch = {start: now, moved: false,\n                         prev: now - prevTouch.end <= 300 ? prevTouch : null};\n        if (e.touches.length == 1) {\n          d.activeTouch.left = e.touches[0].pageX;\n          d.activeTouch.top = e.touches[0].pageY;\n        }\n      }\n    });\n    on(d.scroller, \"touchmove\", function () {\n      if (d.activeTouch) { d.activeTouch.moved = true; }\n    });\n    on(d.scroller, \"touchend\", function (e) {\n      var touch = d.activeTouch;\n      if (touch && !eventInWidget(d, e) && touch.left != null &&\n          !touch.moved && new Date - touch.start < 300) {\n        var pos = cm.coordsChar(d.activeTouch, \"page\"), range;\n        if (!touch.prev || farAway(touch, touch.prev)) // Single tap\n          { range = new Range(pos, pos); }\n        else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap\n          { range = cm.findWordAt(pos); }\n        else // Triple tap\n          { range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); }\n        cm.setSelection(range.anchor, range.head);\n        cm.focus();\n        e_preventDefault(e);\n      }\n      finishTouch();\n    });\n    on(d.scroller, \"touchcancel\", finishTouch);\n\n    // Sync scrolling between fake scrollbars and real scrollable\n    // area, ensure viewport is updated when scrolling.\n    on(d.scroller, \"scroll\", function () {\n      if (d.scroller.clientHeight) {\n        updateScrollTop(cm, d.scroller.scrollTop);\n        setScrollLeft(cm, d.scroller.scrollLeft, true);\n        signal(cm, \"scroll\", cm);\n      }\n    });\n\n    // Listen to wheel events in order to try and update the viewport on time.\n    on(d.scroller, \"mousewheel\", function (e) { return onScrollWheel(cm, e); });\n    on(d.scroller, \"DOMMouseScroll\", function (e) { return onScrollWheel(cm, e); });\n\n    // Prevent wrapper from ever scrolling\n    on(d.wrapper, \"scroll\", function () { return d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });\n\n    d.dragFunctions = {\n      enter: function (e) {if (!signalDOMEvent(cm, e)) { e_stop(e); }},\n      over: function (e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},\n      start: function (e) { return onDragStart(cm, e); },\n      drop: operation(cm, onDrop),\n      leave: function (e) {if (!signalDOMEvent(cm, e)) { clearDragCursor(cm); }}\n    };\n\n    var inp = d.input.getField();\n    on(inp, \"keyup\", function (e) { return onKeyUp.call(cm, e); });\n    on(inp, \"keydown\", operation(cm, onKeyDown));\n    on(inp, \"keypress\", operation(cm, onKeyPress));\n    on(inp, \"focus\", function (e) { return onFocus(cm, e); });\n    on(inp, \"blur\", function (e) { return onBlur(cm, e); });\n  }\n\n  var initHooks = [];\n  CodeMirror.defineInitHook = function (f) { return initHooks.push(f); };\n\n  // Indent the given line. The how parameter can be \"smart\",\n  // \"add\"/null, \"subtract\", or \"prev\". When aggressive is false\n  // (typically set to true for forced single-line indents), empty\n  // lines are not indented, and places where the mode returns Pass\n  // are left alone.\n  function indentLine(cm, n, how, aggressive) {\n    var doc = cm.doc, state;\n    if (how == null) { how = \"add\"; }\n    if (how == \"smart\") {\n      // Fall back to \"prev\" when the mode doesn't have an indentation\n      // method.\n      if (!doc.mode.indent) { how = \"prev\"; }\n      else { state = getContextBefore(cm, n).state; }\n    }\n\n    var tabSize = cm.options.tabSize;\n    var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);\n    if (line.stateAfter) { line.stateAfter = null; }\n    var curSpaceString = line.text.match(/^\\s*/)[0], indentation;\n    if (!aggressive && !/\\S/.test(line.text)) {\n      indentation = 0;\n      how = \"not\";\n    } else if (how == \"smart\") {\n      indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);\n      if (indentation == Pass || indentation > 150) {\n        if (!aggressive) { return }\n        how = \"prev\";\n      }\n    }\n    if (how == \"prev\") {\n      if (n > doc.first) { indentation = countColumn(getLine(doc, n-1).text, null, tabSize); }\n      else { indentation = 0; }\n    } else if (how == \"add\") {\n      indentation = curSpace + cm.options.indentUnit;\n    } else if (how == \"subtract\") {\n      indentation = curSpace - cm.options.indentUnit;\n    } else if (typeof how == \"number\") {\n      indentation = curSpace + how;\n    }\n    indentation = Math.max(0, indentation);\n\n    var indentString = \"\", pos = 0;\n    if (cm.options.indentWithTabs)\n      { for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += \"\\t\";} }\n    if (pos < indentation) { indentString += spaceStr(indentation - pos); }\n\n    if (indentString != curSpaceString) {\n      replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), \"+input\");\n      line.stateAfter = null;\n      return true\n    } else {\n      // Ensure that, if the cursor was in the whitespace at the start\n      // of the line, it is moved to the end of that space.\n      for (var i$1 = 0; i$1 < doc.sel.ranges.length; i$1++) {\n        var range = doc.sel.ranges[i$1];\n        if (range.head.line == n && range.head.ch < curSpaceString.length) {\n          var pos$1 = Pos(n, curSpaceString.length);\n          replaceOneSelection(doc, i$1, new Range(pos$1, pos$1));\n          break\n        }\n      }\n    }\n  }\n\n  // This will be set to a {lineWise: bool, text: [string]} object, so\n  // that, when pasting, we know what kind of selections the copied\n  // text was made out of.\n  var lastCopied = null;\n\n  function setLastCopied(newLastCopied) {\n    lastCopied = newLastCopied;\n  }\n\n  function applyTextInput(cm, inserted, deleted, sel, origin) {\n    var doc = cm.doc;\n    cm.display.shift = false;\n    if (!sel) { sel = doc.sel; }\n\n    var recent = +new Date - 200;\n    var paste = origin == \"paste\" || cm.state.pasteIncoming > recent;\n    var textLines = splitLinesAuto(inserted), multiPaste = null;\n    // When pasting N lines into N selections, insert one line per selection\n    if (paste && sel.ranges.length > 1) {\n      if (lastCopied && lastCopied.text.join(\"\\n\") == inserted) {\n        if (sel.ranges.length % lastCopied.text.length == 0) {\n          multiPaste = [];\n          for (var i = 0; i < lastCopied.text.length; i++)\n            { multiPaste.push(doc.splitLines(lastCopied.text[i])); }\n        }\n      } else if (textLines.length == sel.ranges.length && cm.options.pasteLinesPerSelection) {\n        multiPaste = map(textLines, function (l) { return [l]; });\n      }\n    }\n\n    var updateInput = cm.curOp.updateInput;\n    // Normal behavior is to insert the new text into every selection\n    for (var i$1 = sel.ranges.length - 1; i$1 >= 0; i$1--) {\n      var range = sel.ranges[i$1];\n      var from = range.from(), to = range.to();\n      if (range.empty()) {\n        if (deleted && deleted > 0) // Handle deletion\n          { from = Pos(from.line, from.ch - deleted); }\n        else if (cm.state.overwrite && !paste) // Handle overwrite\n          { to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); }\n        else if (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join(\"\\n\") == textLines.join(\"\\n\"))\n          { from = to = Pos(from.line, 0); }\n      }\n      var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,\n                         origin: origin || (paste ? \"paste\" : cm.state.cutIncoming > recent ? \"cut\" : \"+input\")};\n      makeChange(cm.doc, changeEvent);\n      signalLater(cm, \"inputRead\", cm, changeEvent);\n    }\n    if (inserted && !paste)\n      { triggerElectric(cm, inserted); }\n\n    ensureCursorVisible(cm);\n    if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; }\n    cm.curOp.typing = true;\n    cm.state.pasteIncoming = cm.state.cutIncoming = -1;\n  }\n\n  function handlePaste(e, cm) {\n    var pasted = e.clipboardData && e.clipboardData.getData(\"Text\");\n    if (pasted) {\n      e.preventDefault();\n      if (!cm.isReadOnly() && !cm.options.disableInput)\n        { runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, \"paste\"); }); }\n      return true\n    }\n  }\n\n  function triggerElectric(cm, inserted) {\n    // When an 'electric' character is inserted, immediately trigger a reindent\n    if (!cm.options.electricChars || !cm.options.smartIndent) { return }\n    var sel = cm.doc.sel;\n\n    for (var i = sel.ranges.length - 1; i >= 0; i--) {\n      var range = sel.ranges[i];\n      if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) { continue }\n      var mode = cm.getModeAt(range.head);\n      var indented = false;\n      if (mode.electricChars) {\n        for (var j = 0; j < mode.electricChars.length; j++)\n          { if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {\n            indented = indentLine(cm, range.head.line, \"smart\");\n            break\n          } }\n      } else if (mode.electricInput) {\n        if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))\n          { indented = indentLine(cm, range.head.line, \"smart\"); }\n      }\n      if (indented) { signalLater(cm, \"electricInput\", cm, range.head.line); }\n    }\n  }\n\n  function copyableRanges(cm) {\n    var text = [], ranges = [];\n    for (var i = 0; i < cm.doc.sel.ranges.length; i++) {\n      var line = cm.doc.sel.ranges[i].head.line;\n      var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};\n      ranges.push(lineRange);\n      text.push(cm.getRange(lineRange.anchor, lineRange.head));\n    }\n    return {text: text, ranges: ranges}\n  }\n\n  function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {\n    field.setAttribute(\"autocorrect\", autocorrect ? \"\" : \"off\");\n    field.setAttribute(\"autocapitalize\", autocapitalize ? \"\" : \"off\");\n    field.setAttribute(\"spellcheck\", !!spellcheck);\n  }\n\n  function hiddenTextarea() {\n    var te = elt(\"textarea\", null, null, \"position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none\");\n    var div = elt(\"div\", [te], null, \"overflow: hidden; position: relative; width: 3px; height: 0px;\");\n    // The textarea is kept positioned near the cursor to prevent the\n    // fact that it'll be scrolled into view on input from scrolling\n    // our fake cursor out of view. On webkit, when wrap=off, paste is\n    // very slow. So make the area wide instead.\n    if (webkit) { te.style.width = \"1000px\"; }\n    else { te.setAttribute(\"wrap\", \"off\"); }\n    // If border: 0; -- iOS fails to open keyboard (issue #1287)\n    if (ios) { te.style.border = \"1px solid black\"; }\n    disableBrowserMagic(te);\n    return div\n  }\n\n  // The publicly visible API. Note that methodOp(f) means\n  // 'wrap f in an operation, performed on its `this` parameter'.\n\n  // This is not the complete set of editor methods. Most of the\n  // methods defined on the Doc type are also injected into\n  // CodeMirror.prototype, for backwards compatibility and\n  // convenience.\n\n  function addEditorMethods(CodeMirror) {\n    var optionHandlers = CodeMirror.optionHandlers;\n\n    var helpers = CodeMirror.helpers = {};\n\n    CodeMirror.prototype = {\n      constructor: CodeMirror,\n      focus: function(){window.focus(); this.display.input.focus();},\n\n      setOption: function(option, value) {\n        var options = this.options, old = options[option];\n        if (options[option] == value && option != \"mode\") { return }\n        options[option] = value;\n        if (optionHandlers.hasOwnProperty(option))\n          { operation(this, optionHandlers[option])(this, value, old); }\n        signal(this, \"optionChange\", this, option);\n      },\n\n      getOption: function(option) {return this.options[option]},\n      getDoc: function() {return this.doc},\n\n      addKeyMap: function(map, bottom) {\n        this.state.keyMaps[bottom ? \"push\" : \"unshift\"](getKeyMap(map));\n      },\n      removeKeyMap: function(map) {\n        var maps = this.state.keyMaps;\n        for (var i = 0; i < maps.length; ++i)\n          { if (maps[i] == map || maps[i].name == map) {\n            maps.splice(i, 1);\n            return true\n          } }\n      },\n\n      addOverlay: methodOp(function(spec, options) {\n        var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);\n        if (mode.startState) { throw new Error(\"Overlays may not be stateful.\") }\n        insertSorted(this.state.overlays,\n                     {mode: mode, modeSpec: spec, opaque: options && options.opaque,\n                      priority: (options && options.priority) || 0},\n                     function (overlay) { return overlay.priority; });\n        this.state.modeGen++;\n        regChange(this);\n      }),\n      removeOverlay: methodOp(function(spec) {\n        var overlays = this.state.overlays;\n        for (var i = 0; i < overlays.length; ++i) {\n          var cur = overlays[i].modeSpec;\n          if (cur == spec || typeof spec == \"string\" && cur.name == spec) {\n            overlays.splice(i, 1);\n            this.state.modeGen++;\n            regChange(this);\n            return\n          }\n        }\n      }),\n\n      indentLine: methodOp(function(n, dir, aggressive) {\n        if (typeof dir != \"string\" && typeof dir != \"number\") {\n          if (dir == null) { dir = this.options.smartIndent ? \"smart\" : \"prev\"; }\n          else { dir = dir ? \"add\" : \"subtract\"; }\n        }\n        if (isLine(this.doc, n)) { indentLine(this, n, dir, aggressive); }\n      }),\n      indentSelection: methodOp(function(how) {\n        var ranges = this.doc.sel.ranges, end = -1;\n        for (var i = 0; i < ranges.length; i++) {\n          var range = ranges[i];\n          if (!range.empty()) {\n            var from = range.from(), to = range.to();\n            var start = Math.max(end, from.line);\n            end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;\n            for (var j = start; j < end; ++j)\n              { indentLine(this, j, how); }\n            var newRanges = this.doc.sel.ranges;\n            if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)\n              { replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); }\n          } else if (range.head.line > end) {\n            indentLine(this, range.head.line, how, true);\n            end = range.head.line;\n            if (i == this.doc.sel.primIndex) { ensureCursorVisible(this); }\n          }\n        }\n      }),\n\n      // Fetch the parser token for a given character. Useful for hacks\n      // that want to inspect the mode state (say, for completion).\n      getTokenAt: function(pos, precise) {\n        return takeToken(this, pos, precise)\n      },\n\n      getLineTokens: function(line, precise) {\n        return takeToken(this, Pos(line), precise, true)\n      },\n\n      getTokenTypeAt: function(pos) {\n        pos = clipPos(this.doc, pos);\n        var styles = getLineStyles(this, getLine(this.doc, pos.line));\n        var before = 0, after = (styles.length - 1) / 2, ch = pos.ch;\n        var type;\n        if (ch == 0) { type = styles[2]; }\n        else { for (;;) {\n          var mid = (before + after) >> 1;\n          if ((mid ? styles[mid * 2 - 1] : 0) >= ch) { after = mid; }\n          else if (styles[mid * 2 + 1] < ch) { before = mid + 1; }\n          else { type = styles[mid * 2 + 2]; break }\n        } }\n        var cut = type ? type.indexOf(\"overlay \") : -1;\n        return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1)\n      },\n\n      getModeAt: function(pos) {\n        var mode = this.doc.mode;\n        if (!mode.innerMode) { return mode }\n        return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode\n      },\n\n      getHelper: function(pos, type) {\n        return this.getHelpers(pos, type)[0]\n      },\n\n      getHelpers: function(pos, type) {\n        var found = [];\n        if (!helpers.hasOwnProperty(type)) { return found }\n        var help = helpers[type], mode = this.getModeAt(pos);\n        if (typeof mode[type] == \"string\") {\n          if (help[mode[type]]) { found.push(help[mode[type]]); }\n        } else if (mode[type]) {\n          for (var i = 0; i < mode[type].length; i++) {\n            var val = help[mode[type][i]];\n            if (val) { found.push(val); }\n          }\n        } else if (mode.helperType && help[mode.helperType]) {\n          found.push(help[mode.helperType]);\n        } else if (help[mode.name]) {\n          found.push(help[mode.name]);\n        }\n        for (var i$1 = 0; i$1 < help._global.length; i$1++) {\n          var cur = help._global[i$1];\n          if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)\n            { found.push(cur.val); }\n        }\n        return found\n      },\n\n      getStateAfter: function(line, precise) {\n        var doc = this.doc;\n        line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);\n        return getContextBefore(this, line + 1, precise).state\n      },\n\n      cursorCoords: function(start, mode) {\n        var pos, range = this.doc.sel.primary();\n        if (start == null) { pos = range.head; }\n        else if (typeof start == \"object\") { pos = clipPos(this.doc, start); }\n        else { pos = start ? range.from() : range.to(); }\n        return cursorCoords(this, pos, mode || \"page\")\n      },\n\n      charCoords: function(pos, mode) {\n        return charCoords(this, clipPos(this.doc, pos), mode || \"page\")\n      },\n\n      coordsChar: function(coords, mode) {\n        coords = fromCoordSystem(this, coords, mode || \"page\");\n        return coordsChar(this, coords.left, coords.top)\n      },\n\n      lineAtHeight: function(height, mode) {\n        height = fromCoordSystem(this, {top: height, left: 0}, mode || \"page\").top;\n        return lineAtHeight(this.doc, height + this.display.viewOffset)\n      },\n      heightAtLine: function(line, mode, includeWidgets) {\n        var end = false, lineObj;\n        if (typeof line == \"number\") {\n          var last = this.doc.first + this.doc.size - 1;\n          if (line < this.doc.first) { line = this.doc.first; }\n          else if (line > last) { line = last; end = true; }\n          lineObj = getLine(this.doc, line);\n        } else {\n          lineObj = line;\n        }\n        return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || \"page\", includeWidgets || end).top +\n          (end ? this.doc.height - heightAtLine(lineObj) : 0)\n      },\n\n      defaultTextHeight: function() { return textHeight(this.display) },\n      defaultCharWidth: function() { return charWidth(this.display) },\n\n      getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}},\n\n      addWidget: function(pos, node, scroll, vert, horiz) {\n        var display = this.display;\n        pos = cursorCoords(this, clipPos(this.doc, pos));\n        var top = pos.bottom, left = pos.left;\n        node.style.position = \"absolute\";\n        node.setAttribute(\"cm-ignore-events\", \"true\");\n        this.display.input.setUneditable(node);\n        display.sizer.appendChild(node);\n        if (vert == \"over\") {\n          top = pos.top;\n        } else if (vert == \"above\" || vert == \"near\") {\n          var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),\n          hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);\n          // Default to positioning above (if specified and possible); otherwise default to positioning below\n          if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)\n            { top = pos.top - node.offsetHeight; }\n          else if (pos.bottom + node.offsetHeight <= vspace)\n            { top = pos.bottom; }\n          if (left + node.offsetWidth > hspace)\n            { left = hspace - node.offsetWidth; }\n        }\n        node.style.top = top + \"px\";\n        node.style.left = node.style.right = \"\";\n        if (horiz == \"right\") {\n          left = display.sizer.clientWidth - node.offsetWidth;\n          node.style.right = \"0px\";\n        } else {\n          if (horiz == \"left\") { left = 0; }\n          else if (horiz == \"middle\") { left = (display.sizer.clientWidth - node.offsetWidth) / 2; }\n          node.style.left = left + \"px\";\n        }\n        if (scroll)\n          { scrollIntoView(this, {left: left, top: top, right: left + node.offsetWidth, bottom: top + node.offsetHeight}); }\n      },\n\n      triggerOnKeyDown: methodOp(onKeyDown),\n      triggerOnKeyPress: methodOp(onKeyPress),\n      triggerOnKeyUp: onKeyUp,\n      triggerOnMouseDown: methodOp(onMouseDown),\n\n      execCommand: function(cmd) {\n        if (commands.hasOwnProperty(cmd))\n          { return commands[cmd].call(null, this) }\n      },\n\n      triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),\n\n      findPosH: function(from, amount, unit, visually) {\n        var dir = 1;\n        if (amount < 0) { dir = -1; amount = -amount; }\n        var cur = clipPos(this.doc, from);\n        for (var i = 0; i < amount; ++i) {\n          cur = findPosH(this.doc, cur, dir, unit, visually);\n          if (cur.hitSide) { break }\n        }\n        return cur\n      },\n\n      moveH: methodOp(function(dir, unit) {\n        var this$1 = this;\n\n        this.extendSelectionsBy(function (range) {\n          if (this$1.display.shift || this$1.doc.extend || range.empty())\n            { return findPosH(this$1.doc, range.head, dir, unit, this$1.options.rtlMoveVisually) }\n          else\n            { return dir < 0 ? range.from() : range.to() }\n        }, sel_move);\n      }),\n\n      deleteH: methodOp(function(dir, unit) {\n        var sel = this.doc.sel, doc = this.doc;\n        if (sel.somethingSelected())\n          { doc.replaceSelection(\"\", null, \"+delete\"); }\n        else\n          { deleteNearSelection(this, function (range) {\n            var other = findPosH(doc, range.head, dir, unit, false);\n            return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}\n          }); }\n      }),\n\n      findPosV: function(from, amount, unit, goalColumn) {\n        var dir = 1, x = goalColumn;\n        if (amount < 0) { dir = -1; amount = -amount; }\n        var cur = clipPos(this.doc, from);\n        for (var i = 0; i < amount; ++i) {\n          var coords = cursorCoords(this, cur, \"div\");\n          if (x == null) { x = coords.left; }\n          else { coords.left = x; }\n          cur = findPosV(this, coords, dir, unit);\n          if (cur.hitSide) { break }\n        }\n        return cur\n      },\n\n      moveV: methodOp(function(dir, unit) {\n        var this$1 = this;\n\n        var doc = this.doc, goals = [];\n        var collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected();\n        doc.extendSelectionsBy(function (range) {\n          if (collapse)\n            { return dir < 0 ? range.from() : range.to() }\n          var headPos = cursorCoords(this$1, range.head, \"div\");\n          if (range.goalColumn != null) { headPos.left = range.goalColumn; }\n          goals.push(headPos.left);\n          var pos = findPosV(this$1, headPos, dir, unit);\n          if (unit == \"page\" && range == doc.sel.primary())\n            { addToScrollTop(this$1, charCoords(this$1, pos, \"div\").top - headPos.top); }\n          return pos\n        }, sel_move);\n        if (goals.length) { for (var i = 0; i < doc.sel.ranges.length; i++)\n          { doc.sel.ranges[i].goalColumn = goals[i]; } }\n      }),\n\n      // Find the word at the given position (as returned by coordsChar).\n      findWordAt: function(pos) {\n        var doc = this.doc, line = getLine(doc, pos.line).text;\n        var start = pos.ch, end = pos.ch;\n        if (line) {\n          var helper = this.getHelper(pos, \"wordChars\");\n          if ((pos.sticky == \"before\" || end == line.length) && start) { --start; } else { ++end; }\n          var startChar = line.charAt(start);\n          var check = isWordChar(startChar, helper)\n            ? function (ch) { return isWordChar(ch, helper); }\n            : /\\s/.test(startChar) ? function (ch) { return /\\s/.test(ch); }\n            : function (ch) { return (!/\\s/.test(ch) && !isWordChar(ch)); };\n          while (start > 0 && check(line.charAt(start - 1))) { --start; }\n          while (end < line.length && check(line.charAt(end))) { ++end; }\n        }\n        return new Range(Pos(pos.line, start), Pos(pos.line, end))\n      },\n\n      toggleOverwrite: function(value) {\n        if (value != null && value == this.state.overwrite) { return }\n        if (this.state.overwrite = !this.state.overwrite)\n          { addClass(this.display.cursorDiv, \"CodeMirror-overwrite\"); }\n        else\n          { rmClass(this.display.cursorDiv, \"CodeMirror-overwrite\"); }\n\n        signal(this, \"overwriteToggle\", this, this.state.overwrite);\n      },\n      hasFocus: function() { return this.display.input.getField() == activeElt() },\n      isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },\n\n      scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }),\n      getScrollInfo: function() {\n        var scroller = this.display.scroller;\n        return {left: scroller.scrollLeft, top: scroller.scrollTop,\n                height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight,\n                width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth,\n                clientHeight: displayHeight(this), clientWidth: displayWidth(this)}\n      },\n\n      scrollIntoView: methodOp(function(range, margin) {\n        if (range == null) {\n          range = {from: this.doc.sel.primary().head, to: null};\n          if (margin == null) { margin = this.options.cursorScrollMargin; }\n        } else if (typeof range == \"number\") {\n          range = {from: Pos(range, 0), to: null};\n        } else if (range.from == null) {\n          range = {from: range, to: null};\n        }\n        if (!range.to) { range.to = range.from; }\n        range.margin = margin || 0;\n\n        if (range.from.line != null) {\n          scrollToRange(this, range);\n        } else {\n          scrollToCoordsRange(this, range.from, range.to, range.margin);\n        }\n      }),\n\n      setSize: methodOp(function(width, height) {\n        var this$1 = this;\n\n        var interpret = function (val) { return typeof val == \"number\" || /^\\d+$/.test(String(val)) ? val + \"px\" : val; };\n        if (width != null) { this.display.wrapper.style.width = interpret(width); }\n        if (height != null) { this.display.wrapper.style.height = interpret(height); }\n        if (this.options.lineWrapping) { clearLineMeasurementCache(this); }\n        var lineNo = this.display.viewFrom;\n        this.doc.iter(lineNo, this.display.viewTo, function (line) {\n          if (line.widgets) { for (var i = 0; i < line.widgets.length; i++)\n            { if (line.widgets[i].noHScroll) { regLineChange(this$1, lineNo, \"widget\"); break } } }\n          ++lineNo;\n        });\n        this.curOp.forceUpdate = true;\n        signal(this, \"refresh\", this);\n      }),\n\n      operation: function(f){return runInOp(this, f)},\n      startOperation: function(){return startOperation(this)},\n      endOperation: function(){return endOperation(this)},\n\n      refresh: methodOp(function() {\n        var oldHeight = this.display.cachedTextHeight;\n        regChange(this);\n        this.curOp.forceUpdate = true;\n        clearCaches(this);\n        scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop);\n        updateGutterSpace(this.display);\n        if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping)\n          { estimateLineHeights(this); }\n        signal(this, \"refresh\", this);\n      }),\n\n      swapDoc: methodOp(function(doc) {\n        var old = this.doc;\n        old.cm = null;\n        // Cancel the current text selection if any (#5821)\n        if (this.state.selectingText) { this.state.selectingText(); }\n        attachDoc(this, doc);\n        clearCaches(this);\n        this.display.input.reset();\n        scrollToCoords(this, doc.scrollLeft, doc.scrollTop);\n        this.curOp.forceScroll = true;\n        signalLater(this, \"swapDoc\", this, old);\n        return old\n      }),\n\n      phrase: function(phraseText) {\n        var phrases = this.options.phrases;\n        return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText\n      },\n\n      getInputField: function(){return this.display.input.getField()},\n      getWrapperElement: function(){return this.display.wrapper},\n      getScrollerElement: function(){return this.display.scroller},\n      getGutterElement: function(){return this.display.gutters}\n    };\n    eventMixin(CodeMirror);\n\n    CodeMirror.registerHelper = function(type, name, value) {\n      if (!helpers.hasOwnProperty(type)) { helpers[type] = CodeMirror[type] = {_global: []}; }\n      helpers[type][name] = value;\n    };\n    CodeMirror.registerGlobalHelper = function(type, name, predicate, value) {\n      CodeMirror.registerHelper(type, name, value);\n      helpers[type]._global.push({pred: predicate, val: value});\n    };\n  }\n\n  // Used for horizontal relative motion. Dir is -1 or 1 (left or\n  // right), unit can be \"char\", \"column\" (like char, but doesn't\n  // cross line boundaries), \"word\" (across next word), or \"group\" (to\n  // the start of next group of word or non-word-non-whitespace\n  // chars). The visually param controls whether, in right-to-left\n  // text, direction 1 means to move towards the next index in the\n  // string, or towards the character to the right of the current\n  // position. The resulting position will have a hitSide=true\n  // property if it reached the end of the document.\n  function findPosH(doc, pos, dir, unit, visually) {\n    var oldPos = pos;\n    var origDir = dir;\n    var lineObj = getLine(doc, pos.line);\n    var lineDir = visually && doc.direction == \"rtl\" ? -dir : dir;\n    function findNextLine() {\n      var l = pos.line + lineDir;\n      if (l < doc.first || l >= doc.first + doc.size) { return false }\n      pos = new Pos(l, pos.ch, pos.sticky);\n      return lineObj = getLine(doc, l)\n    }\n    function moveOnce(boundToLine) {\n      var next;\n      if (visually) {\n        next = moveVisually(doc.cm, lineObj, pos, dir);\n      } else {\n        next = moveLogically(lineObj, pos, dir);\n      }\n      if (next == null) {\n        if (!boundToLine && findNextLine())\n          { pos = endOfLine(visually, doc.cm, lineObj, pos.line, lineDir); }\n        else\n          { return false }\n      } else {\n        pos = next;\n      }\n      return true\n    }\n\n    if (unit == \"char\") {\n      moveOnce();\n    } else if (unit == \"column\") {\n      moveOnce(true);\n    } else if (unit == \"word\" || unit == \"group\") {\n      var sawType = null, group = unit == \"group\";\n      var helper = doc.cm && doc.cm.getHelper(pos, \"wordChars\");\n      for (var first = true;; first = false) {\n        if (dir < 0 && !moveOnce(!first)) { break }\n        var cur = lineObj.text.charAt(pos.ch) || \"\\n\";\n        var type = isWordChar(cur, helper) ? \"w\"\n          : group && cur == \"\\n\" ? \"n\"\n          : !group || /\\s/.test(cur) ? null\n          : \"p\";\n        if (group && !first && !type) { type = \"s\"; }\n        if (sawType && sawType != type) {\n          if (dir < 0) {dir = 1; moveOnce(); pos.sticky = \"after\";}\n          break\n        }\n\n        if (type) { sawType = type; }\n        if (dir > 0 && !moveOnce(!first)) { break }\n      }\n    }\n    var result = skipAtomic(doc, pos, oldPos, origDir, true);\n    if (equalCursorPos(oldPos, result)) { result.hitSide = true; }\n    return result\n  }\n\n  // For relative vertical movement. Dir may be -1 or 1. Unit can be\n  // \"page\" or \"line\". The resulting position will have a hitSide=true\n  // property if it reached the end of the document.\n  function findPosV(cm, pos, dir, unit) {\n    var doc = cm.doc, x = pos.left, y;\n    if (unit == \"page\") {\n      var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);\n      var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);\n      y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;\n\n    } else if (unit == \"line\") {\n      y = dir > 0 ? pos.bottom + 3 : pos.top - 3;\n    }\n    var target;\n    for (;;) {\n      target = coordsChar(cm, x, y);\n      if (!target.outside) { break }\n      if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break }\n      y += dir * 5;\n    }\n    return target\n  }\n\n  // CONTENTEDITABLE INPUT STYLE\n\n  var ContentEditableInput = function(cm) {\n    this.cm = cm;\n    this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;\n    this.polling = new Delayed();\n    this.composing = null;\n    this.gracePeriod = false;\n    this.readDOMTimeout = null;\n  };\n\n  ContentEditableInput.prototype.init = function (display) {\n      var this$1 = this;\n\n    var input = this, cm = input.cm;\n    var div = input.div = display.lineDiv;\n    disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);\n\n    function belongsToInput(e) {\n      for (var t = e.target; t; t = t.parentNode) {\n        if (t == div) { return true }\n        if (/\\bCodeMirror-(?:line)?widget\\b/.test(t.className)) { break }\n      }\n      return false\n    }\n\n    on(div, \"paste\", function (e) {\n      if (!belongsToInput(e) || signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }\n      // IE doesn't fire input events, so we schedule a read for the pasted content in this way\n      if (ie_version <= 11) { setTimeout(operation(cm, function () { return this$1.updateFromDOM(); }), 20); }\n    });\n\n    on(div, \"compositionstart\", function (e) {\n      this$1.composing = {data: e.data, done: false};\n    });\n    on(div, \"compositionupdate\", function (e) {\n      if (!this$1.composing) { this$1.composing = {data: e.data, done: false}; }\n    });\n    on(div, \"compositionend\", function (e) {\n      if (this$1.composing) {\n        if (e.data != this$1.composing.data) { this$1.readFromDOMSoon(); }\n        this$1.composing.done = true;\n      }\n    });\n\n    on(div, \"touchstart\", function () { return input.forceCompositionEnd(); });\n\n    on(div, \"input\", function () {\n      if (!this$1.composing) { this$1.readFromDOMSoon(); }\n    });\n\n    function onCopyCut(e) {\n      if (!belongsToInput(e) || signalDOMEvent(cm, e)) { return }\n      if (cm.somethingSelected()) {\n        setLastCopied({lineWise: false, text: cm.getSelections()});\n        if (e.type == \"cut\") { cm.replaceSelection(\"\", null, \"cut\"); }\n      } else if (!cm.options.lineWiseCopyCut) {\n        return\n      } else {\n        var ranges = copyableRanges(cm);\n        setLastCopied({lineWise: true, text: ranges.text});\n        if (e.type == \"cut\") {\n          cm.operation(function () {\n            cm.setSelections(ranges.ranges, 0, sel_dontScroll);\n            cm.replaceSelection(\"\", null, \"cut\");\n          });\n        }\n      }\n      if (e.clipboardData) {\n        e.clipboardData.clearData();\n        var content = lastCopied.text.join(\"\\n\");\n        // iOS exposes the clipboard API, but seems to discard content inserted into it\n        e.clipboardData.setData(\"Text\", content);\n        if (e.clipboardData.getData(\"Text\") == content) {\n          e.preventDefault();\n          return\n        }\n      }\n      // Old-fashioned briefly-focus-a-textarea hack\n      var kludge = hiddenTextarea(), te = kludge.firstChild;\n      cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);\n      te.value = lastCopied.text.join(\"\\n\");\n      var hadFocus = document.activeElement;\n      selectInput(te);\n      setTimeout(function () {\n        cm.display.lineSpace.removeChild(kludge);\n        hadFocus.focus();\n        if (hadFocus == div) { input.showPrimarySelection(); }\n      }, 50);\n    }\n    on(div, \"copy\", onCopyCut);\n    on(div, \"cut\", onCopyCut);\n  };\n\n  ContentEditableInput.prototype.screenReaderLabelChanged = function (label) {\n    // Label for screenreaders, accessibility\n    if(label) {\n      this.div.setAttribute('aria-label', label);\n    } else {\n      this.div.removeAttribute('aria-label');\n    }\n  };\n\n  ContentEditableInput.prototype.prepareSelection = function () {\n    var result = prepareSelection(this.cm, false);\n    result.focus = document.activeElement == this.div;\n    return result\n  };\n\n  ContentEditableInput.prototype.showSelection = function (info, takeFocus) {\n    if (!info || !this.cm.display.view.length) { return }\n    if (info.focus || takeFocus) { this.showPrimarySelection(); }\n    this.showMultipleSelections(info);\n  };\n\n  ContentEditableInput.prototype.getSelection = function () {\n    return this.cm.display.wrapper.ownerDocument.getSelection()\n  };\n\n  ContentEditableInput.prototype.showPrimarySelection = function () {\n    var sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary();\n    var from = prim.from(), to = prim.to();\n\n    if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {\n      sel.removeAllRanges();\n      return\n    }\n\n    var curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);\n    var curFocus = domToPos(cm, sel.focusNode, sel.focusOffset);\n    if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&\n        cmp(minPos(curAnchor, curFocus), from) == 0 &&\n        cmp(maxPos(curAnchor, curFocus), to) == 0)\n      { return }\n\n    var view = cm.display.view;\n    var start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||\n        {node: view[0].measure.map[2], offset: 0};\n    var end = to.line < cm.display.viewTo && posToDOM(cm, to);\n    if (!end) {\n      var measure = view[view.length - 1].measure;\n      var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map;\n      end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]};\n    }\n\n    if (!start || !end) {\n      sel.removeAllRanges();\n      return\n    }\n\n    var old = sel.rangeCount && sel.getRangeAt(0), rng;\n    try { rng = range(start.node, start.offset, end.offset, end.node); }\n    catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible\n    if (rng) {\n      if (!gecko && cm.state.focused) {\n        sel.collapse(start.node, start.offset);\n        if (!rng.collapsed) {\n          sel.removeAllRanges();\n          sel.addRange(rng);\n        }\n      } else {\n        sel.removeAllRanges();\n        sel.addRange(rng);\n      }\n      if (old && sel.anchorNode == null) { sel.addRange(old); }\n      else if (gecko) { this.startGracePeriod(); }\n    }\n    this.rememberSelection();\n  };\n\n  ContentEditableInput.prototype.startGracePeriod = function () {\n      var this$1 = this;\n\n    clearTimeout(this.gracePeriod);\n    this.gracePeriod = setTimeout(function () {\n      this$1.gracePeriod = false;\n      if (this$1.selectionChanged())\n        { this$1.cm.operation(function () { return this$1.cm.curOp.selectionChanged = true; }); }\n    }, 20);\n  };\n\n  ContentEditableInput.prototype.showMultipleSelections = function (info) {\n    removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);\n    removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);\n  };\n\n  ContentEditableInput.prototype.rememberSelection = function () {\n    var sel = this.getSelection();\n    this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;\n    this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;\n  };\n\n  ContentEditableInput.prototype.selectionInEditor = function () {\n    var sel = this.getSelection();\n    if (!sel.rangeCount) { return false }\n    var node = sel.getRangeAt(0).commonAncestorContainer;\n    return contains(this.div, node)\n  };\n\n  ContentEditableInput.prototype.focus = function () {\n    if (this.cm.options.readOnly != \"nocursor\") {\n      if (!this.selectionInEditor() || document.activeElement != this.div)\n        { this.showSelection(this.prepareSelection(), true); }\n      this.div.focus();\n    }\n  };\n  ContentEditableInput.prototype.blur = function () { this.div.blur(); };\n  ContentEditableInput.prototype.getField = function () { return this.div };\n\n  ContentEditableInput.prototype.supportsTouch = function () { return true };\n\n  ContentEditableInput.prototype.receivedFocus = function () {\n    var input = this;\n    if (this.selectionInEditor())\n      { this.pollSelection(); }\n    else\n      { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }\n\n    function poll() {\n      if (input.cm.state.focused) {\n        input.pollSelection();\n        input.polling.set(input.cm.options.pollInterval, poll);\n      }\n    }\n    this.polling.set(this.cm.options.pollInterval, poll);\n  };\n\n  ContentEditableInput.prototype.selectionChanged = function () {\n    var sel = this.getSelection();\n    return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||\n      sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset\n  };\n\n  ContentEditableInput.prototype.pollSelection = function () {\n    if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }\n    var sel = this.getSelection(), cm = this.cm;\n    // On Android Chrome (version 56, at least), backspacing into an\n    // uneditable block element will put the cursor in that element,\n    // and then, because it's not editable, hide the virtual keyboard.\n    // Because Android doesn't allow us to actually detect backspace\n    // presses in a sane way, this code checks for when that happens\n    // and simulates a backspace press in this case.\n    if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) {\n      this.cm.triggerOnKeyDown({type: \"keydown\", keyCode: 8, preventDefault: Math.abs});\n      this.blur();\n      this.focus();\n      return\n    }\n    if (this.composing) { return }\n    this.rememberSelection();\n    var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset);\n    var head = domToPos(cm, sel.focusNode, sel.focusOffset);\n    if (anchor && head) { runInOp(cm, function () {\n      setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll);\n      if (anchor.bad || head.bad) { cm.curOp.selectionChanged = true; }\n    }); }\n  };\n\n  ContentEditableInput.prototype.pollContent = function () {\n    if (this.readDOMTimeout != null) {\n      clearTimeout(this.readDOMTimeout);\n      this.readDOMTimeout = null;\n    }\n\n    var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();\n    var from = sel.from(), to = sel.to();\n    if (from.ch == 0 && from.line > cm.firstLine())\n      { from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length); }\n    if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())\n      { to = Pos(to.line + 1, 0); }\n    if (from.line < display.viewFrom || to.line > display.viewTo - 1) { return false }\n\n    var fromIndex, fromLine, fromNode;\n    if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {\n      fromLine = lineNo(display.view[0].line);\n      fromNode = display.view[0].node;\n    } else {\n      fromLine = lineNo(display.view[fromIndex].line);\n      fromNode = display.view[fromIndex - 1].node.nextSibling;\n    }\n    var toIndex = findViewIndex(cm, to.line);\n    var toLine, toNode;\n    if (toIndex == display.view.length - 1) {\n      toLine = display.viewTo - 1;\n      toNode = display.lineDiv.lastChild;\n    } else {\n      toLine = lineNo(display.view[toIndex + 1].line) - 1;\n      toNode = display.view[toIndex + 1].node.previousSibling;\n    }\n\n    if (!fromNode) { return false }\n    var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));\n    var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));\n    while (newText.length > 1 && oldText.length > 1) {\n      if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }\n      else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; }\n      else { break }\n    }\n\n    var cutFront = 0, cutEnd = 0;\n    var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length);\n    while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))\n      { ++cutFront; }\n    var newBot = lst(newText), oldBot = lst(oldText);\n    var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),\n                             oldBot.length - (oldText.length == 1 ? cutFront : 0));\n    while (cutEnd < maxCutEnd &&\n           newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))\n      { ++cutEnd; }\n    // Try to move start of change to start of selection if ambiguous\n    if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {\n      while (cutFront && cutFront > from.ch &&\n             newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {\n        cutFront--;\n        cutEnd++;\n      }\n    }\n\n    newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\\u200b+/, \"\");\n    newText[0] = newText[0].slice(cutFront).replace(/\\u200b+$/, \"\");\n\n    var chFrom = Pos(fromLine, cutFront);\n    var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0);\n    if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {\n      replaceRange(cm.doc, newText, chFrom, chTo, \"+input\");\n      return true\n    }\n  };\n\n  ContentEditableInput.prototype.ensurePolled = function () {\n    this.forceCompositionEnd();\n  };\n  ContentEditableInput.prototype.reset = function () {\n    this.forceCompositionEnd();\n  };\n  ContentEditableInput.prototype.forceCompositionEnd = function () {\n    if (!this.composing) { return }\n    clearTimeout(this.readDOMTimeout);\n    this.composing = null;\n    this.updateFromDOM();\n    this.div.blur();\n    this.div.focus();\n  };\n  ContentEditableInput.prototype.readFromDOMSoon = function () {\n      var this$1 = this;\n\n    if (this.readDOMTimeout != null) { return }\n    this.readDOMTimeout = setTimeout(function () {\n      this$1.readDOMTimeout = null;\n      if (this$1.composing) {\n        if (this$1.composing.done) { this$1.composing = null; }\n        else { return }\n      }\n      this$1.updateFromDOM();\n    }, 80);\n  };\n\n  ContentEditableInput.prototype.updateFromDOM = function () {\n      var this$1 = this;\n\n    if (this.cm.isReadOnly() || !this.pollContent())\n      { runInOp(this.cm, function () { return regChange(this$1.cm); }); }\n  };\n\n  ContentEditableInput.prototype.setUneditable = function (node) {\n    node.contentEditable = \"false\";\n  };\n\n  ContentEditableInput.prototype.onKeyPress = function (e) {\n    if (e.charCode == 0 || this.composing) { return }\n    e.preventDefault();\n    if (!this.cm.isReadOnly())\n      { operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); }\n  };\n\n  ContentEditableInput.prototype.readOnlyChanged = function (val) {\n    this.div.contentEditable = String(val != \"nocursor\");\n  };\n\n  ContentEditableInput.prototype.onContextMenu = function () {};\n  ContentEditableInput.prototype.resetPosition = function () {};\n\n  ContentEditableInput.prototype.needsContentAttribute = true;\n\n  function posToDOM(cm, pos) {\n    var view = findViewForLine(cm, pos.line);\n    if (!view || view.hidden) { return null }\n    var line = getLine(cm.doc, pos.line);\n    var info = mapFromLineView(view, line, pos.line);\n\n    var order = getOrder(line, cm.doc.direction), side = \"left\";\n    if (order) {\n      var partPos = getBidiPartAt(order, pos.ch);\n      side = partPos % 2 ? \"right\" : \"left\";\n    }\n    var result = nodeAndOffsetInLineMap(info.map, pos.ch, side);\n    result.offset = result.collapse == \"right\" ? result.end : result.start;\n    return result\n  }\n\n  function isInGutter(node) {\n    for (var scan = node; scan; scan = scan.parentNode)\n      { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }\n    return false\n  }\n\n  function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }\n\n  function domTextBetween(cm, from, to, fromLine, toLine) {\n    var text = \"\", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false;\n    function recognizeMarker(id) { return function (marker) { return marker.id == id; } }\n    function close() {\n      if (closing) {\n        text += lineSep;\n        if (extraLinebreak) { text += lineSep; }\n        closing = extraLinebreak = false;\n      }\n    }\n    function addText(str) {\n      if (str) {\n        close();\n        text += str;\n      }\n    }\n    function walk(node) {\n      if (node.nodeType == 1) {\n        var cmText = node.getAttribute(\"cm-text\");\n        if (cmText) {\n          addText(cmText);\n          return\n        }\n        var markerID = node.getAttribute(\"cm-marker\"), range;\n        if (markerID) {\n          var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));\n          if (found.length && (range = found[0].find(0)))\n            { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); }\n          return\n        }\n        if (node.getAttribute(\"contenteditable\") == \"false\") { return }\n        var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName);\n        if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return }\n\n        if (isBlock) { close(); }\n        for (var i = 0; i < node.childNodes.length; i++)\n          { walk(node.childNodes[i]); }\n\n        if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; }\n        if (isBlock) { closing = true; }\n      } else if (node.nodeType == 3) {\n        addText(node.nodeValue.replace(/\\u200b/g, \"\").replace(/\\u00a0/g, \" \"));\n      }\n    }\n    for (;;) {\n      walk(from);\n      if (from == to) { break }\n      from = from.nextSibling;\n      extraLinebreak = false;\n    }\n    return text\n  }\n\n  function domToPos(cm, node, offset) {\n    var lineNode;\n    if (node == cm.display.lineDiv) {\n      lineNode = cm.display.lineDiv.childNodes[offset];\n      if (!lineNode) { return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) }\n      node = null; offset = 0;\n    } else {\n      for (lineNode = node;; lineNode = lineNode.parentNode) {\n        if (!lineNode || lineNode == cm.display.lineDiv) { return null }\n        if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) { break }\n      }\n    }\n    for (var i = 0; i < cm.display.view.length; i++) {\n      var lineView = cm.display.view[i];\n      if (lineView.node == lineNode)\n        { return locateNodeInLineView(lineView, node, offset) }\n    }\n  }\n\n  function locateNodeInLineView(lineView, node, offset) {\n    var wrapper = lineView.text.firstChild, bad = false;\n    if (!node || !contains(wrapper, node)) { return badPos(Pos(lineNo(lineView.line), 0), true) }\n    if (node == wrapper) {\n      bad = true;\n      node = wrapper.childNodes[offset];\n      offset = 0;\n      if (!node) {\n        var line = lineView.rest ? lst(lineView.rest) : lineView.line;\n        return badPos(Pos(lineNo(line), line.text.length), bad)\n      }\n    }\n\n    var textNode = node.nodeType == 3 ? node : null, topNode = node;\n    if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {\n      textNode = node.firstChild;\n      if (offset) { offset = textNode.nodeValue.length; }\n    }\n    while (topNode.parentNode != wrapper) { topNode = topNode.parentNode; }\n    var measure = lineView.measure, maps = measure.maps;\n\n    function find(textNode, topNode, offset) {\n      for (var i = -1; i < (maps ? maps.length : 0); i++) {\n        var map = i < 0 ? measure.map : maps[i];\n        for (var j = 0; j < map.length; j += 3) {\n          var curNode = map[j + 2];\n          if (curNode == textNode || curNode == topNode) {\n            var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);\n            var ch = map[j] + offset;\n            if (offset < 0 || curNode != textNode) { ch = map[j + (offset ? 1 : 0)]; }\n            return Pos(line, ch)\n          }\n        }\n      }\n    }\n    var found = find(textNode, topNode, offset);\n    if (found) { return badPos(found, bad) }\n\n    // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems\n    for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {\n      found = find(after, after.firstChild, 0);\n      if (found)\n        { return badPos(Pos(found.line, found.ch - dist), bad) }\n      else\n        { dist += after.textContent.length; }\n    }\n    for (var before = topNode.previousSibling, dist$1 = offset; before; before = before.previousSibling) {\n      found = find(before, before.firstChild, -1);\n      if (found)\n        { return badPos(Pos(found.line, found.ch + dist$1), bad) }\n      else\n        { dist$1 += before.textContent.length; }\n    }\n  }\n\n  // TEXTAREA INPUT STYLE\n\n  var TextareaInput = function(cm) {\n    this.cm = cm;\n    // See input.poll and input.reset\n    this.prevInput = \"\";\n\n    // Flag that indicates whether we expect input to appear real soon\n    // now (after some event like 'keypress' or 'input') and are\n    // polling intensively.\n    this.pollingFast = false;\n    // Self-resetting timeout for the poller\n    this.polling = new Delayed();\n    // Used to work around IE issue with selection being forgotten when focus moves away from textarea\n    this.hasSelection = false;\n    this.composing = null;\n  };\n\n  TextareaInput.prototype.init = function (display) {\n      var this$1 = this;\n\n    var input = this, cm = this.cm;\n    this.createField(display);\n    var te = this.textarea;\n\n    display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild);\n\n    // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)\n    if (ios) { te.style.width = \"0px\"; }\n\n    on(te, \"input\", function () {\n      if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; }\n      input.poll();\n    });\n\n    on(te, \"paste\", function (e) {\n      if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }\n\n      cm.state.pasteIncoming = +new Date;\n      input.fastPoll();\n    });\n\n    function prepareCopyCut(e) {\n      if (signalDOMEvent(cm, e)) { return }\n      if (cm.somethingSelected()) {\n        setLastCopied({lineWise: false, text: cm.getSelections()});\n      } else if (!cm.options.lineWiseCopyCut) {\n        return\n      } else {\n        var ranges = copyableRanges(cm);\n        setLastCopied({lineWise: true, text: ranges.text});\n        if (e.type == \"cut\") {\n          cm.setSelections(ranges.ranges, null, sel_dontScroll);\n        } else {\n          input.prevInput = \"\";\n          te.value = ranges.text.join(\"\\n\");\n          selectInput(te);\n        }\n      }\n      if (e.type == \"cut\") { cm.state.cutIncoming = +new Date; }\n    }\n    on(te, \"cut\", prepareCopyCut);\n    on(te, \"copy\", prepareCopyCut);\n\n    on(display.scroller, \"paste\", function (e) {\n      if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }\n      if (!te.dispatchEvent) {\n        cm.state.pasteIncoming = +new Date;\n        input.focus();\n        return\n      }\n\n      // Pass the `paste` event to the textarea so it's handled by its event listener.\n      var event = new Event(\"paste\");\n      event.clipboardData = e.clipboardData;\n      te.dispatchEvent(event);\n    });\n\n    // Prevent normal selection in the editor (we handle our own)\n    on(display.lineSpace, \"selectstart\", function (e) {\n      if (!eventInWidget(display, e)) { e_preventDefault(e); }\n    });\n\n    on(te, \"compositionstart\", function () {\n      var start = cm.getCursor(\"from\");\n      if (input.composing) { input.composing.range.clear(); }\n      input.composing = {\n        start: start,\n        range: cm.markText(start, cm.getCursor(\"to\"), {className: \"CodeMirror-composing\"})\n      };\n    });\n    on(te, \"compositionend\", function () {\n      if (input.composing) {\n        input.poll();\n        input.composing.range.clear();\n        input.composing = null;\n      }\n    });\n  };\n\n  TextareaInput.prototype.createField = function (_display) {\n    // Wraps and hides input textarea\n    this.wrapper = hiddenTextarea();\n    // The semihidden textarea that is focused when the editor is\n    // focused, and receives input.\n    this.textarea = this.wrapper.firstChild;\n  };\n\n  TextareaInput.prototype.screenReaderLabelChanged = function (label) {\n    // Label for screenreaders, accessibility\n    if(label) {\n      this.textarea.setAttribute('aria-label', label);\n    } else {\n      this.textarea.removeAttribute('aria-label');\n    }\n  };\n\n  TextareaInput.prototype.prepareSelection = function () {\n    // Redraw the selection and/or cursor\n    var cm = this.cm, display = cm.display, doc = cm.doc;\n    var result = prepareSelection(cm);\n\n    // Move the hidden textarea near the cursor to prevent scrolling artifacts\n    if (cm.options.moveInputWithCursor) {\n      var headPos = cursorCoords(cm, doc.sel.primary().head, \"div\");\n      var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();\n      result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10,\n                                          headPos.top + lineOff.top - wrapOff.top));\n      result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10,\n                                           headPos.left + lineOff.left - wrapOff.left));\n    }\n\n    return result\n  };\n\n  TextareaInput.prototype.showSelection = function (drawn) {\n    var cm = this.cm, display = cm.display;\n    removeChildrenAndAdd(display.cursorDiv, drawn.cursors);\n    removeChildrenAndAdd(display.selectionDiv, drawn.selection);\n    if (drawn.teTop != null) {\n      this.wrapper.style.top = drawn.teTop + \"px\";\n      this.wrapper.style.left = drawn.teLeft + \"px\";\n    }\n  };\n\n  // Reset the input to correspond to the selection (or to be empty,\n  // when not typing and nothing is selected)\n  TextareaInput.prototype.reset = function (typing) {\n    if (this.contextMenuPending || this.composing) { return }\n    var cm = this.cm;\n    if (cm.somethingSelected()) {\n      this.prevInput = \"\";\n      var content = cm.getSelection();\n      this.textarea.value = content;\n      if (cm.state.focused) { selectInput(this.textarea); }\n      if (ie && ie_version >= 9) { this.hasSelection = content; }\n    } else if (!typing) {\n      this.prevInput = this.textarea.value = \"\";\n      if (ie && ie_version >= 9) { this.hasSelection = null; }\n    }\n  };\n\n  TextareaInput.prototype.getField = function () { return this.textarea };\n\n  TextareaInput.prototype.supportsTouch = function () { return false };\n\n  TextareaInput.prototype.focus = function () {\n    if (this.cm.options.readOnly != \"nocursor\" && (!mobile || activeElt() != this.textarea)) {\n      try { this.textarea.focus(); }\n      catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM\n    }\n  };\n\n  TextareaInput.prototype.blur = function () { this.textarea.blur(); };\n\n  TextareaInput.prototype.resetPosition = function () {\n    this.wrapper.style.top = this.wrapper.style.left = 0;\n  };\n\n  TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); };\n\n  // Poll for input changes, using the normal rate of polling. This\n  // runs as long as the editor is focused.\n  TextareaInput.prototype.slowPoll = function () {\n      var this$1 = this;\n\n    if (this.pollingFast) { return }\n    this.polling.set(this.cm.options.pollInterval, function () {\n      this$1.poll();\n      if (this$1.cm.state.focused) { this$1.slowPoll(); }\n    });\n  };\n\n  // When an event has just come in that is likely to add or change\n  // something in the input textarea, we poll faster, to ensure that\n  // the change appears on the screen quickly.\n  TextareaInput.prototype.fastPoll = function () {\n    var missed = false, input = this;\n    input.pollingFast = true;\n    function p() {\n      var changed = input.poll();\n      if (!changed && !missed) {missed = true; input.polling.set(60, p);}\n      else {input.pollingFast = false; input.slowPoll();}\n    }\n    input.polling.set(20, p);\n  };\n\n  // Read input from the textarea, and update the document to match.\n  // When something is selected, it is present in the textarea, and\n  // selected (unless it is huge, in which case a placeholder is\n  // used). When nothing is selected, the cursor sits after previously\n  // seen text (can be empty), which is stored in prevInput (we must\n  // not reset the textarea when typing, because that breaks IME).\n  TextareaInput.prototype.poll = function () {\n      var this$1 = this;\n\n    var cm = this.cm, input = this.textarea, prevInput = this.prevInput;\n    // Since this is called a *lot*, try to bail out as cheaply as\n    // possible when it is clear that nothing happened. hasSelection\n    // will be the case when there is a lot of text in the textarea,\n    // in which case reading its value would be expensive.\n    if (this.contextMenuPending || !cm.state.focused ||\n        (hasSelection(input) && !prevInput && !this.composing) ||\n        cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)\n      { return false }\n\n    var text = input.value;\n    // If nothing changed, bail.\n    if (text == prevInput && !cm.somethingSelected()) { return false }\n    // Work around nonsensical selection resetting in IE9/10, and\n    // inexplicable appearance of private area unicode characters on\n    // some key combos in Mac (#2689).\n    if (ie && ie_version >= 9 && this.hasSelection === text ||\n        mac && /[\\uf700-\\uf7ff]/.test(text)) {\n      cm.display.input.reset();\n      return false\n    }\n\n    if (cm.doc.sel == cm.display.selForContextMenu) {\n      var first = text.charCodeAt(0);\n      if (first == 0x200b && !prevInput) { prevInput = \"\\u200b\"; }\n      if (first == 0x21da) { this.reset(); return this.cm.execCommand(\"undo\") }\n    }\n    // Find the part of the input that is actually new\n    var same = 0, l = Math.min(prevInput.length, text.length);\n    while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) { ++same; }\n\n    runInOp(cm, function () {\n      applyTextInput(cm, text.slice(same), prevInput.length - same,\n                     null, this$1.composing ? \"*compose\" : null);\n\n      // Don't leave long text in the textarea, since it makes further polling slow\n      if (text.length > 1000 || text.indexOf(\"\\n\") > -1) { input.value = this$1.prevInput = \"\"; }\n      else { this$1.prevInput = text; }\n\n      if (this$1.composing) {\n        this$1.composing.range.clear();\n        this$1.composing.range = cm.markText(this$1.composing.start, cm.getCursor(\"to\"),\n                                           {className: \"CodeMirror-composing\"});\n      }\n    });\n    return true\n  };\n\n  TextareaInput.prototype.ensurePolled = function () {\n    if (this.pollingFast && this.poll()) { this.pollingFast = false; }\n  };\n\n  TextareaInput.prototype.onKeyPress = function () {\n    if (ie && ie_version >= 9) { this.hasSelection = null; }\n    this.fastPoll();\n  };\n\n  TextareaInput.prototype.onContextMenu = function (e) {\n    var input = this, cm = input.cm, display = cm.display, te = input.textarea;\n    if (input.contextMenuPending) { input.contextMenuPending(); }\n    var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;\n    if (!pos || presto) { return } // Opera is difficult.\n\n    // Reset the current text selection only if the click is done outside of the selection\n    // and 'resetSelectionOnContextMenu' option is true.\n    var reset = cm.options.resetSelectionOnContextMenu;\n    if (reset && cm.doc.sel.contains(pos) == -1)\n      { operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); }\n\n    var oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText;\n    var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect();\n    input.wrapper.style.cssText = \"position: static\";\n    te.style.cssText = \"position: absolute; width: 30px; height: 30px;\\n      top: \" + (e.clientY - wrapperBox.top - 5) + \"px; left: \" + (e.clientX - wrapperBox.left - 5) + \"px;\\n      z-index: 1000; background: \" + (ie ? \"rgba(255, 255, 255, .05)\" : \"transparent\") + \";\\n      outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);\";\n    var oldScrollY;\n    if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712)\n    display.input.focus();\n    if (webkit) { window.scrollTo(null, oldScrollY); }\n    display.input.reset();\n    // Adds \"Select all\" to context menu in FF\n    if (!cm.somethingSelected()) { te.value = input.prevInput = \" \"; }\n    input.contextMenuPending = rehide;\n    display.selForContextMenu = cm.doc.sel;\n    clearTimeout(display.detectingSelectAll);\n\n    // Select-all will be greyed out if there's nothing to select, so\n    // this adds a zero-width space so that we can later check whether\n    // it got selected.\n    function prepareSelectAllHack() {\n      if (te.selectionStart != null) {\n        var selected = cm.somethingSelected();\n        var extval = \"\\u200b\" + (selected ? te.value : \"\");\n        te.value = \"\\u21da\"; // Used to catch context-menu undo\n        te.value = extval;\n        input.prevInput = selected ? \"\" : \"\\u200b\";\n        te.selectionStart = 1; te.selectionEnd = extval.length;\n        // Re-set this, in case some other handler touched the\n        // selection in the meantime.\n        display.selForContextMenu = cm.doc.sel;\n      }\n    }\n    function rehide() {\n      if (input.contextMenuPending != rehide) { return }\n      input.contextMenuPending = false;\n      input.wrapper.style.cssText = oldWrapperCSS;\n      te.style.cssText = oldCSS;\n      if (ie && ie_version < 9) { display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); }\n\n      // Try to detect the user choosing select-all\n      if (te.selectionStart != null) {\n        if (!ie || (ie && ie_version < 9)) { prepareSelectAllHack(); }\n        var i = 0, poll = function () {\n          if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 &&\n              te.selectionEnd > 0 && input.prevInput == \"\\u200b\") {\n            operation(cm, selectAll)(cm);\n          } else if (i++ < 10) {\n            display.detectingSelectAll = setTimeout(poll, 500);\n          } else {\n            display.selForContextMenu = null;\n            display.input.reset();\n          }\n        };\n        display.detectingSelectAll = setTimeout(poll, 200);\n      }\n    }\n\n    if (ie && ie_version >= 9) { prepareSelectAllHack(); }\n    if (captureRightClick) {\n      e_stop(e);\n      var mouseup = function () {\n        off(window, \"mouseup\", mouseup);\n        setTimeout(rehide, 20);\n      };\n      on(window, \"mouseup\", mouseup);\n    } else {\n      setTimeout(rehide, 50);\n    }\n  };\n\n  TextareaInput.prototype.readOnlyChanged = function (val) {\n    if (!val) { this.reset(); }\n    this.textarea.disabled = val == \"nocursor\";\n  };\n\n  TextareaInput.prototype.setUneditable = function () {};\n\n  TextareaInput.prototype.needsContentAttribute = false;\n\n  function fromTextArea(textarea, options) {\n    options = options ? copyObj(options) : {};\n    options.value = textarea.value;\n    if (!options.tabindex && textarea.tabIndex)\n      { options.tabindex = textarea.tabIndex; }\n    if (!options.placeholder && textarea.placeholder)\n      { options.placeholder = textarea.placeholder; }\n    // Set autofocus to true if this textarea is focused, or if it has\n    // autofocus and no other element is focused.\n    if (options.autofocus == null) {\n      var hasFocus = activeElt();\n      options.autofocus = hasFocus == textarea ||\n        textarea.getAttribute(\"autofocus\") != null && hasFocus == document.body;\n    }\n\n    function save() {textarea.value = cm.getValue();}\n\n    var realSubmit;\n    if (textarea.form) {\n      on(textarea.form, \"submit\", save);\n      // Deplorable hack to make the submit method do the right thing.\n      if (!options.leaveSubmitMethodAlone) {\n        var form = textarea.form;\n        realSubmit = form.submit;\n        try {\n          var wrappedSubmit = form.submit = function () {\n            save();\n            form.submit = realSubmit;\n            form.submit();\n            form.submit = wrappedSubmit;\n          };\n        } catch(e) {}\n      }\n    }\n\n    options.finishInit = function (cm) {\n      cm.save = save;\n      cm.getTextArea = function () { return textarea; };\n      cm.toTextArea = function () {\n        cm.toTextArea = isNaN; // Prevent this from being ran twice\n        save();\n        textarea.parentNode.removeChild(cm.getWrapperElement());\n        textarea.style.display = \"\";\n        if (textarea.form) {\n          off(textarea.form, \"submit\", save);\n          if (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == \"function\")\n            { textarea.form.submit = realSubmit; }\n        }\n      };\n    };\n\n    textarea.style.display = \"none\";\n    var cm = CodeMirror(function (node) { return textarea.parentNode.insertBefore(node, textarea.nextSibling); },\n      options);\n    return cm\n  }\n\n  function addLegacyProps(CodeMirror) {\n    CodeMirror.off = off;\n    CodeMirror.on = on;\n    CodeMirror.wheelEventPixels = wheelEventPixels;\n    CodeMirror.Doc = Doc;\n    CodeMirror.splitLines = splitLinesAuto;\n    CodeMirror.countColumn = countColumn;\n    CodeMirror.findColumn = findColumn;\n    CodeMirror.isWordChar = isWordCharBasic;\n    CodeMirror.Pass = Pass;\n    CodeMirror.signal = signal;\n    CodeMirror.Line = Line;\n    CodeMirror.changeEnd = changeEnd;\n    CodeMirror.scrollbarModel = scrollbarModel;\n    CodeMirror.Pos = Pos;\n    CodeMirror.cmpPos = cmp;\n    CodeMirror.modes = modes;\n    CodeMirror.mimeModes = mimeModes;\n    CodeMirror.resolveMode = resolveMode;\n    CodeMirror.getMode = getMode;\n    CodeMirror.modeExtensions = modeExtensions;\n    CodeMirror.extendMode = extendMode;\n    CodeMirror.copyState = copyState;\n    CodeMirror.startState = startState;\n    CodeMirror.innerMode = innerMode;\n    CodeMirror.commands = commands;\n    CodeMirror.keyMap = keyMap;\n    CodeMirror.keyName = keyName;\n    CodeMirror.isModifierKey = isModifierKey;\n    CodeMirror.lookupKey = lookupKey;\n    CodeMirror.normalizeKeyMap = normalizeKeyMap;\n    CodeMirror.StringStream = StringStream;\n    CodeMirror.SharedTextMarker = SharedTextMarker;\n    CodeMirror.TextMarker = TextMarker;\n    CodeMirror.LineWidget = LineWidget;\n    CodeMirror.e_preventDefault = e_preventDefault;\n    CodeMirror.e_stopPropagation = e_stopPropagation;\n    CodeMirror.e_stop = e_stop;\n    CodeMirror.addClass = addClass;\n    CodeMirror.contains = contains;\n    CodeMirror.rmClass = rmClass;\n    CodeMirror.keyNames = keyNames;\n  }\n\n  // EDITOR CONSTRUCTOR\n\n  defineOptions(CodeMirror);\n\n  addEditorMethods(CodeMirror);\n\n  // Set up methods on CodeMirror's prototype to redirect to the editor's document.\n  var dontDelegate = \"iter insert remove copy getEditor constructor\".split(\" \");\n  for (var prop in Doc.prototype) { if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0)\n    { CodeMirror.prototype[prop] = (function(method) {\n      return function() {return method.apply(this.doc, arguments)}\n    })(Doc.prototype[prop]); } }\n\n  eventMixin(Doc);\n  CodeMirror.inputStyles = {\"textarea\": TextareaInput, \"contenteditable\": ContentEditableInput};\n\n  // Extra arguments are stored as the mode's dependencies, which is\n  // used by (legacy) mechanisms like loadmode.js to automatically\n  // load a mode. (Preferred mechanism is the require/define calls.)\n  CodeMirror.defineMode = function(name/*, mode, …*/) {\n    if (!CodeMirror.defaults.mode && name != \"null\") { CodeMirror.defaults.mode = name; }\n    defineMode.apply(this, arguments);\n  };\n\n  CodeMirror.defineMIME = defineMIME;\n\n  // Minimal default mode.\n  CodeMirror.defineMode(\"null\", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });\n  CodeMirror.defineMIME(\"text/plain\", \"null\");\n\n  // EXTENSIONS\n\n  CodeMirror.defineExtension = function (name, func) {\n    CodeMirror.prototype[name] = func;\n  };\n  CodeMirror.defineDocExtension = function (name, func) {\n    Doc.prototype[name] = func;\n  };\n\n  CodeMirror.fromTextArea = fromTextArea;\n\n  addLegacyProps(CodeMirror);\n\n  CodeMirror.version = \"5.56.0\";\n\n  return CodeMirror;\n\n})));\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/dialog/dialog.css",
    "content": ".CodeMirror-dialog {\n  position: absolute;\n  left: 0; right: 0;\n  background: inherit;\n  z-index: 15;\n  padding: .1em .8em;\n  overflow: hidden;\n  color: inherit;\n}\n\n.CodeMirror-dialog-top {\n  border-bottom: 1px solid #eee;\n  top: 0;\n}\n\n.CodeMirror-dialog-bottom {\n  border-top: 1px solid #eee;\n  bottom: 0;\n}\n\n.CodeMirror-dialog input {\n  border: none;\n  outline: none;\n  background: transparent;\n  width: 20em;\n  color: inherit;\n  font-family: monospace;\n}\n\n.CodeMirror-dialog button {\n  font-size: 70%;\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/dialog/dialog.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Open simple dialogs on top of an editor. Relies on dialog.css.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  function dialogDiv(cm, template, bottom) {\n    var wrap = cm.getWrapperElement();\n    var dialog;\n    dialog = wrap.appendChild(document.createElement(\"div\"));\n    if (bottom)\n      dialog.className = \"CodeMirror-dialog CodeMirror-dialog-bottom\";\n    else\n      dialog.className = \"CodeMirror-dialog CodeMirror-dialog-top\";\n\n    if (typeof template == \"string\") {\n      dialog.innerHTML = template;\n    } else { // Assuming it's a detached DOM element.\n      dialog.appendChild(template);\n    }\n    CodeMirror.addClass(wrap, 'dialog-opened');\n    return dialog;\n  }\n\n  function closeNotification(cm, newVal) {\n    if (cm.state.currentNotificationClose)\n      cm.state.currentNotificationClose();\n    cm.state.currentNotificationClose = newVal;\n  }\n\n  CodeMirror.defineExtension(\"openDialog\", function(template, callback, options) {\n    if (!options) options = {};\n\n    closeNotification(this, null);\n\n    var dialog = dialogDiv(this, template, options.bottom);\n    var closed = false, me = this;\n    function close(newVal) {\n      if (typeof newVal == 'string') {\n        inp.value = newVal;\n      } else {\n        if (closed) return;\n        closed = true;\n        CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');\n        dialog.parentNode.removeChild(dialog);\n        me.focus();\n\n        if (options.onClose) options.onClose(dialog);\n      }\n    }\n\n    var inp = dialog.getElementsByTagName(\"input\")[0], button;\n    if (inp) {\n      inp.focus();\n\n      if (options.value) {\n        inp.value = options.value;\n        if (options.selectValueOnOpen !== false) {\n          inp.select();\n        }\n      }\n\n      if (options.onInput)\n        CodeMirror.on(inp, \"input\", function(e) { options.onInput(e, inp.value, close);});\n      if (options.onKeyUp)\n        CodeMirror.on(inp, \"keyup\", function(e) {options.onKeyUp(e, inp.value, close);});\n\n      CodeMirror.on(inp, \"keydown\", function(e) {\n        if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }\n        if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {\n          inp.blur();\n          CodeMirror.e_stop(e);\n          close();\n        }\n        if (e.keyCode == 13) callback(inp.value, e);\n      });\n\n      if (options.closeOnBlur !== false) CodeMirror.on(dialog, \"focusout\", function (evt) {\n        if (evt.relatedTarget !== null) close();\n      });\n    } else if (button = dialog.getElementsByTagName(\"button\")[0]) {\n      CodeMirror.on(button, \"click\", function() {\n        close();\n        me.focus();\n      });\n\n      if (options.closeOnBlur !== false) CodeMirror.on(button, \"blur\", close);\n\n      button.focus();\n    }\n    return close;\n  });\n\n  CodeMirror.defineExtension(\"openConfirm\", function(template, callbacks, options) {\n    closeNotification(this, null);\n    var dialog = dialogDiv(this, template, options && options.bottom);\n    var buttons = dialog.getElementsByTagName(\"button\");\n    var closed = false, me = this, blurring = 1;\n    function close() {\n      if (closed) return;\n      closed = true;\n      CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');\n      dialog.parentNode.removeChild(dialog);\n      me.focus();\n    }\n    buttons[0].focus();\n    for (var i = 0; i < buttons.length; ++i) {\n      var b = buttons[i];\n      (function(callback) {\n        CodeMirror.on(b, \"click\", function(e) {\n          CodeMirror.e_preventDefault(e);\n          close();\n          if (callback) callback(me);\n        });\n      })(callbacks[i]);\n      CodeMirror.on(b, \"blur\", function() {\n        --blurring;\n        setTimeout(function() { if (blurring <= 0) close(); }, 200);\n      });\n      CodeMirror.on(b, \"focus\", function() { ++blurring; });\n    }\n  });\n\n  /*\n   * openNotification\n   * Opens a notification, that can be closed with an optional timer\n   * (default 5000ms timer) and always closes on click.\n   *\n   * If a notification is opened while another is opened, it will close the\n   * currently opened one and open the new one immediately.\n   */\n  CodeMirror.defineExtension(\"openNotification\", function(template, options) {\n    closeNotification(this, close);\n    var dialog = dialogDiv(this, template, options && options.bottom);\n    var closed = false, doneTimer;\n    var duration = options && typeof options.duration !== \"undefined\" ? options.duration : 5000;\n\n    function close() {\n      if (closed) return;\n      closed = true;\n      clearTimeout(doneTimer);\n      CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');\n      dialog.parentNode.removeChild(dialog);\n    }\n\n    CodeMirror.on(dialog, 'click', function(e) {\n      CodeMirror.e_preventDefault(e);\n      close();\n    });\n\n    if (duration)\n      doneTimer = setTimeout(close, duration);\n\n    return close;\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/edit/closebrackets.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  var defaults = {\n    pairs: \"()[]{}''\\\"\\\"\",\n    closeBefore: \")]}'\\\":;>\",\n    triples: \"\",\n    explode: \"[]{}\"\n  };\n\n  var Pos = CodeMirror.Pos;\n\n  CodeMirror.defineOption(\"autoCloseBrackets\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.removeKeyMap(keyMap);\n      cm.state.closeBrackets = null;\n    }\n    if (val) {\n      ensureBound(getOption(val, \"pairs\"))\n      cm.state.closeBrackets = val;\n      cm.addKeyMap(keyMap);\n    }\n  });\n\n  function getOption(conf, name) {\n    if (name == \"pairs\" && typeof conf == \"string\") return conf;\n    if (typeof conf == \"object\" && conf[name] != null) return conf[name];\n    return defaults[name];\n  }\n\n  var keyMap = {Backspace: handleBackspace, Enter: handleEnter};\n  function ensureBound(chars) {\n    for (var i = 0; i < chars.length; i++) {\n      var ch = chars.charAt(i), key = \"'\" + ch + \"'\"\n      if (!keyMap[key]) keyMap[key] = handler(ch)\n    }\n  }\n  ensureBound(defaults.pairs + \"`\")\n\n  function handler(ch) {\n    return function(cm) { return handleChar(cm, ch); };\n  }\n\n  function getConfig(cm) {\n    var deflt = cm.state.closeBrackets;\n    if (!deflt || deflt.override) return deflt;\n    var mode = cm.getModeAt(cm.getCursor());\n    return mode.closeBrackets || deflt;\n  }\n\n  function handleBackspace(cm) {\n    var conf = getConfig(cm);\n    if (!conf || cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n\n    var pairs = getOption(conf, \"pairs\");\n    var ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var around = charsAround(cm, ranges[i].head);\n      if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;\n    }\n    for (var i = ranges.length - 1; i >= 0; i--) {\n      var cur = ranges[i].head;\n      cm.replaceRange(\"\", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), \"+delete\");\n    }\n  }\n\n  function handleEnter(cm) {\n    var conf = getConfig(cm);\n    var explode = conf && getOption(conf, \"explode\");\n    if (!explode || cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n\n    var ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var around = charsAround(cm, ranges[i].head);\n      if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;\n    }\n    cm.operation(function() {\n      var linesep = cm.lineSeparator() || \"\\n\";\n      cm.replaceSelection(linesep + linesep, null);\n      cm.execCommand(\"goCharLeft\");\n      ranges = cm.listSelections();\n      for (var i = 0; i < ranges.length; i++) {\n        var line = ranges[i].head.line;\n        cm.indentLine(line, null, true);\n        cm.indentLine(line + 1, null, true);\n      }\n    });\n  }\n\n  function contractSelection(sel) {\n    var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;\n    return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),\n            head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};\n  }\n\n  function handleChar(cm, ch) {\n    var conf = getConfig(cm);\n    if (!conf || cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n\n    var pairs = getOption(conf, \"pairs\");\n    var pos = pairs.indexOf(ch);\n    if (pos == -1) return CodeMirror.Pass;\n\n    var closeBefore = getOption(conf,\"closeBefore\");\n\n    var triples = getOption(conf, \"triples\");\n\n    var identical = pairs.charAt(pos + 1) == ch;\n    var ranges = cm.listSelections();\n    var opening = pos % 2 == 0;\n\n    var type;\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], cur = range.head, curType;\n      var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));\n      if (opening && !range.empty()) {\n        curType = \"surround\";\n      } else if ((identical || !opening) && next == ch) {\n        if (identical && stringStartsAfter(cm, cur))\n          curType = \"both\";\n        else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)\n          curType = \"skipThree\";\n        else\n          curType = \"skip\";\n      } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&\n                 cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {\n        if (cur.ch > 2 && /\\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;\n        curType = \"addFour\";\n      } else if (identical) {\n        var prev = cur.ch == 0 ? \" \" : cm.getRange(Pos(cur.line, cur.ch - 1), cur)\n        if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = \"both\";\n        else return CodeMirror.Pass;\n      } else if (opening && (next.length === 0 || /\\s/.test(next) || closeBefore.indexOf(next) > -1)) {\n        curType = \"both\";\n      } else {\n        return CodeMirror.Pass;\n      }\n      if (!type) type = curType;\n      else if (type != curType) return CodeMirror.Pass;\n    }\n\n    var left = pos % 2 ? pairs.charAt(pos - 1) : ch;\n    var right = pos % 2 ? ch : pairs.charAt(pos + 1);\n    cm.operation(function() {\n      if (type == \"skip\") {\n        cm.execCommand(\"goCharRight\");\n      } else if (type == \"skipThree\") {\n        for (var i = 0; i < 3; i++)\n          cm.execCommand(\"goCharRight\");\n      } else if (type == \"surround\") {\n        var sels = cm.getSelections();\n        for (var i = 0; i < sels.length; i++)\n          sels[i] = left + sels[i] + right;\n        cm.replaceSelections(sels, \"around\");\n        sels = cm.listSelections().slice();\n        for (var i = 0; i < sels.length; i++)\n          sels[i] = contractSelection(sels[i]);\n        cm.setSelections(sels);\n      } else if (type == \"both\") {\n        cm.replaceSelection(left + right, null);\n        cm.triggerElectric(left + right);\n        cm.execCommand(\"goCharLeft\");\n      } else if (type == \"addFour\") {\n        cm.replaceSelection(left + left + left + left, \"before\");\n        cm.execCommand(\"goCharRight\");\n      }\n    });\n  }\n\n  function charsAround(cm, pos) {\n    var str = cm.getRange(Pos(pos.line, pos.ch - 1),\n                          Pos(pos.line, pos.ch + 1));\n    return str.length == 2 ? str : null;\n  }\n\n  function stringStartsAfter(cm, pos) {\n    var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))\n    return /\\bstring/.test(token.type) && token.start == pos.ch &&\n      (pos.ch == 0 || !/\\bstring/.test(cm.getTokenTypeAt(pos)))\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/edit/closetag.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n/**\n * Tag-closer extension for CodeMirror.\n *\n * This extension adds an \"autoCloseTags\" option that can be set to\n * either true to get the default behavior, or an object to further\n * configure its behavior.\n *\n * These are supported options:\n *\n * `whenClosing` (default true)\n *   Whether to autoclose when the '/' of a closing tag is typed.\n * `whenOpening` (default true)\n *   Whether to autoclose the tag when the final '>' of an opening\n *   tag is typed.\n * `dontCloseTags` (default is empty tags for HTML, none for XML)\n *   An array of tag names that should not be autoclosed.\n * `indentTags` (default is block tags for HTML, none for XML)\n *   An array of tag names that should, when opened, cause a\n *   blank line to be added inside the tag, and the blank line and\n *   closing line to be indented.\n * `emptyTags` (default is none)\n *   An array of XML tag names that should be autoclosed with '/>'.\n *\n * See demos/closetag.html for a usage example.\n */\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../fold/xml-fold\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../fold/xml-fold\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  CodeMirror.defineOption(\"autoCloseTags\", false, function(cm, val, old) {\n    if (old != CodeMirror.Init && old)\n      cm.removeKeyMap(\"autoCloseTags\");\n    if (!val) return;\n    var map = {name: \"autoCloseTags\"};\n    if (typeof val != \"object\" || val.whenClosing !== false)\n      map[\"'/'\"] = function(cm) { return autoCloseSlash(cm); };\n    if (typeof val != \"object\" || val.whenOpening !== false)\n      map[\"'>'\"] = function(cm) { return autoCloseGT(cm); };\n    cm.addKeyMap(map);\n  });\n\n  var htmlDontClose = [\"area\", \"base\", \"br\", \"col\", \"command\", \"embed\", \"hr\", \"img\", \"input\", \"keygen\", \"link\", \"meta\", \"param\",\n                       \"source\", \"track\", \"wbr\"];\n  var htmlIndent = [\"applet\", \"blockquote\", \"body\", \"button\", \"div\", \"dl\", \"fieldset\", \"form\", \"frameset\", \"h1\", \"h2\", \"h3\", \"h4\",\n                    \"h5\", \"h6\", \"head\", \"html\", \"iframe\", \"layer\", \"legend\", \"object\", \"ol\", \"p\", \"select\", \"table\", \"ul\"];\n\n  function autoCloseGT(cm) {\n    if (cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n    var ranges = cm.listSelections(), replacements = [];\n    var opt = cm.getOption(\"autoCloseTags\");\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var pos = ranges[i].head, tok = cm.getTokenAt(pos);\n      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;\n      var tagInfo = inner.mode.xmlCurrentTag && inner.mode.xmlCurrentTag(state)\n      var tagName = tagInfo && tagInfo.name\n      if (!tagName) return CodeMirror.Pass\n\n      var html = inner.mode.configuration == \"html\";\n      var dontCloseTags = (typeof opt == \"object\" && opt.dontCloseTags) || (html && htmlDontClose);\n      var indentTags = (typeof opt == \"object\" && opt.indentTags) || (html && htmlIndent);\n\n      if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);\n      var lowerTagName = tagName.toLowerCase();\n      // Don't process the '>' at the end of an end-tag or self-closing tag\n      if (!tagName ||\n          tok.type == \"string\" && (tok.end != pos.ch || !/[\\\"\\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||\n          tok.type == \"tag\" && tagInfo.close ||\n          tok.string.indexOf(\"/\") == (pos.ch - tok.start - 1) || // match something like <someTagName />\n          dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||\n          closingTagExists(cm, inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state) || [], tagName, pos, true))\n        return CodeMirror.Pass;\n\n      var emptyTags = typeof opt == \"object\" && opt.emptyTags;\n      if (emptyTags && indexOf(emptyTags, tagName) > -1) {\n        replacements[i] = { text: \"/>\", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) };\n        continue;\n      }\n\n      var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;\n      replacements[i] = {indent: indent,\n                         text: \">\" + (indent ? \"\\n\\n\" : \"\") + \"</\" + tagName + \">\",\n                         newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};\n    }\n\n    var dontIndentOnAutoClose = (typeof opt == \"object\" && opt.dontIndentOnAutoClose);\n    for (var i = ranges.length - 1; i >= 0; i--) {\n      var info = replacements[i];\n      cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, \"+insert\");\n      var sel = cm.listSelections().slice(0);\n      sel[i] = {head: info.newPos, anchor: info.newPos};\n      cm.setSelections(sel);\n      if (!dontIndentOnAutoClose && info.indent) {\n        cm.indentLine(info.newPos.line, null, true);\n        cm.indentLine(info.newPos.line + 1, null, true);\n      }\n    }\n  }\n\n  function autoCloseCurrent(cm, typingSlash) {\n    var ranges = cm.listSelections(), replacements = [];\n    var head = typingSlash ? \"/\" : \"</\";\n    var opt = cm.getOption(\"autoCloseTags\");\n    var dontIndentOnAutoClose = (typeof opt == \"object\" && opt.dontIndentOnSlash);\n    for (var i = 0; i < ranges.length; i++) {\n      if (!ranges[i].empty()) return CodeMirror.Pass;\n      var pos = ranges[i].head, tok = cm.getTokenAt(pos);\n      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;\n      if (typingSlash && (tok.type == \"string\" || tok.string.charAt(0) != \"<\" ||\n                          tok.start != pos.ch - 1))\n        return CodeMirror.Pass;\n      // Kludge to get around the fact that we are not in XML mode\n      // when completing in JS/CSS snippet in htmlmixed mode. Does not\n      // work for other XML embedded languages (there is no general\n      // way to go from a mixed mode to its current XML state).\n      var replacement, mixed = inner.mode.name != \"xml\" && cm.getMode().name == \"htmlmixed\"\n      if (mixed && inner.mode.name == \"javascript\") {\n        replacement = head + \"script\";\n      } else if (mixed && inner.mode.name == \"css\") {\n        replacement = head + \"style\";\n      } else {\n        var context = inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state)\n        if (!context || (context.length && closingTagExists(cm, context, context[context.length - 1], pos)))\n          return CodeMirror.Pass;\n        replacement = head + context[context.length - 1]\n      }\n      if (cm.getLine(pos.line).charAt(tok.end) != \">\") replacement += \">\";\n      replacements[i] = replacement;\n    }\n    cm.replaceSelections(replacements);\n    ranges = cm.listSelections();\n    if (!dontIndentOnAutoClose) {\n        for (var i = 0; i < ranges.length; i++)\n            if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)\n                cm.indentLine(ranges[i].head.line);\n    }\n  }\n\n  function autoCloseSlash(cm) {\n    if (cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n    return autoCloseCurrent(cm, true);\n  }\n\n  CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };\n\n  function indexOf(collection, elt) {\n    if (collection.indexOf) return collection.indexOf(elt);\n    for (var i = 0, e = collection.length; i < e; ++i)\n      if (collection[i] == elt) return i;\n    return -1;\n  }\n\n  // If xml-fold is loaded, we use its functionality to try and verify\n  // whether a given tag is actually unclosed.\n  function closingTagExists(cm, context, tagName, pos, newTag) {\n    if (!CodeMirror.scanForClosingTag) return false;\n    var end = Math.min(cm.lastLine() + 1, pos.line + 500);\n    var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);\n    if (!nextClose || nextClose.tag != tagName) return false;\n    // If the immediate wrapping context contains onCx instances of\n    // the same tag, a closing tag only exists if there are at least\n    // that many closing tags of that type following.\n    var onCx = newTag ? 1 : 0\n    for (var i = context.length - 1; i >= 0; i--) {\n      if (context[i] == tagName) ++onCx\n      else break\n    }\n    pos = nextClose.to;\n    for (var i = 1; i < onCx; i++) {\n      var next = CodeMirror.scanForClosingTag(cm, pos, null, end);\n      if (!next || next.tag != tagName) return false;\n      pos = next.to;\n    }\n    return true;\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/edit/continuelist.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var listRE = /^(\\s*)(>[> ]*|[*+-] \\[[x ]\\]\\s|[*+-]\\s|(\\d+)([.)]))(\\s*)/,\n      emptyListRE = /^(\\s*)(>[> ]*|[*+-] \\[[x ]\\]|[*+-]|(\\d+)[.)])(\\s*)$/,\n      unorderedListRE = /[*+-]\\s/;\n\n  CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {\n    if (cm.getOption(\"disableInput\")) return CodeMirror.Pass;\n    var ranges = cm.listSelections(), replacements = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var pos = ranges[i].head;\n\n      // If we're not in Markdown mode, fall back to normal newlineAndIndent\n      var eolState = cm.getStateAfter(pos.line);\n      var inner = CodeMirror.innerMode(cm.getMode(), eolState);\n      if (inner.mode.name !== \"markdown\") {\n        cm.execCommand(\"newlineAndIndent\");\n        return;\n      } else {\n        eolState = inner.state;\n      }\n\n      var inList = eolState.list !== false;\n      var inQuote = eolState.quote !== 0;\n\n      var line = cm.getLine(pos.line), match = listRE.exec(line);\n      var cursorBeforeBullet = /^\\s*$/.test(line.slice(0, pos.ch));\n      if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) {\n        cm.execCommand(\"newlineAndIndent\");\n        return;\n      }\n      if (emptyListRE.test(line)) {\n        var endOfQuote = inQuote && />\\s*$/.test(line)\n        var endOfList = !/>\\s*$/.test(line)\n        if (endOfQuote || endOfList) cm.replaceRange(\"\", {\n          line: pos.line, ch: 0\n        }, {\n          line: pos.line, ch: pos.ch + 1\n        });\n        replacements[i] = \"\\n\";\n      } else {\n        var indent = match[1], after = match[5];\n        var numbered = !(unorderedListRE.test(match[2]) || match[2].indexOf(\">\") >= 0);\n        var bullet = numbered ? (parseInt(match[3], 10) + 1) + match[4] : match[2].replace(\"x\", \" \");\n        replacements[i] = \"\\n\" + indent + bullet + after;\n\n        if (numbered) incrementRemainingMarkdownListNumbers(cm, pos);\n      }\n    }\n\n    cm.replaceSelections(replacements);\n  };\n\n  // Auto-updating Markdown list numbers when a new item is added to the\n  // middle of a list\n  function incrementRemainingMarkdownListNumbers(cm, pos) {\n    var startLine = pos.line, lookAhead = 0, skipCount = 0;\n    var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1];\n\n    do {\n      lookAhead += 1;\n      var nextLineNumber = startLine + lookAhead;\n      var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine);\n\n      if (nextItem) {\n        var nextIndent = nextItem[1];\n        var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount);\n        var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber;\n\n        if (startIndent === nextIndent && !isNaN(nextNumber)) {\n          if (newNumber === nextNumber) itemNumber = nextNumber + 1;\n          if (newNumber > nextNumber) itemNumber = newNumber + 1;\n          cm.replaceRange(\n            nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]),\n          {\n            line: nextLineNumber, ch: 0\n          }, {\n            line: nextLineNumber, ch: nextLine.length\n          });\n        } else {\n          if (startIndent.length > nextIndent.length) return;\n          // This doesn't run if the next line immediatley indents, as it is\n          // not clear of the users intention (new indented item or same level)\n          if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return;\n          skipCount += 1;\n        }\n      }\n    } while (nextItem);\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/edit/matchbrackets.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  var ie_lt8 = /MSIE \\d/.test(navigator.userAgent) &&\n    (document.documentMode == null || document.documentMode < 8);\n\n  var Pos = CodeMirror.Pos;\n\n  var matching = {\"(\": \")>\", \")\": \"(<\", \"[\": \"]>\", \"]\": \"[<\", \"{\": \"}>\", \"}\": \"{<\", \"<\": \">>\", \">\": \"<<\"};\n\n  function bracketRegex(config) {\n    return config && config.bracketRegex || /[(){}[\\]]/\n  }\n\n  function findMatchingBracket(cm, where, config) {\n    var line = cm.getLineHandle(where.line), pos = where.ch - 1;\n    var afterCursor = config && config.afterCursor\n    if (afterCursor == null)\n      afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)\n    var re = bracketRegex(config)\n\n    // A cursor is defined as between two characters, but in in vim command mode\n    // (i.e. not insert mode), the cursor is visually represented as a\n    // highlighted box on top of the 2nd character. Otherwise, we allow matches\n    // from before or after the cursor.\n    var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||\n        re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];\n    if (!match) return null;\n    var dir = match.charAt(1) == \">\" ? 1 : -1;\n    if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;\n    var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));\n\n    var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);\n    if (found == null) return null;\n    return {from: Pos(where.line, pos), to: found && found.pos,\n            match: found && found.ch == match.charAt(0), forward: dir > 0};\n  }\n\n  // bracketRegex is used to specify which type of bracket to scan\n  // should be a regexp, e.g. /[[\\]]/\n  //\n  // Note: If \"where\" is on an open bracket, then this bracket is ignored.\n  //\n  // Returns false when no bracket was found, null when it reached\n  // maxScanLines and gave up\n  function scanForBracket(cm, where, dir, style, config) {\n    var maxScanLen = (config && config.maxScanLineLength) || 10000;\n    var maxScanLines = (config && config.maxScanLines) || 1000;\n\n    var stack = [];\n    var re = bracketRegex(config)\n    var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)\n                          : Math.max(cm.firstLine() - 1, where.line - maxScanLines);\n    for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {\n      var line = cm.getLine(lineNo);\n      if (!line) continue;\n      var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;\n      if (line.length > maxScanLen) continue;\n      if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);\n      for (; pos != end; pos += dir) {\n        var ch = line.charAt(pos);\n        if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {\n          var match = matching[ch];\n          if (match && (match.charAt(1) == \">\") == (dir > 0)) stack.push(ch);\n          else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};\n          else stack.pop();\n        }\n      }\n    }\n    return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;\n  }\n\n  function matchBrackets(cm, autoclear, config) {\n    // Disable brace matching in long lines, since it'll cause hugely slow updates\n    var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;\n    var marks = [], ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++) {\n      var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);\n      if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {\n        var style = match.match ? \"CodeMirror-matchingbracket\" : \"CodeMirror-nonmatchingbracket\";\n        marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));\n        if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)\n          marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));\n      }\n    }\n\n    if (marks.length) {\n      // Kludge to work around the IE bug from issue #1193, where text\n      // input stops going to the textare whever this fires.\n      if (ie_lt8 && cm.state.focused) cm.focus();\n\n      var clear = function() {\n        cm.operation(function() {\n          for (var i = 0; i < marks.length; i++) marks[i].clear();\n        });\n      };\n      if (autoclear) setTimeout(clear, 800);\n      else return clear;\n    }\n  }\n\n  function doMatchBrackets(cm) {\n    cm.operation(function() {\n      if (cm.state.matchBrackets.currentlyHighlighted) {\n        cm.state.matchBrackets.currentlyHighlighted();\n        cm.state.matchBrackets.currentlyHighlighted = null;\n      }\n      cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);\n    });\n  }\n\n  CodeMirror.defineOption(\"matchBrackets\", false, function(cm, val, old) {\n    function clear(cm) {\n      if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {\n        cm.state.matchBrackets.currentlyHighlighted();\n        cm.state.matchBrackets.currentlyHighlighted = null;\n      }\n    }\n\n    if (old && old != CodeMirror.Init) {\n      cm.off(\"cursorActivity\", doMatchBrackets);\n      cm.off(\"focus\", doMatchBrackets)\n      cm.off(\"blur\", clear)\n      clear(cm);\n    }\n    if (val) {\n      cm.state.matchBrackets = typeof val == \"object\" ? val : {};\n      cm.on(\"cursorActivity\", doMatchBrackets);\n      cm.on(\"focus\", doMatchBrackets)\n      cm.on(\"blur\", clear)\n    }\n  });\n\n  CodeMirror.defineExtension(\"matchBrackets\", function() {matchBrackets(this, true);});\n  CodeMirror.defineExtension(\"findMatchingBracket\", function(pos, config, oldConfig){\n    // Backwards-compatibility kludge\n    if (oldConfig || typeof config == \"boolean\") {\n      if (!oldConfig) {\n        config = config ? {strict: true} : null\n      } else {\n        oldConfig.strict = config\n        config = oldConfig\n      }\n    }\n    return findMatchingBracket(this, pos, config)\n  });\n  CodeMirror.defineExtension(\"scanForBracket\", function(pos, dir, style, config){\n    return scanForBracket(this, pos, dir, style, config);\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/edit/matchtags.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../fold/xml-fold\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../fold/xml-fold\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"matchTags\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.off(\"cursorActivity\", doMatchTags);\n      cm.off(\"viewportChange\", maybeUpdateMatch);\n      clear(cm);\n    }\n    if (val) {\n      cm.state.matchBothTags = typeof val == \"object\" && val.bothTags;\n      cm.on(\"cursorActivity\", doMatchTags);\n      cm.on(\"viewportChange\", maybeUpdateMatch);\n      doMatchTags(cm);\n    }\n  });\n\n  function clear(cm) {\n    if (cm.state.tagHit) cm.state.tagHit.clear();\n    if (cm.state.tagOther) cm.state.tagOther.clear();\n    cm.state.tagHit = cm.state.tagOther = null;\n  }\n\n  function doMatchTags(cm) {\n    cm.state.failedTagMatch = false;\n    cm.operation(function() {\n      clear(cm);\n      if (cm.somethingSelected()) return;\n      var cur = cm.getCursor(), range = cm.getViewport();\n      range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);\n      var match = CodeMirror.findMatchingTag(cm, cur, range);\n      if (!match) return;\n      if (cm.state.matchBothTags) {\n        var hit = match.at == \"open\" ? match.open : match.close;\n        if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: \"CodeMirror-matchingtag\"});\n      }\n      var other = match.at == \"close\" ? match.open : match.close;\n      if (other)\n        cm.state.tagOther = cm.markText(other.from, other.to, {className: \"CodeMirror-matchingtag\"});\n      else\n        cm.state.failedTagMatch = true;\n    });\n  }\n\n  function maybeUpdateMatch(cm) {\n    if (cm.state.failedTagMatch) doMatchTags(cm);\n  }\n\n  CodeMirror.commands.toMatchingTag = function(cm) {\n    var found = CodeMirror.findMatchingTag(cm, cm.getCursor());\n    if (found) {\n      var other = found.at == \"close\" ? found.open : found.close;\n      if (other) cm.extendSelection(other.to, other.from);\n    }\n  };\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/edit/trailingspace.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  CodeMirror.defineOption(\"showTrailingSpace\", false, function(cm, val, prev) {\n    if (prev == CodeMirror.Init) prev = false;\n    if (prev && !val)\n      cm.removeOverlay(\"trailingspace\");\n    else if (!prev && val)\n      cm.addOverlay({\n        token: function(stream) {\n          for (var l = stream.string.length, i = l; i && /\\s/.test(stream.string.charAt(i - 1)); --i) {}\n          if (i > stream.pos) { stream.pos = i; return null; }\n          stream.pos = l;\n          return \"trailingspace\";\n        },\n        name: \"trailingspace\"\n      });\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/brace-fold.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerHelper(\"fold\", \"brace\", function(cm, start) {\n  var line = start.line, lineText = cm.getLine(line);\n  var tokenType;\n\n  function findOpening(openCh) {\n    for (var at = start.ch, pass = 0;;) {\n      var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);\n      if (found == -1) {\n        if (pass == 1) break;\n        pass = 1;\n        at = lineText.length;\n        continue;\n      }\n      if (pass == 1 && found < start.ch) break;\n      tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));\n      if (!/^(comment|string)/.test(tokenType)) return found + 1;\n      at = found - 1;\n    }\n  }\n\n  var startToken = \"{\", endToken = \"}\", startCh = findOpening(\"{\");\n  if (startCh == null) {\n    startToken = \"[\", endToken = \"]\";\n    startCh = findOpening(\"[\");\n  }\n\n  if (startCh == null) return;\n  var count = 1, lastLine = cm.lastLine(), end, endCh;\n  outer: for (var i = line; i <= lastLine; ++i) {\n    var text = cm.getLine(i), pos = i == line ? startCh : 0;\n    for (;;) {\n      var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);\n      if (nextOpen < 0) nextOpen = text.length;\n      if (nextClose < 0) nextClose = text.length;\n      pos = Math.min(nextOpen, nextClose);\n      if (pos == text.length) break;\n      if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {\n        if (pos == nextOpen) ++count;\n        else if (!--count) { end = i; endCh = pos; break outer; }\n      }\n      ++pos;\n    }\n  }\n  if (end == null || line == end) return;\n  return {from: CodeMirror.Pos(line, startCh),\n          to: CodeMirror.Pos(end, endCh)};\n});\n\nCodeMirror.registerHelper(\"fold\", \"import\", function(cm, start) {\n  function hasImport(line) {\n    if (line < cm.firstLine() || line > cm.lastLine()) return null;\n    var start = cm.getTokenAt(CodeMirror.Pos(line, 1));\n    if (!/\\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));\n    if (start.type != \"keyword\" || start.string != \"import\") return null;\n    // Now find closing semicolon, return its position\n    for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {\n      var text = cm.getLine(i), semi = text.indexOf(\";\");\n      if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};\n    }\n  }\n\n  var startLine = start.line, has = hasImport(startLine), prev;\n  if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))\n    return null;\n  for (var end = has.end;;) {\n    var next = hasImport(end.line + 1);\n    if (next == null) break;\n    end = next.end;\n  }\n  return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};\n});\n\nCodeMirror.registerHelper(\"fold\", \"include\", function(cm, start) {\n  function hasInclude(line) {\n    if (line < cm.firstLine() || line > cm.lastLine()) return null;\n    var start = cm.getTokenAt(CodeMirror.Pos(line, 1));\n    if (!/\\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));\n    if (start.type == \"meta\" && start.string.slice(0, 8) == \"#include\") return start.start + 8;\n  }\n\n  var startLine = start.line, has = hasInclude(startLine);\n  if (has == null || hasInclude(startLine - 1) != null) return null;\n  for (var end = startLine;;) {\n    var next = hasInclude(end + 1);\n    if (next == null) break;\n    ++end;\n  }\n  return {from: CodeMirror.Pos(startLine, has + 1),\n          to: cm.clipPos(CodeMirror.Pos(end))};\n});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/comment-fold.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerGlobalHelper(\"fold\", \"comment\", function(mode) {\n  return mode.blockCommentStart && mode.blockCommentEnd;\n}, function(cm, start) {\n  var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;\n  if (!startToken || !endToken) return;\n  var line = start.line, lineText = cm.getLine(line);\n\n  var startCh;\n  for (var at = start.ch, pass = 0;;) {\n    var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);\n    if (found == -1) {\n      if (pass == 1) return;\n      pass = 1;\n      at = lineText.length;\n      continue;\n    }\n    if (pass == 1 && found < start.ch) return;\n    if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&\n        (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||\n         !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {\n      startCh = found + startToken.length;\n      break;\n    }\n    at = found - 1;\n  }\n\n  var depth = 1, lastLine = cm.lastLine(), end, endCh;\n  outer: for (var i = line; i <= lastLine; ++i) {\n    var text = cm.getLine(i), pos = i == line ? startCh : 0;\n    for (;;) {\n      var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);\n      if (nextOpen < 0) nextOpen = text.length;\n      if (nextClose < 0) nextClose = text.length;\n      pos = Math.min(nextOpen, nextClose);\n      if (pos == text.length) break;\n      if (pos == nextOpen) ++depth;\n      else if (!--depth) { end = i; endCh = pos; break outer; }\n      ++pos;\n    }\n  }\n  if (end == null || line == end && endCh == startCh) return;\n  return {from: CodeMirror.Pos(line, startCh),\n          to: CodeMirror.Pos(end, endCh)};\n});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/foldcode.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function doFold(cm, pos, options, force) {\n    if (options && options.call) {\n      var finder = options;\n      options = null;\n    } else {\n      var finder = getOption(cm, options, \"rangeFinder\");\n    }\n    if (typeof pos == \"number\") pos = CodeMirror.Pos(pos, 0);\n    var minSize = getOption(cm, options, \"minFoldSize\");\n\n    function getRange(allowFolded) {\n      var range = finder(cm, pos);\n      if (!range || range.to.line - range.from.line < minSize) return null;\n      var marks = cm.findMarksAt(range.from);\n      for (var i = 0; i < marks.length; ++i) {\n        if (marks[i].__isFold && force !== \"fold\") {\n          if (!allowFolded) return null;\n          range.cleared = true;\n          marks[i].clear();\n        }\n      }\n      return range;\n    }\n\n    var range = getRange(true);\n    if (getOption(cm, options, \"scanUp\")) while (!range && pos.line > cm.firstLine()) {\n      pos = CodeMirror.Pos(pos.line - 1, 0);\n      range = getRange(false);\n    }\n    if (!range || range.cleared || force === \"unfold\") return;\n\n    var myWidget = makeWidget(cm, options, range);\n    CodeMirror.on(myWidget, \"mousedown\", function(e) {\n      myRange.clear();\n      CodeMirror.e_preventDefault(e);\n    });\n    var myRange = cm.markText(range.from, range.to, {\n      replacedWith: myWidget,\n      clearOnEnter: getOption(cm, options, \"clearOnEnter\"),\n      __isFold: true\n    });\n    myRange.on(\"clear\", function(from, to) {\n      CodeMirror.signal(cm, \"unfold\", cm, from, to);\n    });\n    CodeMirror.signal(cm, \"fold\", cm, range.from, range.to);\n  }\n\n  function makeWidget(cm, options, range) {\n    var widget = getOption(cm, options, \"widget\");\n\n    if (typeof widget == \"function\") {\n      widget = widget(range.from, range.to);\n    }\n\n    if (typeof widget == \"string\") {\n      var text = document.createTextNode(widget);\n      widget = document.createElement(\"span\");\n      widget.appendChild(text);\n      widget.className = \"CodeMirror-foldmarker\";\n    } else if (widget) {\n      widget = widget.cloneNode(true)\n    }\n    return widget;\n  }\n\n  // Clumsy backwards-compatible interface\n  CodeMirror.newFoldFunction = function(rangeFinder, widget) {\n    return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };\n  };\n\n  // New-style interface\n  CodeMirror.defineExtension(\"foldCode\", function(pos, options, force) {\n    doFold(this, pos, options, force);\n  });\n\n  CodeMirror.defineExtension(\"isFolded\", function(pos) {\n    var marks = this.findMarksAt(pos);\n    for (var i = 0; i < marks.length; ++i)\n      if (marks[i].__isFold) return true;\n  });\n\n  CodeMirror.commands.toggleFold = function(cm) {\n    cm.foldCode(cm.getCursor());\n  };\n  CodeMirror.commands.fold = function(cm) {\n    cm.foldCode(cm.getCursor(), null, \"fold\");\n  };\n  CodeMirror.commands.unfold = function(cm) {\n    cm.foldCode(cm.getCursor(), null, \"unfold\");\n  };\n  CodeMirror.commands.foldAll = function(cm) {\n    cm.operation(function() {\n      for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)\n        cm.foldCode(CodeMirror.Pos(i, 0), null, \"fold\");\n    });\n  };\n  CodeMirror.commands.unfoldAll = function(cm) {\n    cm.operation(function() {\n      for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)\n        cm.foldCode(CodeMirror.Pos(i, 0), null, \"unfold\");\n    });\n  };\n\n  CodeMirror.registerHelper(\"fold\", \"combine\", function() {\n    var funcs = Array.prototype.slice.call(arguments, 0);\n    return function(cm, start) {\n      for (var i = 0; i < funcs.length; ++i) {\n        var found = funcs[i](cm, start);\n        if (found) return found;\n      }\n    };\n  });\n\n  CodeMirror.registerHelper(\"fold\", \"auto\", function(cm, start) {\n    var helpers = cm.getHelpers(start, \"fold\");\n    for (var i = 0; i < helpers.length; i++) {\n      var cur = helpers[i](cm, start);\n      if (cur) return cur;\n    }\n  });\n\n  var defaultOptions = {\n    rangeFinder: CodeMirror.fold.auto,\n    widget: \"\\u2194\",\n    minFoldSize: 0,\n    scanUp: false,\n    clearOnEnter: true\n  };\n\n  CodeMirror.defineOption(\"foldOptions\", null);\n\n  function getOption(cm, options, name) {\n    if (options && options[name] !== undefined)\n      return options[name];\n    var editorOptions = cm.options.foldOptions;\n    if (editorOptions && editorOptions[name] !== undefined)\n      return editorOptions[name];\n    return defaultOptions[name];\n  }\n\n  CodeMirror.defineExtension(\"foldOption\", function(options, name) {\n    return getOption(this, options, name);\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/foldgutter.css",
    "content": ".CodeMirror-foldmarker {\n  color: blue;\n  text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;\n  font-family: arial;\n  line-height: .3;\n  cursor: pointer;\n}\n.CodeMirror-foldgutter {\n  width: .7em;\n}\n.CodeMirror-foldgutter-open,\n.CodeMirror-foldgutter-folded {\n  cursor: pointer;\n}\n.CodeMirror-foldgutter-open:after {\n  content: \"\\25BE\";\n}\n.CodeMirror-foldgutter-folded:after {\n  content: \"\\25B8\";\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/foldgutter.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./foldcode\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./foldcode\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"foldGutter\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.clearGutter(cm.state.foldGutter.options.gutter);\n      cm.state.foldGutter = null;\n      cm.off(\"gutterClick\", onGutterClick);\n      cm.off(\"changes\", onChange);\n      cm.off(\"viewportChange\", onViewportChange);\n      cm.off(\"fold\", onFold);\n      cm.off(\"unfold\", onFold);\n      cm.off(\"swapDoc\", onChange);\n    }\n    if (val) {\n      cm.state.foldGutter = new State(parseOptions(val));\n      updateInViewport(cm);\n      cm.on(\"gutterClick\", onGutterClick);\n      cm.on(\"changes\", onChange);\n      cm.on(\"viewportChange\", onViewportChange);\n      cm.on(\"fold\", onFold);\n      cm.on(\"unfold\", onFold);\n      cm.on(\"swapDoc\", onChange);\n    }\n  });\n\n  var Pos = CodeMirror.Pos;\n\n  function State(options) {\n    this.options = options;\n    this.from = this.to = 0;\n  }\n\n  function parseOptions(opts) {\n    if (opts === true) opts = {};\n    if (opts.gutter == null) opts.gutter = \"CodeMirror-foldgutter\";\n    if (opts.indicatorOpen == null) opts.indicatorOpen = \"CodeMirror-foldgutter-open\";\n    if (opts.indicatorFolded == null) opts.indicatorFolded = \"CodeMirror-foldgutter-folded\";\n    return opts;\n  }\n\n  function isFolded(cm, line) {\n    var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));\n    for (var i = 0; i < marks.length; ++i) {\n      if (marks[i].__isFold) {\n        var fromPos = marks[i].find(-1);\n        if (fromPos && fromPos.line === line)\n          return marks[i];\n      }\n    }\n  }\n\n  function marker(spec) {\n    if (typeof spec == \"string\") {\n      var elt = document.createElement(\"div\");\n      elt.className = spec + \" CodeMirror-guttermarker-subtle\";\n      return elt;\n    } else {\n      return spec.cloneNode(true);\n    }\n  }\n\n  function updateFoldInfo(cm, from, to) {\n    var opts = cm.state.foldGutter.options, cur = from - 1;\n    var minSize = cm.foldOption(opts, \"minFoldSize\");\n    var func = cm.foldOption(opts, \"rangeFinder\");\n    // we can reuse the built-in indicator element if its className matches the new state\n    var clsFolded = typeof opts.indicatorFolded == \"string\" && classTest(opts.indicatorFolded);\n    var clsOpen = typeof opts.indicatorOpen == \"string\" && classTest(opts.indicatorOpen);\n    cm.eachLine(from, to, function(line) {\n      ++cur;\n      var mark = null;\n      var old = line.gutterMarkers;\n      if (old) old = old[opts.gutter];\n      if (isFolded(cm, cur)) {\n        if (clsFolded && old && clsFolded.test(old.className)) return;\n        mark = marker(opts.indicatorFolded);\n      } else {\n        var pos = Pos(cur, 0);\n        var range = func && func(cm, pos);\n        if (range && range.to.line - range.from.line >= minSize) {\n          if (clsOpen && old && clsOpen.test(old.className)) return;\n          mark = marker(opts.indicatorOpen);\n        }\n      }\n      if (!mark && !old) return;\n      cm.setGutterMarker(line, opts.gutter, mark);\n    });\n  }\n\n  // copied from CodeMirror/src/util/dom.js\n  function classTest(cls) { return new RegExp(\"(^|\\\\s)\" + cls + \"(?:$|\\\\s)\\\\s*\") }\n\n  function updateInViewport(cm) {\n    var vp = cm.getViewport(), state = cm.state.foldGutter;\n    if (!state) return;\n    cm.operation(function() {\n      updateFoldInfo(cm, vp.from, vp.to);\n    });\n    state.from = vp.from; state.to = vp.to;\n  }\n\n  function onGutterClick(cm, line, gutter) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var opts = state.options;\n    if (gutter != opts.gutter) return;\n    var folded = isFolded(cm, line);\n    if (folded) folded.clear();\n    else cm.foldCode(Pos(line, 0), opts);\n  }\n\n  function onChange(cm) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var opts = state.options;\n    state.from = state.to = 0;\n    clearTimeout(state.changeUpdate);\n    state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);\n  }\n\n  function onViewportChange(cm) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var opts = state.options;\n    clearTimeout(state.changeUpdate);\n    state.changeUpdate = setTimeout(function() {\n      var vp = cm.getViewport();\n      if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {\n        updateInViewport(cm);\n      } else {\n        cm.operation(function() {\n          if (vp.from < state.from) {\n            updateFoldInfo(cm, vp.from, state.from);\n            state.from = vp.from;\n          }\n          if (vp.to > state.to) {\n            updateFoldInfo(cm, state.to, vp.to);\n            state.to = vp.to;\n          }\n        });\n      }\n    }, opts.updateViewportTimeSpan || 400);\n  }\n\n  function onFold(cm, from) {\n    var state = cm.state.foldGutter;\n    if (!state) return;\n    var line = from.line;\n    if (line >= state.from && line < state.to)\n      updateFoldInfo(cm, line, line + 1);\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/indent-fold.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nfunction lineIndent(cm, lineNo) {\n  var text = cm.getLine(lineNo)\n  var spaceTo = text.search(/\\S/)\n  if (spaceTo == -1 || /\\bcomment\\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))\n    return -1\n  return CodeMirror.countColumn(text, null, cm.getOption(\"tabSize\"))\n}\n\nCodeMirror.registerHelper(\"fold\", \"indent\", function(cm, start) {\n  var myIndent = lineIndent(cm, start.line)\n  if (myIndent < 0) return\n  var lastLineInFold = null\n\n  // Go through lines until we find a line that definitely doesn't belong in\n  // the block we're folding, or to the end.\n  for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {\n    var indent = lineIndent(cm, i)\n    if (indent == -1) {\n    } else if (indent > myIndent) {\n      // Lines with a greater indent are considered part of the block.\n      lastLineInFold = i;\n    } else {\n      // If this line has non-space, non-comment content, and is\n      // indented less or equal to the start line, it is the start of\n      // another block.\n      break;\n    }\n  }\n  if (lastLineInFold) return {\n    from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),\n    to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)\n  };\n});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/markdown-fold.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerHelper(\"fold\", \"markdown\", function(cm, start) {\n  var maxDepth = 100;\n\n  function isHeader(lineNo) {\n    var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));\n    return tokentype && /\\bheader\\b/.test(tokentype);\n  }\n\n  function headerLevel(lineNo, line, nextLine) {\n    var match = line && line.match(/^#+/);\n    if (match && isHeader(lineNo)) return match[0].length;\n    match = nextLine && nextLine.match(/^[=\\-]+\\s*$/);\n    if (match && isHeader(lineNo + 1)) return nextLine[0] == \"=\" ? 1 : 2;\n    return maxDepth;\n  }\n\n  var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);\n  var level = headerLevel(start.line, firstLine, nextLine);\n  if (level === maxDepth) return undefined;\n\n  var lastLineNo = cm.lastLine();\n  var end = start.line, nextNextLine = cm.getLine(end + 2);\n  while (end < lastLineNo) {\n    if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;\n    ++end;\n    nextLine = nextNextLine;\n    nextNextLine = cm.getLine(end + 2);\n  }\n\n  return {\n    from: CodeMirror.Pos(start.line, firstLine.length),\n    to: CodeMirror.Pos(end, cm.getLine(end).length)\n  };\n});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/fold/xml-fold.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var Pos = CodeMirror.Pos;\n  function cmp(a, b) { return a.line - b.line || a.ch - b.ch; }\n\n  var nameStartChar = \"A-Z_a-z\\\\u00C0-\\\\u00D6\\\\u00D8-\\\\u00F6\\\\u00F8-\\\\u02FF\\\\u0370-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C-\\\\u200D\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD\";\n  var nameChar = nameStartChar + \"\\-\\:\\.0-9\\\\u00B7\\\\u0300-\\\\u036F\\\\u203F-\\\\u2040\";\n  var xmlTagStart = new RegExp(\"<(/?)([\" + nameStartChar + \"][\" + nameChar + \"]*)\", \"g\");\n\n  function Iter(cm, line, ch, range) {\n    this.line = line; this.ch = ch;\n    this.cm = cm; this.text = cm.getLine(line);\n    this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();\n    this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();\n  }\n\n  function tagAt(iter, ch) {\n    var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch));\n    return type && /\\btag\\b/.test(type);\n  }\n\n  function nextLine(iter) {\n    if (iter.line >= iter.max) return;\n    iter.ch = 0;\n    iter.text = iter.cm.getLine(++iter.line);\n    return true;\n  }\n  function prevLine(iter) {\n    if (iter.line <= iter.min) return;\n    iter.text = iter.cm.getLine(--iter.line);\n    iter.ch = iter.text.length;\n    return true;\n  }\n\n  function toTagEnd(iter) {\n    for (;;) {\n      var gt = iter.text.indexOf(\">\", iter.ch);\n      if (gt == -1) { if (nextLine(iter)) continue; else return; }\n      if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; }\n      var lastSlash = iter.text.lastIndexOf(\"/\", gt);\n      var selfClose = lastSlash > -1 && !/\\S/.test(iter.text.slice(lastSlash + 1, gt));\n      iter.ch = gt + 1;\n      return selfClose ? \"selfClose\" : \"regular\";\n    }\n  }\n  function toTagStart(iter) {\n    for (;;) {\n      var lt = iter.ch ? iter.text.lastIndexOf(\"<\", iter.ch - 1) : -1;\n      if (lt == -1) { if (prevLine(iter)) continue; else return; }\n      if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; }\n      xmlTagStart.lastIndex = lt;\n      iter.ch = lt;\n      var match = xmlTagStart.exec(iter.text);\n      if (match && match.index == lt) return match;\n    }\n  }\n\n  function toNextTag(iter) {\n    for (;;) {\n      xmlTagStart.lastIndex = iter.ch;\n      var found = xmlTagStart.exec(iter.text);\n      if (!found) { if (nextLine(iter)) continue; else return; }\n      if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; }\n      iter.ch = found.index + found[0].length;\n      return found;\n    }\n  }\n  function toPrevTag(iter) {\n    for (;;) {\n      var gt = iter.ch ? iter.text.lastIndexOf(\">\", iter.ch - 1) : -1;\n      if (gt == -1) { if (prevLine(iter)) continue; else return; }\n      if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; }\n      var lastSlash = iter.text.lastIndexOf(\"/\", gt);\n      var selfClose = lastSlash > -1 && !/\\S/.test(iter.text.slice(lastSlash + 1, gt));\n      iter.ch = gt + 1;\n      return selfClose ? \"selfClose\" : \"regular\";\n    }\n  }\n\n  function findMatchingClose(iter, tag) {\n    var stack = [];\n    for (;;) {\n      var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0);\n      if (!next || !(end = toTagEnd(iter))) return;\n      if (end == \"selfClose\") continue;\n      if (next[1]) { // closing tag\n        for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) {\n          stack.length = i;\n          break;\n        }\n        if (i < 0 && (!tag || tag == next[2])) return {\n          tag: next[2],\n          from: Pos(startLine, startCh),\n          to: Pos(iter.line, iter.ch)\n        };\n      } else { // opening tag\n        stack.push(next[2]);\n      }\n    }\n  }\n  function findMatchingOpen(iter, tag) {\n    var stack = [];\n    for (;;) {\n      var prev = toPrevTag(iter);\n      if (!prev) return;\n      if (prev == \"selfClose\") { toTagStart(iter); continue; }\n      var endLine = iter.line, endCh = iter.ch;\n      var start = toTagStart(iter);\n      if (!start) return;\n      if (start[1]) { // closing tag\n        stack.push(start[2]);\n      } else { // opening tag\n        for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) {\n          stack.length = i;\n          break;\n        }\n        if (i < 0 && (!tag || tag == start[2])) return {\n          tag: start[2],\n          from: Pos(iter.line, iter.ch),\n          to: Pos(endLine, endCh)\n        };\n      }\n    }\n  }\n\n  CodeMirror.registerHelper(\"fold\", \"xml\", function(cm, start) {\n    var iter = new Iter(cm, start.line, 0);\n    for (;;) {\n      var openTag = toNextTag(iter)\n      if (!openTag || iter.line != start.line) return\n      var end = toTagEnd(iter)\n      if (!end) return\n      if (!openTag[1] && end != \"selfClose\") {\n        var startPos = Pos(iter.line, iter.ch);\n        var endPos = findMatchingClose(iter, openTag[2]);\n        return endPos && cmp(endPos.from, startPos) > 0 ? {from: startPos, to: endPos.from} : null\n      }\n    }\n  });\n  CodeMirror.findMatchingTag = function(cm, pos, range) {\n    var iter = new Iter(cm, pos.line, pos.ch, range);\n    if (iter.text.indexOf(\">\") == -1 && iter.text.indexOf(\"<\") == -1) return;\n    var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch);\n    var start = end && toTagStart(iter);\n    if (!end || !start || cmp(iter, pos) > 0) return;\n    var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]};\n    if (end == \"selfClose\") return {open: here, close: null, at: \"open\"};\n\n    if (start[1]) { // closing tag\n      return {open: findMatchingOpen(iter, start[2]), close: here, at: \"close\"};\n    } else { // opening tag\n      iter = new Iter(cm, to.line, to.ch, range);\n      return {open: here, close: findMatchingClose(iter, start[2]), at: \"open\"};\n    }\n  };\n\n  CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {\n    var iter = new Iter(cm, pos.line, pos.ch, range);\n    for (;;) {\n      var open = findMatchingOpen(iter, tag);\n      if (!open) break;\n      var forward = new Iter(cm, pos.line, pos.ch, range);\n      var close = findMatchingClose(forward, open.tag);\n      if (close) return {open: open, close: close};\n    }\n  };\n\n  // Used by addon/edit/closetag.js\n  CodeMirror.scanForClosingTag = function(cm, pos, name, end) {\n    var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null);\n    return findMatchingClose(iter, name);\n  };\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/hint/anyword-hint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var WORD = /[\\w$]+/, RANGE = 500;\n\n  CodeMirror.registerHelper(\"hint\", \"anyword\", function(editor, options) {\n    var word = options && options.word || WORD;\n    var range = options && options.range || RANGE;\n    var cur = editor.getCursor(), curLine = editor.getLine(cur.line);\n    var end = cur.ch, start = end;\n    while (start && word.test(curLine.charAt(start - 1))) --start;\n    var curWord = start != end && curLine.slice(start, end);\n\n    var list = options && options.list || [], seen = {};\n    var re = new RegExp(word.source, \"g\");\n    for (var dir = -1; dir <= 1; dir += 2) {\n      var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;\n      for (; line != endLine; line += dir) {\n        var text = editor.getLine(line), m;\n        while (m = re.exec(text)) {\n          if (line == cur.line && m[0] === curWord) continue;\n          if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {\n            seen[m[0]] = true;\n            list.push(m[0]);\n          }\n        }\n      }\n    }\n    return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/hint/html-hint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./xml-hint\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./xml-hint\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var langs = \"ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu\".split(\" \");\n  var targets = [\"_blank\", \"_self\", \"_top\", \"_parent\"];\n  var charsets = [\"ascii\", \"utf-8\", \"utf-16\", \"latin1\", \"latin1\"];\n  var methods = [\"get\", \"post\", \"put\", \"delete\"];\n  var encs = [\"application/x-www-form-urlencoded\", \"multipart/form-data\", \"text/plain\"];\n  var media = [\"all\", \"screen\", \"print\", \"embossed\", \"braille\", \"handheld\", \"print\", \"projection\", \"screen\", \"tty\", \"tv\", \"speech\",\n               \"3d-glasses\", \"resolution [>][<][=] [X]\", \"device-aspect-ratio: X/Y\", \"orientation:portrait\",\n               \"orientation:landscape\", \"device-height: [X]\", \"device-width: [X]\"];\n  var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags\n\n  var data = {\n    a: {\n      attrs: {\n        href: null, ping: null, type: null,\n        media: media,\n        target: targets,\n        hreflang: langs\n      }\n    },\n    abbr: s,\n    acronym: s,\n    address: s,\n    applet: s,\n    area: {\n      attrs: {\n        alt: null, coords: null, href: null, target: null, ping: null,\n        media: media, hreflang: langs, type: null,\n        shape: [\"default\", \"rect\", \"circle\", \"poly\"]\n      }\n    },\n    article: s,\n    aside: s,\n    audio: {\n      attrs: {\n        src: null, mediagroup: null,\n        crossorigin: [\"anonymous\", \"use-credentials\"],\n        preload: [\"none\", \"metadata\", \"auto\"],\n        autoplay: [\"\", \"autoplay\"],\n        loop: [\"\", \"loop\"],\n        controls: [\"\", \"controls\"]\n      }\n    },\n    b: s,\n    base: { attrs: { href: null, target: targets } },\n    basefont: s,\n    bdi: s,\n    bdo: s,\n    big: s,\n    blockquote: { attrs: { cite: null } },\n    body: s,\n    br: s,\n    button: {\n      attrs: {\n        form: null, formaction: null, name: null, value: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"autofocus\"],\n        formenctype: encs,\n        formmethod: methods,\n        formnovalidate: [\"\", \"novalidate\"],\n        formtarget: targets,\n        type: [\"submit\", \"reset\", \"button\"]\n      }\n    },\n    canvas: { attrs: { width: null, height: null } },\n    caption: s,\n    center: s,\n    cite: s,\n    code: s,\n    col: { attrs: { span: null } },\n    colgroup: { attrs: { span: null } },\n    command: {\n      attrs: {\n        type: [\"command\", \"checkbox\", \"radio\"],\n        label: null, icon: null, radiogroup: null, command: null, title: null,\n        disabled: [\"\", \"disabled\"],\n        checked: [\"\", \"checked\"]\n      }\n    },\n    data: { attrs: { value: null } },\n    datagrid: { attrs: { disabled: [\"\", \"disabled\"], multiple: [\"\", \"multiple\"] } },\n    datalist: { attrs: { data: null } },\n    dd: s,\n    del: { attrs: { cite: null, datetime: null } },\n    details: { attrs: { open: [\"\", \"open\"] } },\n    dfn: s,\n    dir: s,\n    div: s,\n    dl: s,\n    dt: s,\n    em: s,\n    embed: { attrs: { src: null, type: null, width: null, height: null } },\n    eventsource: { attrs: { src: null } },\n    fieldset: { attrs: { disabled: [\"\", \"disabled\"], form: null, name: null } },\n    figcaption: s,\n    figure: s,\n    font: s,\n    footer: s,\n    form: {\n      attrs: {\n        action: null, name: null,\n        \"accept-charset\": charsets,\n        autocomplete: [\"on\", \"off\"],\n        enctype: encs,\n        method: methods,\n        novalidate: [\"\", \"novalidate\"],\n        target: targets\n      }\n    },\n    frame: s,\n    frameset: s,\n    h1: s, h2: s, h3: s, h4: s, h5: s, h6: s,\n    head: {\n      attrs: {},\n      children: [\"title\", \"base\", \"link\", \"style\", \"meta\", \"script\", \"noscript\", \"command\"]\n    },\n    header: s,\n    hgroup: s,\n    hr: s,\n    html: {\n      attrs: { manifest: null },\n      children: [\"head\", \"body\"]\n    },\n    i: s,\n    iframe: {\n      attrs: {\n        src: null, srcdoc: null, name: null, width: null, height: null,\n        sandbox: [\"allow-top-navigation\", \"allow-same-origin\", \"allow-forms\", \"allow-scripts\"],\n        seamless: [\"\", \"seamless\"]\n      }\n    },\n    img: {\n      attrs: {\n        alt: null, src: null, ismap: null, usemap: null, width: null, height: null,\n        crossorigin: [\"anonymous\", \"use-credentials\"]\n      }\n    },\n    input: {\n      attrs: {\n        alt: null, dirname: null, form: null, formaction: null,\n        height: null, list: null, max: null, maxlength: null, min: null,\n        name: null, pattern: null, placeholder: null, size: null, src: null,\n        step: null, value: null, width: null,\n        accept: [\"audio/*\", \"video/*\", \"image/*\"],\n        autocomplete: [\"on\", \"off\"],\n        autofocus: [\"\", \"autofocus\"],\n        checked: [\"\", \"checked\"],\n        disabled: [\"\", \"disabled\"],\n        formenctype: encs,\n        formmethod: methods,\n        formnovalidate: [\"\", \"novalidate\"],\n        formtarget: targets,\n        multiple: [\"\", \"multiple\"],\n        readonly: [\"\", \"readonly\"],\n        required: [\"\", \"required\"],\n        type: [\"hidden\", \"text\", \"search\", \"tel\", \"url\", \"email\", \"password\", \"datetime\", \"date\", \"month\",\n               \"week\", \"time\", \"datetime-local\", \"number\", \"range\", \"color\", \"checkbox\", \"radio\",\n               \"file\", \"submit\", \"image\", \"reset\", \"button\"]\n      }\n    },\n    ins: { attrs: { cite: null, datetime: null } },\n    kbd: s,\n    keygen: {\n      attrs: {\n        challenge: null, form: null, name: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"disabled\"],\n        keytype: [\"RSA\"]\n      }\n    },\n    label: { attrs: { \"for\": null, form: null } },\n    legend: s,\n    li: { attrs: { value: null } },\n    link: {\n      attrs: {\n        href: null, type: null,\n        hreflang: langs,\n        media: media,\n        sizes: [\"all\", \"16x16\", \"16x16 32x32\", \"16x16 32x32 64x64\"]\n      }\n    },\n    map: { attrs: { name: null } },\n    mark: s,\n    menu: { attrs: { label: null, type: [\"list\", \"context\", \"toolbar\"] } },\n    meta: {\n      attrs: {\n        content: null,\n        charset: charsets,\n        name: [\"viewport\", \"application-name\", \"author\", \"description\", \"generator\", \"keywords\"],\n        \"http-equiv\": [\"content-language\", \"content-type\", \"default-style\", \"refresh\"]\n      }\n    },\n    meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } },\n    nav: s,\n    noframes: s,\n    noscript: s,\n    object: {\n      attrs: {\n        data: null, type: null, name: null, usemap: null, form: null, width: null, height: null,\n        typemustmatch: [\"\", \"typemustmatch\"]\n      }\n    },\n    ol: { attrs: { reversed: [\"\", \"reversed\"], start: null, type: [\"1\", \"a\", \"A\", \"i\", \"I\"] } },\n    optgroup: { attrs: { disabled: [\"\", \"disabled\"], label: null } },\n    option: { attrs: { disabled: [\"\", \"disabled\"], label: null, selected: [\"\", \"selected\"], value: null } },\n    output: { attrs: { \"for\": null, form: null, name: null } },\n    p: s,\n    param: { attrs: { name: null, value: null } },\n    pre: s,\n    progress: { attrs: { value: null, max: null } },\n    q: { attrs: { cite: null } },\n    rp: s,\n    rt: s,\n    ruby: s,\n    s: s,\n    samp: s,\n    script: {\n      attrs: {\n        type: [\"text/javascript\"],\n        src: null,\n        async: [\"\", \"async\"],\n        defer: [\"\", \"defer\"],\n        charset: charsets\n      }\n    },\n    section: s,\n    select: {\n      attrs: {\n        form: null, name: null, size: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"disabled\"],\n        multiple: [\"\", \"multiple\"]\n      }\n    },\n    small: s,\n    source: { attrs: { src: null, type: null, media: null } },\n    span: s,\n    strike: s,\n    strong: s,\n    style: {\n      attrs: {\n        type: [\"text/css\"],\n        media: media,\n        scoped: null\n      }\n    },\n    sub: s,\n    summary: s,\n    sup: s,\n    table: s,\n    tbody: s,\n    td: { attrs: { colspan: null, rowspan: null, headers: null } },\n    textarea: {\n      attrs: {\n        dirname: null, form: null, maxlength: null, name: null, placeholder: null,\n        rows: null, cols: null,\n        autofocus: [\"\", \"autofocus\"],\n        disabled: [\"\", \"disabled\"],\n        readonly: [\"\", \"readonly\"],\n        required: [\"\", \"required\"],\n        wrap: [\"soft\", \"hard\"]\n      }\n    },\n    tfoot: s,\n    th: { attrs: { colspan: null, rowspan: null, headers: null, scope: [\"row\", \"col\", \"rowgroup\", \"colgroup\"] } },\n    thead: s,\n    time: { attrs: { datetime: null } },\n    title: s,\n    tr: s,\n    track: {\n      attrs: {\n        src: null, label: null, \"default\": null,\n        kind: [\"subtitles\", \"captions\", \"descriptions\", \"chapters\", \"metadata\"],\n        srclang: langs\n      }\n    },\n    tt: s,\n    u: s,\n    ul: s,\n    \"var\": s,\n    video: {\n      attrs: {\n        src: null, poster: null, width: null, height: null,\n        crossorigin: [\"anonymous\", \"use-credentials\"],\n        preload: [\"auto\", \"metadata\", \"none\"],\n        autoplay: [\"\", \"autoplay\"],\n        mediagroup: [\"movie\"],\n        muted: [\"\", \"muted\"],\n        controls: [\"\", \"controls\"]\n      }\n    },\n    wbr: s\n  };\n\n  var globalAttrs = {\n    accesskey: [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"],\n    \"class\": null,\n    contenteditable: [\"true\", \"false\"],\n    contextmenu: null,\n    dir: [\"ltr\", \"rtl\", \"auto\"],\n    draggable: [\"true\", \"false\", \"auto\"],\n    dropzone: [\"copy\", \"move\", \"link\", \"string:\", \"file:\"],\n    hidden: [\"hidden\"],\n    id: null,\n    inert: [\"inert\"],\n    itemid: null,\n    itemprop: null,\n    itemref: null,\n    itemscope: [\"itemscope\"],\n    itemtype: null,\n    lang: [\"en\", \"es\"],\n    spellcheck: [\"true\", \"false\"],\n    autocorrect: [\"true\", \"false\"],\n    autocapitalize: [\"true\", \"false\"],\n    style: null,\n    tabindex: [\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\"],\n    title: null,\n    translate: [\"yes\", \"no\"],\n    onclick: null,\n    rel: [\"stylesheet\", \"alternate\", \"author\", \"bookmark\", \"help\", \"license\", \"next\", \"nofollow\", \"noreferrer\", \"prefetch\", \"prev\", \"search\", \"tag\"]\n  };\n  function populate(obj) {\n    for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr))\n      obj.attrs[attr] = globalAttrs[attr];\n  }\n\n  populate(s);\n  for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s)\n    populate(data[tag]);\n\n  CodeMirror.htmlSchema = data;\n  function htmlHint(cm, options) {\n    var local = {schemaInfo: data};\n    if (options) for (var opt in options) local[opt] = options[opt];\n    return CodeMirror.hint.xml(cm, local);\n  }\n  CodeMirror.registerHelper(\"hint\", \"html\", htmlHint);\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/hint/show-hint.css",
    "content": ".CodeMirror-hints {\n  position: absolute;\n  z-index: 10;\n  overflow: hidden;\n  list-style: none;\n\n  margin: 0;\n  padding: 2px;\n\n  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  box-shadow: 2px 3px 5px rgba(0,0,0,.2);\n  border-radius: 3px;\n  border: 1px solid silver;\n\n  background: white;\n  font-size: 90%;\n  font-family: monospace;\n\n  max-height: 20em;\n  overflow-y: auto;\n}\n\n.CodeMirror-hint {\n  margin: 0;\n  padding: 0 4px;\n  border-radius: 2px;\n  white-space: pre;\n  color: black;\n  cursor: pointer;\n}\n\nli.CodeMirror-hint-active {\n  background: #08f;\n  color: white;\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/hint/show-hint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var HINT_ELEMENT_CLASS        = \"CodeMirror-hint\";\n  var ACTIVE_HINT_ELEMENT_CLASS = \"CodeMirror-hint-active\";\n\n  // This is the old interface, kept around for now to stay\n  // backwards-compatible.\n  CodeMirror.showHint = function(cm, getHints, options) {\n    if (!getHints) return cm.showHint(options);\n    if (options && options.async) getHints.async = true;\n    var newOpts = {hint: getHints};\n    if (options) for (var prop in options) newOpts[prop] = options[prop];\n    return cm.showHint(newOpts);\n  };\n\n  CodeMirror.defineExtension(\"showHint\", function(options) {\n    options = parseOptions(this, this.getCursor(\"start\"), options);\n    var selections = this.listSelections()\n    if (selections.length > 1) return;\n    // By default, don't allow completion when something is selected.\n    // A hint function can have a `supportsSelection` property to\n    // indicate that it can handle selections.\n    if (this.somethingSelected()) {\n      if (!options.hint.supportsSelection) return;\n      // Don't try with cross-line selections\n      for (var i = 0; i < selections.length; i++)\n        if (selections[i].head.line != selections[i].anchor.line) return;\n    }\n\n    if (this.state.completionActive) this.state.completionActive.close();\n    var completion = this.state.completionActive = new Completion(this, options);\n    if (!completion.options.hint) return;\n\n    CodeMirror.signal(this, \"startCompletion\", this);\n    completion.update(true);\n  });\n\n  CodeMirror.defineExtension(\"closeHint\", function() {\n    if (this.state.completionActive) this.state.completionActive.close()\n  })\n\n  function Completion(cm, options) {\n    this.cm = cm;\n    this.options = options;\n    this.widget = null;\n    this.debounce = 0;\n    this.tick = 0;\n    this.startPos = this.cm.getCursor(\"start\");\n    this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;\n\n    var self = this;\n    cm.on(\"cursorActivity\", this.activityFunc = function() { self.cursorActivity(); });\n  }\n\n  var requestAnimationFrame = window.requestAnimationFrame || function(fn) {\n    return setTimeout(fn, 1000/60);\n  };\n  var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;\n\n  Completion.prototype = {\n    close: function() {\n      if (!this.active()) return;\n      this.cm.state.completionActive = null;\n      this.tick = null;\n      this.cm.off(\"cursorActivity\", this.activityFunc);\n\n      if (this.widget && this.data) CodeMirror.signal(this.data, \"close\");\n      if (this.widget) this.widget.close();\n      CodeMirror.signal(this.cm, \"endCompletion\", this.cm);\n    },\n\n    active: function() {\n      return this.cm.state.completionActive == this;\n    },\n\n    pick: function(data, i) {\n      var completion = data.list[i], self = this;\n      this.cm.operation(function() {\n        if (completion.hint)\n          completion.hint(self.cm, data, completion);\n        else\n          self.cm.replaceRange(getText(completion), completion.from || data.from,\n                               completion.to || data.to, \"complete\");\n        CodeMirror.signal(data, \"pick\", completion);\n        self.cm.scrollIntoView();\n      })\n      this.close();\n    },\n\n    cursorActivity: function() {\n      if (this.debounce) {\n        cancelAnimationFrame(this.debounce);\n        this.debounce = 0;\n      }\n\n      var identStart = this.startPos;\n      if(this.data) {\n        identStart = this.data.from;\n      }\n\n      var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);\n      if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||\n          pos.ch < identStart.ch || this.cm.somethingSelected() ||\n          (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {\n        this.close();\n      } else {\n        var self = this;\n        this.debounce = requestAnimationFrame(function() {self.update();});\n        if (this.widget) this.widget.disable();\n      }\n    },\n\n    update: function(first) {\n      if (this.tick == null) return\n      var self = this, myTick = ++this.tick\n      fetchHints(this.options.hint, this.cm, this.options, function(data) {\n        if (self.tick == myTick) self.finishUpdate(data, first)\n      })\n    },\n\n    finishUpdate: function(data, first) {\n      if (this.data) CodeMirror.signal(this.data, \"update\");\n\n      var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);\n      if (this.widget) this.widget.close();\n\n      this.data = data;\n\n      if (data && data.list.length) {\n        if (picked && data.list.length == 1) {\n          this.pick(data, 0);\n        } else {\n          this.widget = new Widget(this, data);\n          CodeMirror.signal(data, \"shown\");\n        }\n      }\n    }\n  };\n\n  function parseOptions(cm, pos, options) {\n    var editor = cm.options.hintOptions;\n    var out = {};\n    for (var prop in defaultOptions) out[prop] = defaultOptions[prop];\n    if (editor) for (var prop in editor)\n      if (editor[prop] !== undefined) out[prop] = editor[prop];\n    if (options) for (var prop in options)\n      if (options[prop] !== undefined) out[prop] = options[prop];\n    if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)\n    return out;\n  }\n\n  function getText(completion) {\n    if (typeof completion == \"string\") return completion;\n    else return completion.text;\n  }\n\n  function buildKeyMap(completion, handle) {\n    var baseMap = {\n      Up: function() {handle.moveFocus(-1);},\n      Down: function() {handle.moveFocus(1);},\n      PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},\n      PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},\n      Home: function() {handle.setFocus(0);},\n      End: function() {handle.setFocus(handle.length - 1);},\n      Enter: handle.pick,\n      Tab: handle.pick,\n      Esc: handle.close\n    };\n\n    var mac = /Mac/.test(navigator.platform);\n\n    if (mac) {\n      baseMap[\"Ctrl-P\"] = function() {handle.moveFocus(-1);};\n      baseMap[\"Ctrl-N\"] = function() {handle.moveFocus(1);};\n    }\n\n    var custom = completion.options.customKeys;\n    var ourMap = custom ? {} : baseMap;\n    function addBinding(key, val) {\n      var bound;\n      if (typeof val != \"string\")\n        bound = function(cm) { return val(cm, handle); };\n      // This mechanism is deprecated\n      else if (baseMap.hasOwnProperty(val))\n        bound = baseMap[val];\n      else\n        bound = val;\n      ourMap[key] = bound;\n    }\n    if (custom)\n      for (var key in custom) if (custom.hasOwnProperty(key))\n        addBinding(key, custom[key]);\n    var extra = completion.options.extraKeys;\n    if (extra)\n      for (var key in extra) if (extra.hasOwnProperty(key))\n        addBinding(key, extra[key]);\n    return ourMap;\n  }\n\n  function getHintElement(hintsElement, el) {\n    while (el && el != hintsElement) {\n      if (el.nodeName.toUpperCase() === \"LI\" && el.parentNode == hintsElement) return el;\n      el = el.parentNode;\n    }\n  }\n\n  function Widget(completion, data) {\n    this.completion = completion;\n    this.data = data;\n    this.picked = false;\n    var widget = this, cm = completion.cm;\n    var ownerDocument = cm.getInputField().ownerDocument;\n    var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;\n\n    var hints = this.hints = ownerDocument.createElement(\"ul\");\n    var theme = completion.cm.options.theme;\n    hints.className = \"CodeMirror-hints \" + theme;\n    this.selectedHint = data.selectedHint || 0;\n\n    var completions = data.list;\n    for (var i = 0; i < completions.length; ++i) {\n      var elt = hints.appendChild(ownerDocument.createElement(\"li\")), cur = completions[i];\n      var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? \"\" : \" \" + ACTIVE_HINT_ELEMENT_CLASS);\n      if (cur.className != null) className = cur.className + \" \" + className;\n      elt.className = className;\n      if (cur.render) cur.render(elt, data, cur);\n      else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));\n      elt.hintId = i;\n    }\n\n    var container = completion.options.container || ownerDocument.body;\n    var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);\n    var left = pos.left, top = pos.bottom, below = true;\n    var offsetLeft = 0, offsetTop = 0;\n    if (container !== ownerDocument.body) {\n      // We offset the cursor position because left and top are relative to the offsetParent's top left corner.\n      var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;\n      var offsetParent = isContainerPositioned ? container : container.offsetParent;\n      var offsetParentPosition = offsetParent.getBoundingClientRect();\n      var bodyPosition = ownerDocument.body.getBoundingClientRect();\n      offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);\n      offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);\n    }\n    hints.style.left = (left - offsetLeft) + \"px\";\n    hints.style.top = (top - offsetTop) + \"px\";\n\n    // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.\n    var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);\n    var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);\n    container.appendChild(hints);\n    var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;\n    var scrolls = hints.scrollHeight > hints.clientHeight + 1\n    var startScroll = cm.getScrollInfo();\n\n    if (overlapY > 0) {\n      var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);\n      if (curTop - height > 0) { // Fits above cursor\n        hints.style.top = (top = pos.top - height - offsetTop) + \"px\";\n        below = false;\n      } else if (height > winH) {\n        hints.style.height = (winH - 5) + \"px\";\n        hints.style.top = (top = pos.bottom - box.top - offsetTop) + \"px\";\n        var cursor = cm.getCursor();\n        if (data.from.ch != cursor.ch) {\n          pos = cm.cursorCoords(cursor);\n          hints.style.left = (left = pos.left - offsetLeft) + \"px\";\n          box = hints.getBoundingClientRect();\n        }\n      }\n    }\n    var overlapX = box.right - winW;\n    if (overlapX > 0) {\n      if (box.right - box.left > winW) {\n        hints.style.width = (winW - 5) + \"px\";\n        overlapX -= (box.right - box.left) - winW;\n      }\n      hints.style.left = (left = pos.left - overlapX - offsetLeft) + \"px\";\n    }\n    if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)\n      node.style.paddingRight = cm.display.nativeBarWidth + \"px\"\n\n    cm.addKeyMap(this.keyMap = buildKeyMap(completion, {\n      moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },\n      setFocus: function(n) { widget.changeActive(n); },\n      menuSize: function() { return widget.screenAmount(); },\n      length: completions.length,\n      close: function() { completion.close(); },\n      pick: function() { widget.pick(); },\n      data: data\n    }));\n\n    if (completion.options.closeOnUnfocus) {\n      var closingOnBlur;\n      cm.on(\"blur\", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });\n      cm.on(\"focus\", this.onFocus = function() { clearTimeout(closingOnBlur); });\n    }\n\n    cm.on(\"scroll\", this.onScroll = function() {\n      var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();\n      var newTop = top + startScroll.top - curScroll.top;\n      var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);\n      if (!below) point += hints.offsetHeight;\n      if (point <= editor.top || point >= editor.bottom) return completion.close();\n      hints.style.top = newTop + \"px\";\n      hints.style.left = (left + startScroll.left - curScroll.left) + \"px\";\n    });\n\n    CodeMirror.on(hints, \"dblclick\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}\n    });\n\n    CodeMirror.on(hints, \"click\", function(e) {\n      var t = getHintElement(hints, e.target || e.srcElement);\n      if (t && t.hintId != null) {\n        widget.changeActive(t.hintId);\n        if (completion.options.completeOnSingleClick) widget.pick();\n      }\n    });\n\n    CodeMirror.on(hints, \"mousedown\", function() {\n      setTimeout(function(){cm.focus();}, 20);\n    });\n    this.scrollToActive()\n\n    CodeMirror.signal(data, \"select\", completions[this.selectedHint], hints.childNodes[this.selectedHint]);\n    return true;\n  }\n\n  Widget.prototype = {\n    close: function() {\n      if (this.completion.widget != this) return;\n      this.completion.widget = null;\n      this.hints.parentNode.removeChild(this.hints);\n      this.completion.cm.removeKeyMap(this.keyMap);\n\n      var cm = this.completion.cm;\n      if (this.completion.options.closeOnUnfocus) {\n        cm.off(\"blur\", this.onBlur);\n        cm.off(\"focus\", this.onFocus);\n      }\n      cm.off(\"scroll\", this.onScroll);\n    },\n\n    disable: function() {\n      this.completion.cm.removeKeyMap(this.keyMap);\n      var widget = this;\n      this.keyMap = {Enter: function() { widget.picked = true; }};\n      this.completion.cm.addKeyMap(this.keyMap);\n    },\n\n    pick: function() {\n      this.completion.pick(this.data, this.selectedHint);\n    },\n\n    changeActive: function(i, avoidWrap) {\n      if (i >= this.data.list.length)\n        i = avoidWrap ? this.data.list.length - 1 : 0;\n      else if (i < 0)\n        i = avoidWrap ? 0  : this.data.list.length - 1;\n      if (this.selectedHint == i) return;\n      var node = this.hints.childNodes[this.selectedHint];\n      if (node) node.className = node.className.replace(\" \" + ACTIVE_HINT_ELEMENT_CLASS, \"\");\n      node = this.hints.childNodes[this.selectedHint = i];\n      node.className += \" \" + ACTIVE_HINT_ELEMENT_CLASS;\n      this.scrollToActive()\n      CodeMirror.signal(this.data, \"select\", this.data.list[this.selectedHint], node);\n    },\n\n    scrollToActive: function() {\n      var margin = this.completion.options.scrollMargin || 0;\n      var node1 = this.hints.childNodes[Math.max(0, this.selectedHint - margin)];\n      var node2 = this.hints.childNodes[Math.min(this.data.list.length - 1, this.selectedHint + margin)];\n      var firstNode = this.hints.firstChild;\n      if (node1.offsetTop < this.hints.scrollTop)\n        this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;\n      else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)\n        this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;\n    },\n\n    screenAmount: function() {\n      return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;\n    }\n  };\n\n  function applicableHelpers(cm, helpers) {\n    if (!cm.somethingSelected()) return helpers\n    var result = []\n    for (var i = 0; i < helpers.length; i++)\n      if (helpers[i].supportsSelection) result.push(helpers[i])\n    return result\n  }\n\n  function fetchHints(hint, cm, options, callback) {\n    if (hint.async) {\n      hint(cm, callback, options)\n    } else {\n      var result = hint(cm, options)\n      if (result && result.then) result.then(callback)\n      else callback(result)\n    }\n  }\n\n  function resolveAutoHints(cm, pos) {\n    var helpers = cm.getHelpers(pos, \"hint\"), words\n    if (helpers.length) {\n      var resolved = function(cm, callback, options) {\n        var app = applicableHelpers(cm, helpers);\n        function run(i) {\n          if (i == app.length) return callback(null)\n          fetchHints(app[i], cm, options, function(result) {\n            if (result && result.list.length > 0) callback(result)\n            else run(i + 1)\n          })\n        }\n        run(0)\n      }\n      resolved.async = true\n      resolved.supportsSelection = true\n      return resolved\n    } else if (words = cm.getHelper(cm.getCursor(), \"hintWords\")) {\n      return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }\n    } else if (CodeMirror.hint.anyword) {\n      return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }\n    } else {\n      return function() {}\n    }\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"auto\", {\n    resolve: resolveAutoHints\n  });\n\n  CodeMirror.registerHelper(\"hint\", \"fromList\", function(cm, options) {\n    var cur = cm.getCursor(), token = cm.getTokenAt(cur)\n    var term, from = CodeMirror.Pos(cur.line, token.start), to = cur\n    if (token.start < cur.ch && /\\w/.test(token.string.charAt(cur.ch - token.start - 1))) {\n      term = token.string.substr(0, cur.ch - token.start)\n    } else {\n      term = \"\"\n      from = cur\n    }\n    var found = [];\n    for (var i = 0; i < options.words.length; i++) {\n      var word = options.words[i];\n      if (word.slice(0, term.length) == term)\n        found.push(word);\n    }\n\n    if (found.length) return {list: found, from: from, to: to};\n  });\n\n  CodeMirror.commands.autocomplete = CodeMirror.showHint;\n\n  var defaultOptions = {\n    hint: CodeMirror.hint.auto,\n    completeSingle: true,\n    alignWithWord: true,\n    closeCharacters: /[\\s()\\[\\]{};:>,]/,\n    closeOnUnfocus: true,\n    completeOnSingleClick: true,\n    container: null,\n    customKeys: null,\n    extraKeys: null\n  };\n\n  CodeMirror.defineOption(\"hintOptions\", null);\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/hint/sql-hint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../../mode/sql/sql\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../../mode/sql/sql\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var tables;\n  var defaultTable;\n  var keywords;\n  var identifierQuote;\n  var CONS = {\n    QUERY_DIV: \";\",\n    ALIAS_KEYWORD: \"AS\"\n  };\n  var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;\n\n  function isArray(val) { return Object.prototype.toString.call(val) == \"[object Array]\" }\n\n  function getKeywords(editor) {\n    var mode = editor.doc.modeOption;\n    if (mode === \"sql\") mode = \"text/x-sql\";\n    return CodeMirror.resolveMode(mode).keywords;\n  }\n\n  function getIdentifierQuote(editor) {\n    var mode = editor.doc.modeOption;\n    if (mode === \"sql\") mode = \"text/x-sql\";\n    return CodeMirror.resolveMode(mode).identifierQuote || \"`\";\n  }\n\n  function getText(item) {\n    return typeof item == \"string\" ? item : item.text;\n  }\n\n  function wrapTable(name, value) {\n    if (isArray(value)) value = {columns: value}\n    if (!value.text) value.text = name\n    return value\n  }\n\n  function parseTables(input) {\n    var result = {}\n    if (isArray(input)) {\n      for (var i = input.length - 1; i >= 0; i--) {\n        var item = input[i]\n        result[getText(item).toUpperCase()] = wrapTable(getText(item), item)\n      }\n    } else if (input) {\n      for (var name in input)\n        result[name.toUpperCase()] = wrapTable(name, input[name])\n    }\n    return result\n  }\n\n  function getTable(name) {\n    return tables[name.toUpperCase()]\n  }\n\n  function shallowClone(object) {\n    var result = {};\n    for (var key in object) if (object.hasOwnProperty(key))\n      result[key] = object[key];\n    return result;\n  }\n\n  function match(string, word) {\n    var len = string.length;\n    var sub = getText(word).substr(0, len);\n    return string.toUpperCase() === sub.toUpperCase();\n  }\n\n  function addMatches(result, search, wordlist, formatter) {\n    if (isArray(wordlist)) {\n      for (var i = 0; i < wordlist.length; i++)\n        if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))\n    } else {\n      for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {\n        var val = wordlist[word]\n        if (!val || val === true)\n          val = word\n        else\n          val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text\n        if (match(search, val)) result.push(formatter(val))\n      }\n    }\n  }\n\n  function cleanName(name) {\n    // Get rid name from identifierQuote and preceding dot(.)\n    if (name.charAt(0) == \".\") {\n      name = name.substr(1);\n    }\n    // replace doublicated identifierQuotes with single identifierQuotes\n    // and remove single identifierQuotes\n    var nameParts = name.split(identifierQuote+identifierQuote);\n    for (var i = 0; i < nameParts.length; i++)\n      nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,\"g\"), \"\");\n    return nameParts.join(identifierQuote);\n  }\n\n  function insertIdentifierQuotes(name) {\n    var nameParts = getText(name).split(\".\");\n    for (var i = 0; i < nameParts.length; i++)\n      nameParts[i] = identifierQuote +\n        // doublicate identifierQuotes\n        nameParts[i].replace(new RegExp(identifierQuote,\"g\"), identifierQuote+identifierQuote) +\n        identifierQuote;\n    var escaped = nameParts.join(\".\");\n    if (typeof name == \"string\") return escaped;\n    name = shallowClone(name);\n    name.text = escaped;\n    return name;\n  }\n\n  function nameCompletion(cur, token, result, editor) {\n    // Try to complete table, column names and return start position of completion\n    var useIdentifierQuotes = false;\n    var nameParts = [];\n    var start = token.start;\n    var cont = true;\n    while (cont) {\n      cont = (token.string.charAt(0) == \".\");\n      useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);\n\n      start = token.start;\n      nameParts.unshift(cleanName(token.string));\n\n      token = editor.getTokenAt(Pos(cur.line, token.start));\n      if (token.string == \".\") {\n        cont = true;\n        token = editor.getTokenAt(Pos(cur.line, token.start));\n      }\n    }\n\n    // Try to complete table names\n    var string = nameParts.join(\".\");\n    addMatches(result, string, tables, function(w) {\n      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;\n    });\n\n    // Try to complete columns from defaultTable\n    addMatches(result, string, defaultTable, function(w) {\n      return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;\n    });\n\n    // Try to complete columns\n    string = nameParts.pop();\n    var table = nameParts.join(\".\");\n\n    var alias = false;\n    var aliasTable = table;\n    // Check if table is available. If not, find table by Alias\n    if (!getTable(table)) {\n      var oldTable = table;\n      table = findTableByAlias(table, editor);\n      if (table !== oldTable) alias = true;\n    }\n\n    var columns = getTable(table);\n    if (columns && columns.columns)\n      columns = columns.columns;\n\n    if (columns) {\n      addMatches(result, string, columns, function(w) {\n        var tableInsert = table;\n        if (alias == true) tableInsert = aliasTable;\n        if (typeof w == \"string\") {\n          w = tableInsert + \".\" + w;\n        } else {\n          w = shallowClone(w);\n          w.text = tableInsert + \".\" + w.text;\n        }\n        return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;\n      });\n    }\n\n    return start;\n  }\n\n  function eachWord(lineText, f) {\n    var words = lineText.split(/\\s+/)\n    for (var i = 0; i < words.length; i++)\n      if (words[i]) f(words[i].replace(/[,;]/g, ''))\n  }\n\n  function findTableByAlias(alias, editor) {\n    var doc = editor.doc;\n    var fullQuery = doc.getValue();\n    var aliasUpperCase = alias.toUpperCase();\n    var previousWord = \"\";\n    var table = \"\";\n    var separator = [];\n    var validRange = {\n      start: Pos(0, 0),\n      end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)\n    };\n\n    //add separator\n    var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);\n    while(indexOfSeparator != -1) {\n      separator.push(doc.posFromIndex(indexOfSeparator));\n      indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);\n    }\n    separator.unshift(Pos(0, 0));\n    separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));\n\n    //find valid range\n    var prevItem = null;\n    var current = editor.getCursor()\n    for (var i = 0; i < separator.length; i++) {\n      if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {\n        validRange = {start: prevItem, end: separator[i]};\n        break;\n      }\n      prevItem = separator[i];\n    }\n\n    if (validRange.start) {\n      var query = doc.getRange(validRange.start, validRange.end, false);\n\n      for (var i = 0; i < query.length; i++) {\n        var lineText = query[i];\n        eachWord(lineText, function(word) {\n          var wordUpperCase = word.toUpperCase();\n          if (wordUpperCase === aliasUpperCase && getTable(previousWord))\n            table = previousWord;\n          if (wordUpperCase !== CONS.ALIAS_KEYWORD)\n            previousWord = word;\n        });\n        if (table) break;\n      }\n    }\n    return table;\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"sql\", function(editor, options) {\n    tables = parseTables(options && options.tables)\n    var defaultTableName = options && options.defaultTable;\n    var disableKeywords = options && options.disableKeywords;\n    defaultTable = defaultTableName && getTable(defaultTableName);\n    keywords = getKeywords(editor);\n    identifierQuote = getIdentifierQuote(editor);\n\n    if (defaultTableName && !defaultTable)\n      defaultTable = findTableByAlias(defaultTableName, editor);\n\n    defaultTable = defaultTable || [];\n\n    if (defaultTable.columns)\n      defaultTable = defaultTable.columns;\n\n    var cur = editor.getCursor();\n    var result = [];\n    var token = editor.getTokenAt(cur), start, end, search;\n    if (token.end > cur.ch) {\n      token.end = cur.ch;\n      token.string = token.string.slice(0, cur.ch - token.start);\n    }\n\n    if (token.string.match(/^[.`\"'\\w@][\\w$#]*$/g)) {\n      search = token.string;\n      start = token.start;\n      end = token.end;\n    } else {\n      start = end = cur.ch;\n      search = \"\";\n    }\n    if (search.charAt(0) == \".\" || search.charAt(0) == identifierQuote) {\n      start = nameCompletion(cur, token, result, editor);\n    } else {\n      var objectOrClass = function(w, className) {\n        if (typeof w === \"object\") {\n          w.className = className;\n        } else {\n          w = { text: w, className: className };\n        }\n        return w;\n      };\n    addMatches(result, search, defaultTable, function(w) {\n        return objectOrClass(w, \"CodeMirror-hint-table CodeMirror-hint-default-table\");\n    });\n    addMatches(\n        result,\n        search,\n        tables, function(w) {\n          return objectOrClass(w, \"CodeMirror-hint-table\");\n        }\n    );\n    if (!disableKeywords)\n      addMatches(result, search, keywords, function(w) {\n          return objectOrClass(w.toUpperCase(), \"CodeMirror-hint-keyword\");\n      });\n  }\n\n    return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/hint/xml-hint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var Pos = CodeMirror.Pos;\n\n  function matches(hint, typed, matchInMiddle) {\n    if (matchInMiddle) return hint.indexOf(typed) >= 0;\n    else return hint.lastIndexOf(typed, 0) == 0;\n  }\n\n  function getHints(cm, options) {\n    var tags = options && options.schemaInfo;\n    var quote = (options && options.quoteChar) || '\"';\n    var matchInMiddle = options && options.matchInMiddle;\n    if (!tags) return;\n    var cur = cm.getCursor(), token = cm.getTokenAt(cur);\n    if (token.end > cur.ch) {\n      token.end = cur.ch;\n      token.string = token.string.slice(0, cur.ch - token.start);\n    }\n    var inner = CodeMirror.innerMode(cm.getMode(), token.state);\n    if (!inner.mode.xmlCurrentTag) return\n    var result = [], replaceToken = false, prefix;\n    var tag = /\\btag\\b/.test(token.type) && !/>$/.test(token.string);\n    var tagName = tag && /^\\w/.test(token.string), tagStart;\n\n    if (tagName) {\n      var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);\n      var tagType = /<\\/$/.test(before) ? \"close\" : /<$/.test(before) ? \"open\" : null;\n      if (tagType) tagStart = token.start - (tagType == \"close\" ? 2 : 1);\n    } else if (tag && token.string == \"<\") {\n      tagType = \"open\";\n    } else if (tag && token.string == \"</\") {\n      tagType = \"close\";\n    }\n\n    var tagInfo = inner.mode.xmlCurrentTag(inner.state)\n    if (!tag && !tagInfo || tagType) {\n      if (tagName)\n        prefix = token.string;\n      replaceToken = tagType;\n      var context = inner.mode.xmlCurrentContext ? inner.mode.xmlCurrentContext(inner.state) : []\n      var inner = context.length && context[context.length - 1]\n      var curTag = inner && tags[inner]\n      var childList = inner ? curTag && curTag.children : tags[\"!top\"];\n      if (childList && tagType != \"close\") {\n        for (var i = 0; i < childList.length; ++i) if (!prefix || matches(childList[i], prefix, matchInMiddle))\n          result.push(\"<\" + childList[i]);\n      } else if (tagType != \"close\") {\n        for (var name in tags)\n          if (tags.hasOwnProperty(name) && name != \"!top\" && name != \"!attrs\" && (!prefix || matches(name, prefix, matchInMiddle)))\n            result.push(\"<\" + name);\n      }\n      if (inner && (!prefix || tagType == \"close\" && matches(inner, prefix, matchInMiddle)))\n        result.push(\"</\" + inner + \">\");\n    } else {\n      // Attribute completion\n      var curTag = tagInfo && tags[tagInfo.name], attrs = curTag && curTag.attrs;\n      var globalAttrs = tags[\"!attrs\"];\n      if (!attrs && !globalAttrs) return;\n      if (!attrs) {\n        attrs = globalAttrs;\n      } else if (globalAttrs) { // Combine tag-local and global attributes\n        var set = {};\n        for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm];\n        for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm];\n        attrs = set;\n      }\n      if (token.type == \"string\" || token.string == \"=\") { // A value\n        var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),\n                                 Pos(cur.line, token.type == \"string\" ? token.start : token.end));\n        var atName = before.match(/([^\\s\\u00a0=<>\\\"\\']+)=$/), atValues;\n        if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return;\n        if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget\n        if (token.type == \"string\") {\n          prefix = token.string;\n          var n = 0;\n          if (/['\"]/.test(token.string.charAt(0))) {\n            quote = token.string.charAt(0);\n            prefix = token.string.slice(1);\n            n++;\n          }\n          var len = token.string.length;\n          if (/['\"]/.test(token.string.charAt(len - 1))) {\n            quote = token.string.charAt(len - 1);\n            prefix = token.string.substr(n, len - 2);\n          }\n          if (n) { // an opening quote\n            var line = cm.getLine(cur.line);\n            if (line.length > token.end && line.charAt(token.end) == quote) token.end++; // include a closing quote\n          }\n          replaceToken = true;\n        }\n        for (var i = 0; i < atValues.length; ++i) if (!prefix || matches(atValues[i], prefix, matchInMiddle))\n          result.push(quote + atValues[i] + quote);\n      } else { // An attribute name\n        if (token.type == \"attribute\") {\n          prefix = token.string;\n          replaceToken = true;\n        }\n        for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || matches(attr, prefix, matchInMiddle)))\n          result.push(attr);\n      }\n    }\n    return {\n      list: result,\n      from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,\n      to: replaceToken ? Pos(cur.line, token.end) : cur\n    };\n  }\n\n  CodeMirror.registerHelper(\"hint\", \"xml\", getHints);\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/lint/json-lint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Depends on jsonlint.js from https://github.com/zaach/jsonlint\n\n// declare global: jsonlint\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.registerHelper(\"lint\", \"json\", function(text) {\n  var found = [];\n  if (!window.jsonlint) {\n    if (window.console) {\n      window.console.error(\"Error: window.jsonlint not defined, CodeMirror JSON linting cannot run.\");\n    }\n    return found;\n  }\n  // for jsonlint's web dist jsonlint is exported as an object with a single property parser, of which parseError\n  // is a subproperty\n  var jsonlint = window.jsonlint.parser || window.jsonlint\n  jsonlint.parseError = function(str, hash) {\n    var loc = hash.loc;\n    found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),\n                to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),\n                message: str});\n  };\n  try { jsonlint.parse(text); }\n  catch(e) {}\n  return found;\n});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/lint/jsonlint.js",
    "content": "var jsonlint=function(){var a=!0,b=!1,c={},d=function(){var a={trace:function(){},yy:{},symbols_:{error:2,JSONString:3,STRING:4,JSONNumber:5,NUMBER:6,JSONNullLiteral:7,NULL:8,JSONBooleanLiteral:9,TRUE:10,FALSE:11,JSONText:12,JSONValue:13,EOF:14,JSONObject:15,JSONArray:16,\"{\":17,\"}\":18,JSONMemberList:19,JSONMember:20,\":\":21,\",\":22,\"[\":23,\"]\":24,JSONElementList:25,$accept:0,$end:1},terminals_:{2:\"error\",4:\"STRING\",6:\"NUMBER\",8:\"NULL\",10:\"TRUE\",11:\"FALSE\",14:\"EOF\",17:\"{\",18:\"}\",21:\":\",22:\",\",23:\"[\",24:\"]\"},productions_:[0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],performAction:function(b,c,d,e,f,g,h){var i=g.length-1;switch(f){case 1:this.$=b.replace(/\\\\(\\\\|\")/g,\"$1\").replace(/\\\\n/g,\"\\n\").replace(/\\\\r/g,\"\\r\").replace(/\\\\t/g,\"\t\").replace(/\\\\v/g,\"\u000b\").replace(/\\\\f/g,\"\\f\").replace(/\\\\b/g,\"\\b\");break;case 2:this.$=Number(b);break;case 3:this.$=null;break;case 4:this.$=!0;break;case 5:this.$=!1;break;case 6:return this.$=g[i-1];case 13:this.$={};break;case 14:this.$=g[i-1];break;case 15:this.$=[g[i-2],g[i]];break;case 16:this.$={},this.$[g[i][0]]=g[i][1];break;case 17:this.$=g[i-2],g[i-2][g[i][0]]=g[i][1];break;case 18:this.$=[];break;case 19:this.$=g[i-1];break;case 20:this.$=[g[i]];break;case 21:this.$=g[i-2],g[i-2].push(g[i])}},table:[{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],defaultActions:{16:[2,6]},parseError:function(b,c){throw new Error(b)},parse:function(b){function o(a){d.length=d.length-2*a,e.length=e.length-a,f.length=f.length-a}function p(){var a;return a=c.lexer.lex()||1,typeof a!=\"number\"&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h=\"\",i=0,j=0,k=0,l=2,m=1;this.lexer.setInput(b),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,typeof this.lexer.yylloc==\"undefined\"&&(this.lexer.yylloc={});var n=this.lexer.yylloc;f.push(n),typeof this.yy.parseError==\"function\"&&(this.parseError=this.yy.parseError);var q,r,s,t,u,v,w={},x,y,z,A;for(;;){s=d[d.length-1],this.defaultActions[s]?t=this.defaultActions[s]:(q==null&&(q=p()),t=g[s]&&g[s][q]);if(typeof t==\"undefined\"||!t.length||!t[0]){if(!k){A=[];for(x in g[s])this.terminals_[x]&&x>2&&A.push(\"'\"+this.terminals_[x]+\"'\");var B=\"\";this.lexer.showPosition?B=\"Parse error on line \"+(i+1)+\":\\n\"+this.lexer.showPosition()+\"\\nExpecting \"+A.join(\", \")+\", got '\"+this.terminals_[q]+\"'\":B=\"Parse error on line \"+(i+1)+\": Unexpected \"+(q==1?\"end of input\":\"'\"+(this.terminals_[q]||q)+\"'\"),this.parseError(B,{text:this.lexer.match,token:this.terminals_[q]||q,line:this.lexer.yylineno,loc:n,expected:A})}if(k==3){if(q==m)throw new Error(B||\"Parsing halted.\");j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,q=p()}for(;;){if(l.toString()in g[s])break;if(s==0)throw new Error(B||\"Parsing halted.\");o(1),s=d[d.length-1]}r=q,q=l,s=d[d.length-1],t=g[s]&&g[s][l],k=3}if(t[0]instanceof Array&&t.length>1)throw new Error(\"Parse Error: multiple actions possible at state: \"+s+\", token: \"+q);switch(t[0]){case 1:d.push(q),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(t[1]),q=null,r?(q=r,r=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,k>0&&k--);break;case 2:y=this.productions_[t[1]][1],w.$=e[e.length-y],w._$={first_line:f[f.length-(y||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(y||1)].first_column,last_column:f[f.length-1].last_column},v=this.performAction.call(w,h,j,i,this.yy,t[1],e,f);if(typeof v!=\"undefined\")return v;y&&(d=d.slice(0,-1*y*2),e=e.slice(0,-1*y),f=f.slice(0,-1*y)),d.push(this.productions_[t[1]][0]),e.push(w.$),f.push(w._$),z=g[d[d.length-2]][d[d.length-1]],d.push(z);break;case 3:return!0}}return!0}},b=function(){var a={EOF:1,parseError:function(b,c){if(!this.yy.parseError)throw new Error(b);this.yy.parseError(b,c)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match=\"\",this.conditionStack=[\"INITIAL\"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.match+=a,this.matched+=a;var b=a.match(/\\n/);return b&&this.yylineno++,this._input=this._input.slice(1),a},unput:function(a){return this._input=a+this._input,this},more:function(){return this._more=!0,this},less:function(a){this._input=this.match.slice(a)+this._input},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?\"...\":\"\")+a.substr(-20).replace(/\\n/g,\"\")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?\"...\":\"\")).replace(/\\n/g,\"\")},showPosition:function(){var a=this.pastInput(),b=(new Array(a.length+1)).join(\"-\");return a+this.upcomingInput()+\"\\n\"+b+\"^\"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e,f;this._more||(this.yytext=\"\",this.match=\"\");var g=this._currentRules();for(var h=0;h<g.length;h++){c=this._input.match(this.rules[g[h]]);if(c&&(!b||c[0].length>b[0].length)){b=c,d=h;if(!this.options.flex)break}}if(b){f=b[0].match(/\\n.*/g),f&&(this.yylineno+=f.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:f?f[f.length-1].length-1:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.yyleng=this.yytext.length,this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,g[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1);if(a)return a;return}if(this._input===\"\")return this.EOF;this.parseError(\"Lexical error on line \"+(this.yylineno+1)+\". Unrecognized text.\\n\"+this.showPosition(),{text:\"\",token:null,line:this.yylineno})},lex:function(){var b=this.next();return typeof b!=\"undefined\"?b:this.lex()},begin:function(b){this.conditionStack.push(b)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(b){this.begin(b)}};return a.options={},a.performAction=function(b,c,d,e){var f=e;switch(d){case 0:break;case 1:return 6;case 2:return c.yytext=c.yytext.substr(1,c.yyleng-2),4;case 3:return 17;case 4:return 18;case 5:return 23;case 6:return 24;case 7:return 22;case 8:return 21;case 9:return 10;case 10:return 11;case 11:return 8;case 12:return 14;case 13:return\"INVALID\"}},a.rules=[/^(?:\\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\\.[0-9]+)?([eE][-+]?[0-9]+)?\\b)/,/^(?:\"(?:\\\\[\\\\\"bfnrt/]|\\\\u[a-fA-F0-9]{4}|[^\\\\\\0-\\x09\\x0a-\\x1f\"])*\")/,/^(?:\\{)/,/^(?:\\})/,/^(?:\\[)/,/^(?:\\])/,/^(?:,)/,/^(?::)/,/^(?:true\\b)/,/^(?:false\\b)/,/^(?:null\\b)/,/^(?:$)/,/^(?:.)/],a.conditions={INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13],inclusive:!0}},a}();return a.lexer=b,a}();return typeof a!=\"undefined\"&&typeof c!=\"undefined\"&&(c.parser=d,c.parse=function(){return d.parse.apply(d,arguments)},c.main=function(d){if(!d[1])throw new Error(\"Usage: \"+d[0]+\" FILE\");if(typeof process!=\"undefined\")var e=a(\"fs\").readFileSync(a(\"path\").join(process.cwd(),d[1]),\"utf8\");else var f=a(\"file\").path(a(\"file\").cwd()),e=f.join(d[1]).read({charset:\"utf-8\"});return c.parser.parse(e)},typeof b!=\"undefined\"&&a.main===b&&c.main(typeof process!=\"undefined\"?process.argv.slice(1):a(\"system\").args)),c}();"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/lint/lint.css",
    "content": "/* The lint marker gutter */\n.CodeMirror-lint-markers {\n  width: 16px;\n}\n\n.CodeMirror-lint-tooltip {\n  background-color: #ffd;\n  border: 1px solid black;\n  border-radius: 4px 4px 4px 4px;\n  color: black;\n  font-family: monospace;\n  font-size: 10pt;\n  overflow: hidden;\n  padding: 2px 5px;\n  position: fixed;\n  white-space: pre;\n  white-space: pre-wrap;\n  z-index: 100;\n  max-width: 600px;\n  opacity: 0;\n  transition: opacity .4s;\n  -moz-transition: opacity .4s;\n  -webkit-transition: opacity .4s;\n  -o-transition: opacity .4s;\n  -ms-transition: opacity .4s;\n}\n\n.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {\n  background-position: left bottom;\n  background-repeat: repeat-x;\n}\n\n.CodeMirror-lint-mark-error {\n  background-image:\n  url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==\")\n  ;\n}\n\n.CodeMirror-lint-mark-warning {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=\");\n}\n\n.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {\n  background-position: center center;\n  background-repeat: no-repeat;\n  cursor: pointer;\n  display: inline-block;\n  height: 16px;\n  width: 16px;\n  vertical-align: middle;\n  position: relative;\n}\n\n.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {\n  padding-left: 18px;\n  background-position: top left;\n  background-repeat: no-repeat;\n}\n\n.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=\");\n}\n\n.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=\");\n}\n\n.CodeMirror-lint-marker-multiple {\n  background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC\");\n  background-repeat: no-repeat;\n  background-position: right bottom;\n  width: 100%; height: 100%;\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/lint/lint.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n  var GUTTER_ID = \"CodeMirror-lint-markers\";\n\n  function showTooltip(cm, e, content) {\n    var tt = document.createElement(\"div\");\n    tt.className = \"CodeMirror-lint-tooltip cm-s-\" + cm.options.theme;\n    tt.appendChild(content.cloneNode(true));\n    if (cm.state.lint.options.selfContain)\n      cm.getWrapperElement().appendChild(tt);\n    else\n      document.body.appendChild(tt);\n\n    function position(e) {\n      if (!tt.parentNode) return CodeMirror.off(document, \"mousemove\", position);\n      tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + \"px\";\n      tt.style.left = (e.clientX + 5) + \"px\";\n    }\n    CodeMirror.on(document, \"mousemove\", position);\n    position(e);\n    if (tt.style.opacity != null) tt.style.opacity = 1;\n    return tt;\n  }\n  function rm(elt) {\n    if (elt.parentNode) elt.parentNode.removeChild(elt);\n  }\n  function hideTooltip(tt) {\n    if (!tt.parentNode) return;\n    if (tt.style.opacity == null) rm(tt);\n    tt.style.opacity = 0;\n    setTimeout(function() { rm(tt); }, 600);\n  }\n\n  function showTooltipFor(cm, e, content, node) {\n    var tooltip = showTooltip(cm, e, content);\n    function hide() {\n      CodeMirror.off(node, \"mouseout\", hide);\n      if (tooltip) { hideTooltip(tooltip); tooltip = null; }\n    }\n    var poll = setInterval(function() {\n      if (tooltip) for (var n = node;; n = n.parentNode) {\n        if (n && n.nodeType == 11) n = n.host;\n        if (n == document.body) return;\n        if (!n) { hide(); break; }\n      }\n      if (!tooltip) return clearInterval(poll);\n    }, 400);\n    CodeMirror.on(node, \"mouseout\", hide);\n  }\n\n  function LintState(cm, options, hasGutter) {\n    this.marked = [];\n    this.options = options;\n    this.timeout = null;\n    this.hasGutter = hasGutter;\n    this.onMouseOver = function(e) { onMouseOver(cm, e); };\n    this.waitingFor = 0\n  }\n\n  function parseOptions(_cm, options) {\n    if (options instanceof Function) return {getAnnotations: options};\n    if (!options || options === true) options = {};\n    return options;\n  }\n\n  function clearMarks(cm) {\n    var state = cm.state.lint;\n    if (state.hasGutter) cm.clearGutter(GUTTER_ID);\n    for (var i = 0; i < state.marked.length; ++i)\n      state.marked[i].clear();\n    state.marked.length = 0;\n  }\n\n  function makeMarker(cm, labels, severity, multiple, tooltips) {\n    var marker = document.createElement(\"div\"), inner = marker;\n    marker.className = \"CodeMirror-lint-marker-\" + severity;\n    if (multiple) {\n      inner = marker.appendChild(document.createElement(\"div\"));\n      inner.className = \"CodeMirror-lint-marker-multiple\";\n    }\n\n    if (tooltips != false) CodeMirror.on(inner, \"mouseover\", function(e) {\n      showTooltipFor(cm, e, labels, inner);\n    });\n\n    return marker;\n  }\n\n  function getMaxSeverity(a, b) {\n    if (a == \"error\") return a;\n    else return b;\n  }\n\n  function groupByLine(annotations) {\n    var lines = [];\n    for (var i = 0; i < annotations.length; ++i) {\n      var ann = annotations[i], line = ann.from.line;\n      (lines[line] || (lines[line] = [])).push(ann);\n    }\n    return lines;\n  }\n\n  function annotationTooltip(ann) {\n    var severity = ann.severity;\n    if (!severity) severity = \"error\";\n    var tip = document.createElement(\"div\");\n    tip.className = \"CodeMirror-lint-message-\" + severity;\n    if (typeof ann.messageHTML != 'undefined') {\n      tip.innerHTML = ann.messageHTML;\n    } else {\n      tip.appendChild(document.createTextNode(ann.message));\n    }\n    return tip;\n  }\n\n  function lintAsync(cm, getAnnotations, passOptions) {\n    var state = cm.state.lint\n    var id = ++state.waitingFor\n    function abort() {\n      id = -1\n      cm.off(\"change\", abort)\n    }\n    cm.on(\"change\", abort)\n    getAnnotations(cm.getValue(), function(annotations, arg2) {\n      cm.off(\"change\", abort)\n      if (state.waitingFor != id) return\n      if (arg2 && annotations instanceof CodeMirror) annotations = arg2\n      cm.operation(function() {updateLinting(cm, annotations)})\n    }, passOptions, cm);\n  }\n\n  function startLinting(cm) {\n    var state = cm.state.lint, options = state.options;\n    /*\n     * Passing rules in `options` property prevents JSHint (and other linters) from complaining\n     * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.\n     */\n    var passOptions = options.options || options;\n    var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), \"lint\");\n    if (!getAnnotations) return;\n    if (options.async || getAnnotations.async) {\n      lintAsync(cm, getAnnotations, passOptions)\n    } else {\n      var annotations = getAnnotations(cm.getValue(), passOptions, cm);\n      if (!annotations) return;\n      if (annotations.then) annotations.then(function(issues) {\n        cm.operation(function() {updateLinting(cm, issues)})\n      });\n      else cm.operation(function() {updateLinting(cm, annotations)})\n    }\n  }\n\n  function updateLinting(cm, annotationsNotSorted) {\n    clearMarks(cm);\n    var state = cm.state.lint, options = state.options;\n\n    var annotations = groupByLine(annotationsNotSorted);\n\n    for (var line = 0; line < annotations.length; ++line) {\n      var anns = annotations[line];\n      if (!anns) continue;\n\n      var maxSeverity = null;\n      var tipLabel = state.hasGutter && document.createDocumentFragment();\n\n      for (var i = 0; i < anns.length; ++i) {\n        var ann = anns[i];\n        var severity = ann.severity;\n        if (!severity) severity = \"error\";\n        maxSeverity = getMaxSeverity(maxSeverity, severity);\n\n        if (options.formatAnnotation) ann = options.formatAnnotation(ann);\n        if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));\n\n        if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {\n          className: \"CodeMirror-lint-mark-\" + severity,\n          __annotation: ann\n        }));\n      }\n\n      if (state.hasGutter)\n        cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,\n                                                       state.options.tooltips));\n    }\n    if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);\n  }\n\n  function onChange(cm) {\n    var state = cm.state.lint;\n    if (!state) return;\n    clearTimeout(state.timeout);\n    state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);\n  }\n\n  function popupTooltips(cm, annotations, e) {\n    var target = e.target || e.srcElement;\n    var tooltip = document.createDocumentFragment();\n    for (var i = 0; i < annotations.length; i++) {\n      var ann = annotations[i];\n      tooltip.appendChild(annotationTooltip(ann));\n    }\n    showTooltipFor(cm, e, tooltip, target);\n  }\n\n  function onMouseOver(cm, e) {\n    var target = e.target || e.srcElement;\n    if (!/\\bCodeMirror-lint-mark-/.test(target.className)) return;\n    var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;\n    var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, \"client\"));\n\n    var annotations = [];\n    for (var i = 0; i < spans.length; ++i) {\n      var ann = spans[i].__annotation;\n      if (ann) annotations.push(ann);\n    }\n    if (annotations.length) popupTooltips(cm, annotations, e);\n  }\n\n  CodeMirror.defineOption(\"lint\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      clearMarks(cm);\n      if (cm.state.lint.options.lintOnChange !== false)\n        cm.off(\"change\", onChange);\n      CodeMirror.off(cm.getWrapperElement(), \"mouseover\", cm.state.lint.onMouseOver);\n      clearTimeout(cm.state.lint.timeout);\n      delete cm.state.lint;\n    }\n\n    if (val) {\n      var gutters = cm.getOption(\"gutters\"), hasLintGutter = false;\n      for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;\n      var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);\n      if (state.options.lintOnChange !== false)\n        cm.on(\"change\", onChange);\n      if (state.options.tooltips != false && state.options.tooltips != \"gutter\")\n        CodeMirror.on(cm.getWrapperElement(), \"mouseover\", state.onMouseOver);\n\n      startLinting(cm);\n    }\n  });\n\n  CodeMirror.defineExtension(\"performLint\", function() {\n    if (this.state.lint) startLinting(this);\n  });\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/mdn-like-custom.css",
    "content": "/*\n  MDN-LIKE Theme - Mozilla\n  Ported to CodeMirror by Peter Kroon <plakroon@gmail.com>\n  Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues\n  GitHub: @peterkroon\n\n  The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation\n\n*/\n.cm-s-mdn-like.CodeMirror { color: #666; background-color: #fff; }\n.cm-s-mdn-like div.CodeMirror-selected { background: #fefdb5; }\n.cm-s-mdn-like .CodeMirror-line::selection, .cm-s-mdn-like .CodeMirror-line > span::selection, .cm-s-mdn-like .CodeMirror-line > span > span::selection { background: #fefdb5; }\n.cm-s-mdn-like .CodeMirror-line::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span::-moz-selection, .cm-s-mdn-like .CodeMirror-line > span > span::-moz-selection { background: #fefdb5; }\n\n.cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; color: #333; }\n.cm-s-mdn-like .CodeMirror-linenumber { color: #aaa; padding-left: 8px; }\n.cm-s-mdn-like .CodeMirror-cursor { border-left: 2px solid #222; }\n\n.cm-s-mdn-like .cm-keyword { color: #6262FF; }\n.cm-s-mdn-like .cm-atom { color: #F90; }\n.cm-s-mdn-like .cm-number { color:  #ca7841; }\n.cm-s-mdn-like .cm-def { color: #8DA6CE; }\n.cm-s-mdn-like span.cm-variable-2, .cm-s-mdn-like span.cm-tag { color: #690; }\n.cm-s-mdn-like span.cm-variable-3, .cm-s-mdn-like span.cm-def, .cm-s-mdn-like span.cm-type { color: #07a; }\n\n.cm-s-mdn-like .cm-variable { color: #07a; }\n.cm-s-mdn-like .cm-property { color: #905; }\n.cm-s-mdn-like .cm-qualifier { color: #690; }\n\n.cm-s-mdn-like .cm-operator { color: #cda869; }\n.cm-s-mdn-like .cm-comment { color:#777; font-weight:normal; }\n.cm-s-mdn-like .cm-string { color:#07a; }\n.cm-s-mdn-like .cm-string-2 { color:#bd6b18; } /*?*/\n.cm-s-mdn-like .cm-meta { color: #000; } /*?*/\n.cm-s-mdn-like .cm-builtin { color: #9B7536; } /*?*/\n.cm-s-mdn-like .cm-tag { color: #997643; }\n.cm-s-mdn-like .cm-attribute { color: #d6bb6d; } /*?*/\n.cm-s-mdn-like .cm-header { color: #FF6400; }\n.cm-s-mdn-like .cm-hr { color: #AEAEAE; }\n.cm-s-mdn-like .cm-link { color:#ad9361; font-style:italic; text-decoration:none; }\n.cm-s-mdn-like .cm-error { border-bottom: 1px solid red; }\n\ndiv.cm-s-mdn-like .CodeMirror-activeline-background { background: #efefff; }\ndiv.cm-s-mdn-like span.CodeMirror-matchingbracket { outline:1px solid grey; color: inherit; }\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/scroll/annotatescrollbar.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineExtension(\"annotateScrollbar\", function(options) {\n    if (typeof options == \"string\") options = {className: options};\n    return new Annotation(this, options);\n  });\n\n  CodeMirror.defineOption(\"scrollButtonHeight\", 0);\n\n  function Annotation(cm, options) {\n    this.cm = cm;\n    this.options = options;\n    this.buttonHeight = options.scrollButtonHeight || cm.getOption(\"scrollButtonHeight\");\n    this.annotations = [];\n    this.doRedraw = this.doUpdate = null;\n    this.div = cm.getWrapperElement().appendChild(document.createElement(\"div\"));\n    this.div.style.cssText = \"position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none\";\n    this.computeScale();\n\n    function scheduleRedraw(delay) {\n      clearTimeout(self.doRedraw);\n      self.doRedraw = setTimeout(function() { self.redraw(); }, delay);\n    }\n\n    var self = this;\n    cm.on(\"refresh\", this.resizeHandler = function() {\n      clearTimeout(self.doUpdate);\n      self.doUpdate = setTimeout(function() {\n        if (self.computeScale()) scheduleRedraw(20);\n      }, 100);\n    });\n    cm.on(\"markerAdded\", this.resizeHandler);\n    cm.on(\"markerCleared\", this.resizeHandler);\n    if (options.listenForChanges !== false)\n      cm.on(\"changes\", this.changeHandler = function() {\n        scheduleRedraw(250);\n      });\n  }\n\n  Annotation.prototype.computeScale = function() {\n    var cm = this.cm;\n    var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /\n      cm.getScrollerElement().scrollHeight\n    if (hScale != this.hScale) {\n      this.hScale = hScale;\n      return true;\n    }\n  };\n\n  Annotation.prototype.update = function(annotations) {\n    this.annotations = annotations;\n    this.redraw();\n  };\n\n  Annotation.prototype.redraw = function(compute) {\n    if (compute !== false) this.computeScale();\n    var cm = this.cm, hScale = this.hScale;\n\n    var frag = document.createDocumentFragment(), anns = this.annotations;\n\n    var wrapping = cm.getOption(\"lineWrapping\");\n    var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;\n    var curLine = null, curLineObj = null;\n    function getY(pos, top) {\n      if (curLine != pos.line) {\n        curLine = pos.line;\n        curLineObj = cm.getLineHandle(curLine);\n      }\n      if ((curLineObj.widgets && curLineObj.widgets.length) ||\n          (wrapping && curLineObj.height > singleLineH))\n        return cm.charCoords(pos, \"local\")[top ? \"top\" : \"bottom\"];\n      var topY = cm.heightAtLine(curLineObj, \"local\");\n      return topY + (top ? 0 : curLineObj.height);\n    }\n\n    var lastLine = cm.lastLine()\n    if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {\n      var ann = anns[i];\n      if (ann.to.line > lastLine) continue;\n      var top = nextTop || getY(ann.from, true) * hScale;\n      var bottom = getY(ann.to, false) * hScale;\n      while (i < anns.length - 1) {\n        if (anns[i + 1].to.line > lastLine) break;\n        nextTop = getY(anns[i + 1].from, true) * hScale;\n        if (nextTop > bottom + .9) break;\n        ann = anns[++i];\n        bottom = getY(ann.to, false) * hScale;\n      }\n      if (bottom == top) continue;\n      var height = Math.max(bottom - top, 3);\n\n      var elt = frag.appendChild(document.createElement(\"div\"));\n      elt.style.cssText = \"position: absolute; right: 0px; width: \" + Math.max(cm.display.barWidth - 1, 2) + \"px; top: \"\n        + (top + this.buttonHeight) + \"px; height: \" + height + \"px\";\n      elt.className = this.options.className;\n      if (ann.id) {\n        elt.setAttribute(\"annotation-id\", ann.id);\n      }\n    }\n    this.div.textContent = \"\";\n    this.div.appendChild(frag);\n  };\n\n  Annotation.prototype.clear = function() {\n    this.cm.off(\"refresh\", this.resizeHandler);\n    this.cm.off(\"markerAdded\", this.resizeHandler);\n    this.cm.off(\"markerCleared\", this.resizeHandler);\n    if (this.changeHandler) this.cm.off(\"changes\", this.changeHandler);\n    this.div.parentNode.removeChild(this.div);\n  };\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/scroll/scrollpastend.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"scrollPastEnd\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      cm.off(\"change\", onChange);\n      cm.off(\"refresh\", updateBottomMargin);\n      cm.display.lineSpace.parentNode.style.paddingBottom = \"\";\n      cm.state.scrollPastEndPadding = null;\n    }\n    if (val) {\n      cm.on(\"change\", onChange);\n      cm.on(\"refresh\", updateBottomMargin);\n      updateBottomMargin(cm);\n    }\n  });\n\n  function onChange(cm, change) {\n    if (CodeMirror.changeEnd(change).line == cm.lastLine())\n      updateBottomMargin(cm);\n  }\n\n  function updateBottomMargin(cm) {\n    var padding = \"\";\n    if (cm.lineCount() > 1) {\n      var totalH = cm.display.scroller.clientHeight - 30,\n          lastLineH = cm.getLineHandle(cm.lastLine()).height;\n      padding = (totalH - lastLineH) + \"px\";\n    }\n    if (cm.state.scrollPastEndPadding != padding) {\n      cm.state.scrollPastEndPadding = padding;\n      cm.display.lineSpace.parentNode.style.paddingBottom = padding;\n      cm.off(\"refresh\", updateBottomMargin);\n      cm.setSize();\n      cm.on(\"refresh\", updateBottomMargin);\n    }\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/scroll/simplescrollbars.css",
    "content": ".CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div {\n  position: absolute;\n  background: #ccc;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  border: 1px solid #bbb;\n  border-radius: 2px;\n}\n\n.CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical {\n  position: absolute;\n  z-index: 6;\n  background: #eee;\n}\n\n.CodeMirror-simplescroll-horizontal {\n  bottom: 0; left: 0;\n  height: 8px;\n}\n.CodeMirror-simplescroll-horizontal div {\n  bottom: 0;\n  height: 100%;\n}\n\n.CodeMirror-simplescroll-vertical {\n  right: 0; top: 0;\n  width: 8px;\n}\n.CodeMirror-simplescroll-vertical div {\n  right: 0;\n  width: 100%;\n}\n\n\n.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler {\n  display: none;\n}\n\n.CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div {\n  position: absolute;\n  background: #bcd;\n  border-radius: 3px;\n}\n\n.CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical {\n  position: absolute;\n  z-index: 6;\n}\n\n.CodeMirror-overlayscroll-horizontal {\n  bottom: 0; left: 0;\n  height: 6px;\n}\n.CodeMirror-overlayscroll-horizontal div {\n  bottom: 0;\n  height: 100%;\n}\n\n.CodeMirror-overlayscroll-vertical {\n  right: 0; top: 0;\n  width: 6px;\n}\n.CodeMirror-overlayscroll-vertical div {\n  right: 0;\n  width: 100%;\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/scroll/simplescrollbars.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function Bar(cls, orientation, scroll) {\n    this.orientation = orientation;\n    this.scroll = scroll;\n    this.screen = this.total = this.size = 1;\n    this.pos = 0;\n\n    this.node = document.createElement(\"div\");\n    this.node.className = cls + \"-\" + orientation;\n    this.inner = this.node.appendChild(document.createElement(\"div\"));\n\n    var self = this;\n    CodeMirror.on(this.inner, \"mousedown\", function(e) {\n      if (e.which != 1) return;\n      CodeMirror.e_preventDefault(e);\n      var axis = self.orientation == \"horizontal\" ? \"pageX\" : \"pageY\";\n      var start = e[axis], startpos = self.pos;\n      function done() {\n        CodeMirror.off(document, \"mousemove\", move);\n        CodeMirror.off(document, \"mouseup\", done);\n      }\n      function move(e) {\n        if (e.which != 1) return done();\n        self.moveTo(startpos + (e[axis] - start) * (self.total / self.size));\n      }\n      CodeMirror.on(document, \"mousemove\", move);\n      CodeMirror.on(document, \"mouseup\", done);\n    });\n\n    CodeMirror.on(this.node, \"click\", function(e) {\n      CodeMirror.e_preventDefault(e);\n      var innerBox = self.inner.getBoundingClientRect(), where;\n      if (self.orientation == \"horizontal\")\n        where = e.clientX < innerBox.left ? -1 : e.clientX > innerBox.right ? 1 : 0;\n      else\n        where = e.clientY < innerBox.top ? -1 : e.clientY > innerBox.bottom ? 1 : 0;\n      self.moveTo(self.pos + where * self.screen);\n    });\n\n    function onWheel(e) {\n      var moved = CodeMirror.wheelEventPixels(e)[self.orientation == \"horizontal\" ? \"x\" : \"y\"];\n      var oldPos = self.pos;\n      self.moveTo(self.pos + moved);\n      if (self.pos != oldPos) CodeMirror.e_preventDefault(e);\n    }\n    CodeMirror.on(this.node, \"mousewheel\", onWheel);\n    CodeMirror.on(this.node, \"DOMMouseScroll\", onWheel);\n  }\n\n  Bar.prototype.setPos = function(pos, force) {\n    if (pos < 0) pos = 0;\n    if (pos > this.total - this.screen) pos = this.total - this.screen;\n    if (!force && pos == this.pos) return false;\n    this.pos = pos;\n    this.inner.style[this.orientation == \"horizontal\" ? \"left\" : \"top\"] =\n      (pos * (this.size / this.total)) + \"px\";\n    return true\n  };\n\n  Bar.prototype.moveTo = function(pos) {\n    if (this.setPos(pos)) this.scroll(pos, this.orientation);\n  }\n\n  var minButtonSize = 10;\n\n  Bar.prototype.update = function(scrollSize, clientSize, barSize) {\n    var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize\n    if (sizeChanged) {\n      this.screen = clientSize;\n      this.total = scrollSize;\n      this.size = barSize;\n    }\n\n    var buttonSize = this.screen * (this.size / this.total);\n    if (buttonSize < minButtonSize) {\n      this.size -= minButtonSize - buttonSize;\n      buttonSize = minButtonSize;\n    }\n    this.inner.style[this.orientation == \"horizontal\" ? \"width\" : \"height\"] =\n      buttonSize + \"px\";\n    this.setPos(this.pos, sizeChanged);\n  };\n\n  function SimpleScrollbars(cls, place, scroll) {\n    this.addClass = cls;\n    this.horiz = new Bar(cls, \"horizontal\", scroll);\n    place(this.horiz.node);\n    this.vert = new Bar(cls, \"vertical\", scroll);\n    place(this.vert.node);\n    this.width = null;\n  }\n\n  SimpleScrollbars.prototype.update = function(measure) {\n    if (this.width == null) {\n      var style = window.getComputedStyle ? window.getComputedStyle(this.horiz.node) : this.horiz.node.currentStyle;\n      if (style) this.width = parseInt(style.height);\n    }\n    var width = this.width || 0;\n\n    var needsH = measure.scrollWidth > measure.clientWidth + 1;\n    var needsV = measure.scrollHeight > measure.clientHeight + 1;\n    this.vert.node.style.display = needsV ? \"block\" : \"none\";\n    this.horiz.node.style.display = needsH ? \"block\" : \"none\";\n\n    if (needsV) {\n      this.vert.update(measure.scrollHeight, measure.clientHeight,\n                       measure.viewHeight - (needsH ? width : 0));\n      this.vert.node.style.bottom = needsH ? width + \"px\" : \"0\";\n    }\n    if (needsH) {\n      this.horiz.update(measure.scrollWidth, measure.clientWidth,\n                        measure.viewWidth - (needsV ? width : 0) - measure.barLeft);\n      this.horiz.node.style.right = needsV ? width + \"px\" : \"0\";\n      this.horiz.node.style.left = measure.barLeft + \"px\";\n    }\n\n    return {right: needsV ? width : 0, bottom: needsH ? width : 0};\n  };\n\n  SimpleScrollbars.prototype.setScrollTop = function(pos) {\n    this.vert.setPos(pos);\n  };\n\n  SimpleScrollbars.prototype.setScrollLeft = function(pos) {\n    this.horiz.setPos(pos);\n  };\n\n  SimpleScrollbars.prototype.clear = function() {\n    var parent = this.horiz.node.parentNode;\n    parent.removeChild(this.horiz.node);\n    parent.removeChild(this.vert.node);\n  };\n\n  CodeMirror.scrollbarModel.simple = function(place, scroll) {\n    return new SimpleScrollbars(\"CodeMirror-simplescroll\", place, scroll);\n  };\n  CodeMirror.scrollbarModel.overlay = function(place, scroll) {\n    return new SimpleScrollbars(\"CodeMirror-overlayscroll\", place, scroll);\n  };\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/search/jump-to-line.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Defines jumpToLine command. Uses dialog.js if present.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../dialog/dialog\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../dialog/dialog\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function dialog(cm, text, shortText, deflt, f) {\n    if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});\n    else f(prompt(shortText, deflt));\n  }\n\n  function getJumpDialog(cm) {\n    return cm.phrase(\"Jump to line:\") + ' <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/> <span style=\"color: #888\" class=\"CodeMirror-search-hint\">' + cm.phrase(\"(Use line:column or scroll% syntax)\") + '</span>';\n  }\n\n  function interpretLine(cm, string) {\n    var num = Number(string)\n    if (/^[-+]/.test(string)) return cm.getCursor().line + num\n    else return num - 1\n  }\n\n  CodeMirror.commands.jumpToLine = function(cm) {\n    var cur = cm.getCursor();\n    dialog(cm, getJumpDialog(cm), cm.phrase(\"Jump to line:\"), (cur.line + 1) + \":\" + cur.ch, function(posStr) {\n      if (!posStr) return;\n\n      var match;\n      if (match = /^\\s*([\\+\\-]?\\d+)\\s*\\:\\s*(\\d+)\\s*$/.exec(posStr)) {\n        cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))\n      } else if (match = /^\\s*([\\+\\-]?\\d+(\\.\\d+)?)\\%\\s*/.exec(posStr)) {\n        var line = Math.round(cm.lineCount() * Number(match[1]) / 100);\n        if (/^[-+]/.test(match[1])) line = cur.line + line + 1;\n        cm.setCursor(line - 1, cur.ch);\n      } else if (match = /^\\s*\\:?\\s*([\\+\\-]?\\d+)\\s*/.exec(posStr)) {\n        cm.setCursor(interpretLine(cm, match[1]), cur.ch);\n      }\n    });\n  };\n\n  CodeMirror.keyMap[\"default\"][\"Alt-G\"] = \"jumpToLine\";\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/search/match-highlighter.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Highlighting text that matches the selection\n//\n// Defines an option highlightSelectionMatches, which, when enabled,\n// will style strings that match the selection throughout the\n// document.\n//\n// The option can be set to true to simply enable it, or to a\n// {minChars, style, wordsOnly, showToken, delay} object to explicitly\n// configure it. minChars is the minimum amount of characters that should be\n// selected for the behavior to occur, and style is the token style to\n// apply to the matches. This will be prefixed by \"cm-\" to create an\n// actual CSS class name. If wordsOnly is enabled, the matches will be\n// highlighted only if the selected text is a word. showToken, when enabled,\n// will cause the current token to be highlighted when nothing is selected.\n// delay is used to specify how much time to wait, in milliseconds, before\n// highlighting the matches. If annotateScrollbar is enabled, the occurences\n// will be highlighted on the scrollbar via the matchesonscrollbar addon.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./matchesonscrollbar\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./matchesonscrollbar\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var defaults = {\n    style: \"matchhighlight\",\n    minChars: 2,\n    delay: 100,\n    wordsOnly: false,\n    annotateScrollbar: false,\n    showToken: false,\n    trim: true\n  }\n\n  function State(options) {\n    this.options = {}\n    for (var name in defaults)\n      this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]\n    this.overlay = this.timeout = null;\n    this.matchesonscroll = null;\n    this.active = false;\n  }\n\n  CodeMirror.defineOption(\"highlightSelectionMatches\", false, function(cm, val, old) {\n    if (old && old != CodeMirror.Init) {\n      removeOverlay(cm);\n      clearTimeout(cm.state.matchHighlighter.timeout);\n      cm.state.matchHighlighter = null;\n      cm.off(\"cursorActivity\", cursorActivity);\n      cm.off(\"focus\", onFocus)\n    }\n    if (val) {\n      var state = cm.state.matchHighlighter = new State(val);\n      if (cm.hasFocus()) {\n        state.active = true\n        highlightMatches(cm)\n      } else {\n        cm.on(\"focus\", onFocus)\n      }\n      cm.on(\"cursorActivity\", cursorActivity);\n    }\n  });\n\n  function cursorActivity(cm) {\n    var state = cm.state.matchHighlighter;\n    if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)\n  }\n\n  function onFocus(cm) {\n    var state = cm.state.matchHighlighter\n    if (!state.active) {\n      state.active = true\n      scheduleHighlight(cm, state)\n    }\n  }\n\n  function scheduleHighlight(cm, state) {\n    clearTimeout(state.timeout);\n    state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);\n  }\n\n  function addOverlay(cm, query, hasBoundary, style) {\n    var state = cm.state.matchHighlighter;\n    cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));\n    if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {\n      var searchFor = hasBoundary ? new RegExp((/\\w/.test(query.charAt(0)) ? \"\\\\b\" : \"\") +\n                                               query.replace(/[\\\\\\[.+*?(){|^$]/g, \"\\\\$&\") +\n                                               (/\\w/.test(query.charAt(query.length - 1)) ? \"\\\\b\" : \"\")) : query;\n      state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,\n        {className: \"CodeMirror-selection-highlight-scrollbar\"});\n    }\n  }\n\n  function removeOverlay(cm) {\n    var state = cm.state.matchHighlighter;\n    if (state.overlay) {\n      cm.removeOverlay(state.overlay);\n      state.overlay = null;\n      if (state.matchesonscroll) {\n        state.matchesonscroll.clear();\n        state.matchesonscroll = null;\n      }\n    }\n  }\n\n  function highlightMatches(cm) {\n    cm.operation(function() {\n      var state = cm.state.matchHighlighter;\n      removeOverlay(cm);\n      if (!cm.somethingSelected() && state.options.showToken) {\n        var re = state.options.showToken === true ? /[\\w$]/ : state.options.showToken;\n        var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;\n        while (start && re.test(line.charAt(start - 1))) --start;\n        while (end < line.length && re.test(line.charAt(end))) ++end;\n        if (start < end)\n          addOverlay(cm, line.slice(start, end), re, state.options.style);\n        return;\n      }\n      var from = cm.getCursor(\"from\"), to = cm.getCursor(\"to\");\n      if (from.line != to.line) return;\n      if (state.options.wordsOnly && !isWord(cm, from, to)) return;\n      var selection = cm.getRange(from, to)\n      if (state.options.trim) selection = selection.replace(/^\\s+|\\s+$/g, \"\")\n      if (selection.length >= state.options.minChars)\n        addOverlay(cm, selection, false, state.options.style);\n    });\n  }\n\n  function isWord(cm, from, to) {\n    var str = cm.getRange(from, to);\n    if (str.match(/^\\w+$/) !== null) {\n        if (from.ch > 0) {\n            var pos = {line: from.line, ch: from.ch - 1};\n            var chr = cm.getRange(pos, from);\n            if (chr.match(/\\W/) === null) return false;\n        }\n        if (to.ch < cm.getLine(from.line).length) {\n            var pos = {line: to.line, ch: to.ch + 1};\n            var chr = cm.getRange(to, pos);\n            if (chr.match(/\\W/) === null) return false;\n        }\n        return true;\n    } else return false;\n  }\n\n  function boundariesAround(stream, re) {\n    return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) &&\n      (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos)));\n  }\n\n  function makeOverlay(query, hasBoundary, style) {\n    return {token: function(stream) {\n      if (stream.match(query) &&\n          (!hasBoundary || boundariesAround(stream, hasBoundary)))\n        return style;\n      stream.next();\n      stream.skipTo(query.charAt(0)) || stream.skipToEnd();\n    }};\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/search/matchesonscrollbar.css",
    "content": ".CodeMirror-search-match {\n  background: gold;\n  border-top: 1px solid orange;\n  border-bottom: 1px solid orange;\n  -moz-box-sizing: border-box;\n  box-sizing: border-box;\n  opacity: .5;\n}\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/search/matchesonscrollbar.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./searchcursor\"), require(\"../scroll/annotatescrollbar\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./searchcursor\", \"../scroll/annotatescrollbar\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineExtension(\"showMatchesOnScrollbar\", function(query, caseFold, options) {\n    if (typeof options == \"string\") options = {className: options};\n    if (!options) options = {};\n    return new SearchAnnotation(this, query, caseFold, options);\n  });\n\n  function SearchAnnotation(cm, query, caseFold, options) {\n    this.cm = cm;\n    this.options = options;\n    var annotateOptions = {listenForChanges: false};\n    for (var prop in options) annotateOptions[prop] = options[prop];\n    if (!annotateOptions.className) annotateOptions.className = \"CodeMirror-search-match\";\n    this.annotation = cm.annotateScrollbar(annotateOptions);\n    this.query = query;\n    this.caseFold = caseFold;\n    this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};\n    this.matches = [];\n    this.update = null;\n\n    this.findMatches();\n    this.annotation.update(this.matches);\n\n    var self = this;\n    cm.on(\"change\", this.changeHandler = function(_cm, change) { self.onChange(change); });\n  }\n\n  var MAX_MATCHES = 1000;\n\n  SearchAnnotation.prototype.findMatches = function() {\n    if (!this.gap) return;\n    for (var i = 0; i < this.matches.length; i++) {\n      var match = this.matches[i];\n      if (match.from.line >= this.gap.to) break;\n      if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);\n    }\n    var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline});\n    var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;\n    while (cursor.findNext()) {\n      var match = {from: cursor.from(), to: cursor.to()};\n      if (match.from.line >= this.gap.to) break;\n      this.matches.splice(i++, 0, match);\n      if (this.matches.length > maxMatches) break;\n    }\n    this.gap = null;\n  };\n\n  function offsetLine(line, changeStart, sizeChange) {\n    if (line <= changeStart) return line;\n    return Math.max(changeStart, line + sizeChange);\n  }\n\n  SearchAnnotation.prototype.onChange = function(change) {\n    var startLine = change.from.line;\n    var endLine = CodeMirror.changeEnd(change).line;\n    var sizeChange = endLine - change.to.line;\n    if (this.gap) {\n      this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);\n      this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);\n    } else {\n      this.gap = {from: change.from.line, to: endLine + 1};\n    }\n\n    if (sizeChange) for (var i = 0; i < this.matches.length; i++) {\n      var match = this.matches[i];\n      var newFrom = offsetLine(match.from.line, startLine, sizeChange);\n      if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);\n      var newTo = offsetLine(match.to.line, startLine, sizeChange);\n      if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);\n    }\n    clearTimeout(this.update);\n    var self = this;\n    this.update = setTimeout(function() { self.updateAfterChange(); }, 250);\n  };\n\n  SearchAnnotation.prototype.updateAfterChange = function() {\n    this.findMatches();\n    this.annotation.update(this.matches);\n  };\n\n  SearchAnnotation.prototype.clear = function() {\n    this.cm.off(\"change\", this.changeHandler);\n    this.annotation.clear();\n  };\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/search/search.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Define search commands. Depends on dialog.js or another\n// implementation of the openDialog method.\n\n// Replace works a little oddly -- it will do the replace on the next\n// Ctrl-G (or whatever is bound to findNext) press. You prevent a\n// replace by making sure the match is no longer selected when hitting\n// Ctrl-G.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"./searchcursor\"), require(\"../dialog/dialog\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"./searchcursor\", \"../dialog/dialog\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function searchOverlay(query, caseInsensitive) {\n    if (typeof query == \"string\")\n      query = new RegExp(query.replace(/[\\-\\[\\]\\/\\{\\}\\(\\)\\*\\+\\?\\.\\\\\\^\\$\\|]/g, \"\\\\$&\"), caseInsensitive ? \"gi\" : \"g\");\n    else if (!query.global)\n      query = new RegExp(query.source, query.ignoreCase ? \"gi\" : \"g\");\n\n    return {token: function(stream) {\n      query.lastIndex = stream.pos;\n      var match = query.exec(stream.string);\n      if (match && match.index == stream.pos) {\n        stream.pos += match[0].length || 1;\n        return \"searching\";\n      } else if (match) {\n        stream.pos = match.index;\n      } else {\n        stream.skipToEnd();\n      }\n    }};\n  }\n\n  function SearchState() {\n    this.posFrom = this.posTo = this.lastQuery = this.query = null;\n    this.overlay = null;\n  }\n\n  function getSearchState(cm) {\n    return cm.state.search || (cm.state.search = new SearchState());\n  }\n\n  function queryCaseInsensitive(query) {\n    return typeof query == \"string\" && query == query.toLowerCase();\n  }\n\n  function getSearchCursor(cm, query, pos) {\n    // Heuristic: if the query string is all lowercase, do a case insensitive search.\n    return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});\n  }\n\n  function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {\n    cm.openDialog(text, onEnter, {\n      value: deflt,\n      selectValueOnOpen: true,\n      closeOnEnter: false,\n      onClose: function() { clearSearch(cm); },\n      onKeyDown: onKeyDown\n    });\n  }\n\n  function dialog(cm, text, shortText, deflt, f) {\n    if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});\n    else f(prompt(shortText, deflt));\n  }\n\n  function confirmDialog(cm, text, shortText, fs) {\n    if (cm.openConfirm) cm.openConfirm(text, fs);\n    else if (confirm(shortText)) fs[0]();\n  }\n\n  function parseString(string) {\n    return string.replace(/\\\\([nrt\\\\])/g, function(match, ch) {\n      if (ch == \"n\") return \"\\n\"\n      if (ch == \"r\") return \"\\r\"\n      if (ch == \"t\") return \"\\t\"\n      if (ch == \"\\\\\") return \"\\\\\"\n      return match\n    })\n  }\n\n  function parseQuery(query) {\n    var isRE = query.match(/^\\/(.*)\\/([a-z]*)$/);\n    if (isRE) {\n      try { query = new RegExp(isRE[1], isRE[2].indexOf(\"i\") == -1 ? \"\" : \"i\"); }\n      catch(e) {} // Not a regular expression after all, do a string search\n    } else {\n      query = parseString(query)\n    }\n    if (typeof query == \"string\" ? query == \"\" : query.test(\"\"))\n      query = /x^/;\n    return query;\n  }\n\n  function startSearch(cm, state, query) {\n    state.queryText = query;\n    state.query = parseQuery(query);\n    cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));\n    state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));\n    cm.addOverlay(state.overlay);\n    if (cm.showMatchesOnScrollbar) {\n      if (state.annotate) { state.annotate.clear(); state.annotate = null; }\n      state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));\n    }\n  }\n\n  function doSearch(cm, rev, persistent, immediate) {\n    var state = getSearchState(cm);\n    if (state.query) return findNext(cm, rev);\n    var q = cm.getSelection() || state.lastQuery;\n    if (q instanceof RegExp && q.source == \"x^\") q = null\n    if (persistent && cm.openDialog) {\n      var hiding = null\n      var searchNext = function(query, event) {\n        CodeMirror.e_stop(event);\n        if (!query) return;\n        if (query != state.queryText) {\n          startSearch(cm, state, query);\n          state.posFrom = state.posTo = cm.getCursor();\n        }\n        if (hiding) hiding.style.opacity = 1\n        findNext(cm, event.shiftKey, function(_, to) {\n          var dialog\n          if (to.line < 3 && document.querySelector &&\n              (dialog = cm.display.wrapper.querySelector(\".CodeMirror-dialog\")) &&\n              dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, \"window\").top)\n            (hiding = dialog).style.opacity = .4\n        })\n      };\n      persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) {\n        var keyName = CodeMirror.keyName(event)\n        var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption(\"keyMap\")][keyName]\n        if (cmd == \"findNext\" || cmd == \"findPrev\" ||\n          cmd == \"findPersistentNext\" || cmd == \"findPersistentPrev\") {\n          CodeMirror.e_stop(event);\n          startSearch(cm, getSearchState(cm), query);\n          cm.execCommand(cmd);\n        } else if (cmd == \"find\" || cmd == \"findPersistent\") {\n          CodeMirror.e_stop(event);\n          searchNext(query, event);\n        }\n      });\n      if (immediate && q) {\n        startSearch(cm, state, q);\n        findNext(cm, rev);\n      }\n    } else {\n      dialog(cm, getQueryDialog(cm), \"Search for:\", q, function(query) {\n        if (query && !state.query) cm.operation(function() {\n          startSearch(cm, state, query);\n          state.posFrom = state.posTo = cm.getCursor();\n          findNext(cm, rev);\n        });\n      });\n    }\n  }\n\n  function findNext(cm, rev, callback) {cm.operation(function() {\n    var state = getSearchState(cm);\n    var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);\n    if (!cursor.find(rev)) {\n      cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));\n      if (!cursor.find(rev)) return;\n    }\n    cm.setSelection(cursor.from(), cursor.to());\n    cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);\n    state.posFrom = cursor.from(); state.posTo = cursor.to();\n    if (callback) callback(cursor.from(), cursor.to())\n  });}\n\n  function clearSearch(cm) {cm.operation(function() {\n    var state = getSearchState(cm);\n    state.lastQuery = state.query;\n    if (!state.query) return;\n    state.query = state.queryText = null;\n    cm.removeOverlay(state.overlay);\n    if (state.annotate) { state.annotate.clear(); state.annotate = null; }\n  });}\n\n\n  function getQueryDialog(cm)  {\n    return '<span class=\"CodeMirror-search-label\">' + cm.phrase(\"Search:\") + '</span> <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/> <span style=\"color: #888\" class=\"CodeMirror-search-hint\">' + cm.phrase(\"(Use /re/ syntax for regexp search)\") + '</span>';\n  }\n  function getReplaceQueryDialog(cm) {\n    return ' <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/> <span style=\"color: #888\" class=\"CodeMirror-search-hint\">' + cm.phrase(\"(Use /re/ syntax for regexp search)\") + '</span>';\n  }\n  function getReplacementQueryDialog(cm) {\n    return '<span class=\"CodeMirror-search-label\">' + cm.phrase(\"With:\") + '</span> <input type=\"text\" style=\"width: 10em\" class=\"CodeMirror-search-field\"/>';\n  }\n  function getDoReplaceConfirm(cm) {\n    return '<span class=\"CodeMirror-search-label\">' + cm.phrase(\"Replace?\") + '</span> <button>' + cm.phrase(\"Yes\") + '</button> <button>' + cm.phrase(\"No\") + '</button> <button>' + cm.phrase(\"All\") + '</button> <button>' + cm.phrase(\"Stop\") + '</button> ';\n  }\n\n  function replaceAll(cm, query, text) {\n    cm.operation(function() {\n      for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {\n        if (typeof query != \"string\") {\n          var match = cm.getRange(cursor.from(), cursor.to()).match(query);\n          cursor.replace(text.replace(/\\$(\\d)/g, function(_, i) {return match[i];}));\n        } else cursor.replace(text);\n      }\n    });\n  }\n\n  function replace(cm, all) {\n    if (cm.getOption(\"readOnly\")) return;\n    var query = cm.getSelection() || getSearchState(cm).lastQuery;\n    var dialogText = '<span class=\"CodeMirror-search-label\">' + (all ? cm.phrase(\"Replace all:\") : cm.phrase(\"Replace:\")) + '</span>';\n    dialog(cm, dialogText + getReplaceQueryDialog(cm), dialogText, query, function(query) {\n      if (!query) return;\n      query = parseQuery(query);\n      dialog(cm, getReplacementQueryDialog(cm), cm.phrase(\"Replace with:\"), \"\", function(text) {\n        text = parseString(text)\n        if (all) {\n          replaceAll(cm, query, text)\n        } else {\n          clearSearch(cm);\n          var cursor = getSearchCursor(cm, query, cm.getCursor(\"from\"));\n          var advance = function() {\n            var start = cursor.from(), match;\n            if (!(match = cursor.findNext())) {\n              cursor = getSearchCursor(cm, query);\n              if (!(match = cursor.findNext()) ||\n                  (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;\n            }\n            cm.setSelection(cursor.from(), cursor.to());\n            cm.scrollIntoView({from: cursor.from(), to: cursor.to()});\n            confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase(\"Replace?\"),\n                          [function() {doReplace(match);}, advance,\n                           function() {replaceAll(cm, query, text)}]);\n          };\n          var doReplace = function(match) {\n            cursor.replace(typeof query == \"string\" ? text :\n                           text.replace(/\\$(\\d)/g, function(_, i) {return match[i];}));\n            advance();\n          };\n          advance();\n        }\n      });\n    });\n  }\n\n  CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};\n  CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};\n  CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};\n  CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};\n  CodeMirror.commands.findNext = doSearch;\n  CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};\n  CodeMirror.commands.clearSearch = clearSearch;\n  CodeMirror.commands.replace = replace;\n  CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/search/searchcursor.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"))\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod)\n  else // Plain browser env\n    mod(CodeMirror)\n})(function(CodeMirror) {\n  \"use strict\"\n  var Pos = CodeMirror.Pos\n\n  function regexpFlags(regexp) {\n    var flags = regexp.flags\n    return flags != null ? flags : (regexp.ignoreCase ? \"i\" : \"\")\n      + (regexp.global ? \"g\" : \"\")\n      + (regexp.multiline ? \"m\" : \"\")\n  }\n\n  function ensureFlags(regexp, flags) {\n    var current = regexpFlags(regexp), target = current\n    for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)\n      target += flags.charAt(i)\n    return current == target ? regexp : new RegExp(regexp.source, target)\n  }\n\n  function maybeMultiline(regexp) {\n    return /\\\\s|\\\\n|\\n|\\\\W|\\\\D|\\[\\^/.test(regexp.source)\n  }\n\n  function searchRegexpForward(doc, regexp, start) {\n    regexp = ensureFlags(regexp, \"g\")\n    for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {\n      regexp.lastIndex = ch\n      var string = doc.getLine(line), match = regexp.exec(string)\n      if (match)\n        return {from: Pos(line, match.index),\n                to: Pos(line, match.index + match[0].length),\n                match: match}\n    }\n  }\n\n  function searchRegexpForwardMultiline(doc, regexp, start) {\n    if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)\n\n    regexp = ensureFlags(regexp, \"gm\")\n    var string, chunk = 1\n    for (var line = start.line, last = doc.lastLine(); line <= last;) {\n      // This grows the search buffer in exponentially-sized chunks\n      // between matches, so that nearby matches are fast and don't\n      // require concatenating the whole document (in case we're\n      // searching for something that has tons of matches), but at the\n      // same time, the amount of retries is limited.\n      for (var i = 0; i < chunk; i++) {\n        if (line > last) break\n        var curLine = doc.getLine(line++)\n        string = string == null ? curLine : string + \"\\n\" + curLine\n      }\n      chunk = chunk * 2\n      regexp.lastIndex = start.ch\n      var match = regexp.exec(string)\n      if (match) {\n        var before = string.slice(0, match.index).split(\"\\n\"), inside = match[0].split(\"\\n\")\n        var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length\n        return {from: Pos(startLine, startCh),\n                to: Pos(startLine + inside.length - 1,\n                        inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),\n                match: match}\n      }\n    }\n  }\n\n  function lastMatchIn(string, regexp, endMargin) {\n    var match, from = 0\n    while (from <= string.length) {\n      regexp.lastIndex = from\n      var newMatch = regexp.exec(string)\n      if (!newMatch) break\n      var end = newMatch.index + newMatch[0].length\n      if (end > string.length - endMargin) break\n      if (!match || end > match.index + match[0].length)\n        match = newMatch\n      from = newMatch.index + 1\n    }\n    return match\n  }\n\n  function searchRegexpBackward(doc, regexp, start) {\n    regexp = ensureFlags(regexp, \"g\")\n    for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {\n      var string = doc.getLine(line)\n      var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)\n      if (match)\n        return {from: Pos(line, match.index),\n                to: Pos(line, match.index + match[0].length),\n                match: match}\n    }\n  }\n\n  function searchRegexpBackwardMultiline(doc, regexp, start) {\n    if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)\n    regexp = ensureFlags(regexp, \"gm\")\n    var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch\n    for (var line = start.line, first = doc.firstLine(); line >= first;) {\n      for (var i = 0; i < chunkSize && line >= first; i++) {\n        var curLine = doc.getLine(line--)\n        string = string == null ? curLine : curLine + \"\\n\" + string\n      }\n      chunkSize *= 2\n\n      var match = lastMatchIn(string, regexp, endMargin)\n      if (match) {\n        var before = string.slice(0, match.index).split(\"\\n\"), inside = match[0].split(\"\\n\")\n        var startLine = line + before.length, startCh = before[before.length - 1].length\n        return {from: Pos(startLine, startCh),\n                to: Pos(startLine + inside.length - 1,\n                        inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),\n                match: match}\n      }\n    }\n  }\n\n  var doFold, noFold\n  if (String.prototype.normalize) {\n    doFold = function(str) { return str.normalize(\"NFD\").toLowerCase() }\n    noFold = function(str) { return str.normalize(\"NFD\") }\n  } else {\n    doFold = function(str) { return str.toLowerCase() }\n    noFold = function(str) { return str }\n  }\n\n  // Maps a position in a case-folded line back to a position in the original line\n  // (compensating for codepoints increasing in number during folding)\n  function adjustPos(orig, folded, pos, foldFunc) {\n    if (orig.length == folded.length) return pos\n    for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {\n      if (min == max) return min\n      var mid = (min + max) >> 1\n      var len = foldFunc(orig.slice(0, mid)).length\n      if (len == pos) return mid\n      else if (len > pos) max = mid\n      else min = mid + 1\n    }\n  }\n\n  function searchStringForward(doc, query, start, caseFold) {\n    // Empty string would match anything and never progress, so we\n    // define it to match nothing instead.\n    if (!query.length) return null\n    var fold = caseFold ? doFold : noFold\n    var lines = fold(query).split(/\\r|\\n\\r?/)\n\n    search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {\n      var orig = doc.getLine(line).slice(ch), string = fold(orig)\n      if (lines.length == 1) {\n        var found = string.indexOf(lines[0])\n        if (found == -1) continue search\n        var start = adjustPos(orig, string, found, fold) + ch\n        return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),\n                to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}\n      } else {\n        var cutFrom = string.length - lines[0].length\n        if (string.slice(cutFrom) != lines[0]) continue search\n        for (var i = 1; i < lines.length - 1; i++)\n          if (fold(doc.getLine(line + i)) != lines[i]) continue search\n        var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]\n        if (endString.slice(0, lastLine.length) != lastLine) continue search\n        return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),\n                to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}\n      }\n    }\n  }\n\n  function searchStringBackward(doc, query, start, caseFold) {\n    if (!query.length) return null\n    var fold = caseFold ? doFold : noFold\n    var lines = fold(query).split(/\\r|\\n\\r?/)\n\n    search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {\n      var orig = doc.getLine(line)\n      if (ch > -1) orig = orig.slice(0, ch)\n      var string = fold(orig)\n      if (lines.length == 1) {\n        var found = string.lastIndexOf(lines[0])\n        if (found == -1) continue search\n        return {from: Pos(line, adjustPos(orig, string, found, fold)),\n                to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}\n      } else {\n        var lastLine = lines[lines.length - 1]\n        if (string.slice(0, lastLine.length) != lastLine) continue search\n        for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)\n          if (fold(doc.getLine(start + i)) != lines[i]) continue search\n        var top = doc.getLine(line + 1 - lines.length), topString = fold(top)\n        if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search\n        return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),\n                to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}\n      }\n    }\n  }\n\n  function SearchCursor(doc, query, pos, options) {\n    this.atOccurrence = false\n    this.doc = doc\n    pos = pos ? doc.clipPos(pos) : Pos(0, 0)\n    this.pos = {from: pos, to: pos}\n\n    var caseFold\n    if (typeof options == \"object\") {\n      caseFold = options.caseFold\n    } else { // Backwards compat for when caseFold was the 4th argument\n      caseFold = options\n      options = null\n    }\n\n    if (typeof query == \"string\") {\n      if (caseFold == null) caseFold = false\n      this.matches = function(reverse, pos) {\n        return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)\n      }\n    } else {\n      query = ensureFlags(query, \"gm\")\n      if (!options || options.multiline !== false)\n        this.matches = function(reverse, pos) {\n          return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)\n        }\n      else\n        this.matches = function(reverse, pos) {\n          return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)\n        }\n    }\n  }\n\n  SearchCursor.prototype = {\n    findNext: function() {return this.find(false)},\n    findPrevious: function() {return this.find(true)},\n\n    find: function(reverse) {\n      var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))\n\n      // Implements weird auto-growing behavior on null-matches for\n      // backwards-compatibility with the vim code (unfortunately)\n      while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {\n        if (reverse) {\n          if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)\n          else if (result.from.line == this.doc.firstLine()) result = null\n          else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))\n        } else {\n          if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)\n          else if (result.to.line == this.doc.lastLine()) result = null\n          else result = this.matches(reverse, Pos(result.to.line + 1, 0))\n        }\n      }\n\n      if (result) {\n        this.pos = result\n        this.atOccurrence = true\n        return this.pos.match || true\n      } else {\n        var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)\n        this.pos = {from: end, to: end}\n        return this.atOccurrence = false\n      }\n    },\n\n    from: function() {if (this.atOccurrence) return this.pos.from},\n    to: function() {if (this.atOccurrence) return this.pos.to},\n\n    replace: function(newText, origin) {\n      if (!this.atOccurrence) return\n      var lines = CodeMirror.splitLines(newText)\n      this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)\n      this.pos.to = Pos(this.pos.from.line + lines.length - 1,\n                        lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))\n    }\n  }\n\n  CodeMirror.defineExtension(\"getSearchCursor\", function(query, pos, caseFold) {\n    return new SearchCursor(this.doc, query, pos, caseFold)\n  })\n  CodeMirror.defineDocExtension(\"getSearchCursor\", function(query, pos, caseFold) {\n    return new SearchCursor(this, query, pos, caseFold)\n  })\n\n  CodeMirror.defineExtension(\"selectMatches\", function(query, caseFold) {\n    var ranges = []\n    var cur = this.getSearchCursor(query, this.getCursor(\"from\"), caseFold)\n    while (cur.findNext()) {\n      if (CodeMirror.cmpPos(cur.to(), this.getCursor(\"to\")) > 0) break\n      ranges.push({anchor: cur.from(), head: cur.to()})\n    }\n    if (ranges.length)\n      this.setSelections(ranges, 0)\n  })\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/selection/active-line.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n  var WRAP_CLASS = \"CodeMirror-activeline\";\n  var BACK_CLASS = \"CodeMirror-activeline-background\";\n  var GUTT_CLASS = \"CodeMirror-activeline-gutter\";\n\n  CodeMirror.defineOption(\"styleActiveLine\", false, function(cm, val, old) {\n    var prev = old == CodeMirror.Init ? false : old;\n    if (val == prev) return\n    if (prev) {\n      cm.off(\"beforeSelectionChange\", selectionChange);\n      clearActiveLines(cm);\n      delete cm.state.activeLines;\n    }\n    if (val) {\n      cm.state.activeLines = [];\n      updateActiveLines(cm, cm.listSelections());\n      cm.on(\"beforeSelectionChange\", selectionChange);\n    }\n  });\n\n  function clearActiveLines(cm) {\n    for (var i = 0; i < cm.state.activeLines.length; i++) {\n      cm.removeLineClass(cm.state.activeLines[i], \"wrap\", WRAP_CLASS);\n      cm.removeLineClass(cm.state.activeLines[i], \"background\", BACK_CLASS);\n      cm.removeLineClass(cm.state.activeLines[i], \"gutter\", GUTT_CLASS);\n    }\n  }\n\n  function sameArray(a, b) {\n    if (a.length != b.length) return false;\n    for (var i = 0; i < a.length; i++)\n      if (a[i] != b[i]) return false;\n    return true;\n  }\n\n  function updateActiveLines(cm, ranges) {\n    var active = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      var option = cm.getOption(\"styleActiveLine\");\n      if (typeof option == \"object\" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())\n        continue\n      var line = cm.getLineHandleVisualStart(range.head.line);\n      if (active[active.length - 1] != line) active.push(line);\n    }\n    if (sameArray(cm.state.activeLines, active)) return;\n    cm.operation(function() {\n      clearActiveLines(cm);\n      for (var i = 0; i < active.length; i++) {\n        cm.addLineClass(active[i], \"wrap\", WRAP_CLASS);\n        cm.addLineClass(active[i], \"background\", BACK_CLASS);\n        cm.addLineClass(active[i], \"gutter\", GUTT_CLASS);\n      }\n      cm.state.activeLines = active;\n    });\n  }\n\n  function selectionChange(cm, sel) {\n    updateActiveLines(cm, sel.ranges);\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/selection/mark-selection.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// Because sometimes you need to mark the selected *text*.\n//\n// Adds an option 'styleSelectedText' which, when enabled, gives\n// selected text the CSS class given as option value, or\n// \"CodeMirror-selectedtext\" when the value is not a string.\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"styleSelectedText\", false, function(cm, val, old) {\n    var prev = old && old != CodeMirror.Init;\n    if (val && !prev) {\n      cm.state.markedSelection = [];\n      cm.state.markedSelectionStyle = typeof val == \"string\" ? val : \"CodeMirror-selectedtext\";\n      reset(cm);\n      cm.on(\"cursorActivity\", onCursorActivity);\n      cm.on(\"change\", onChange);\n    } else if (!val && prev) {\n      cm.off(\"cursorActivity\", onCursorActivity);\n      cm.off(\"change\", onChange);\n      clear(cm);\n      cm.state.markedSelection = cm.state.markedSelectionStyle = null;\n    }\n  });\n\n  function onCursorActivity(cm) {\n    if (cm.state.markedSelection)\n      cm.operation(function() { update(cm); });\n  }\n\n  function onChange(cm) {\n    if (cm.state.markedSelection && cm.state.markedSelection.length)\n      cm.operation(function() { clear(cm); });\n  }\n\n  var CHUNK_SIZE = 8;\n  var Pos = CodeMirror.Pos;\n  var cmp = CodeMirror.cmpPos;\n\n  function coverRange(cm, from, to, addAt) {\n    if (cmp(from, to) == 0) return;\n    var array = cm.state.markedSelection;\n    var cls = cm.state.markedSelectionStyle;\n    for (var line = from.line;;) {\n      var start = line == from.line ? from : Pos(line, 0);\n      var endLine = line + CHUNK_SIZE, atEnd = endLine >= to.line;\n      var end = atEnd ? to : Pos(endLine, 0);\n      var mark = cm.markText(start, end, {className: cls});\n      if (addAt == null) array.push(mark);\n      else array.splice(addAt++, 0, mark);\n      if (atEnd) break;\n      line = endLine;\n    }\n  }\n\n  function clear(cm) {\n    var array = cm.state.markedSelection;\n    for (var i = 0; i < array.length; ++i) array[i].clear();\n    array.length = 0;\n  }\n\n  function reset(cm) {\n    clear(cm);\n    var ranges = cm.listSelections();\n    for (var i = 0; i < ranges.length; i++)\n      coverRange(cm, ranges[i].from(), ranges[i].to());\n  }\n\n  function update(cm) {\n    if (!cm.somethingSelected()) return clear(cm);\n    if (cm.listSelections().length > 1) return reset(cm);\n\n    var from = cm.getCursor(\"start\"), to = cm.getCursor(\"end\");\n\n    var array = cm.state.markedSelection;\n    if (!array.length) return coverRange(cm, from, to);\n\n    var coverStart = array[0].find(), coverEnd = array[array.length - 1].find();\n    if (!coverStart || !coverEnd || to.line - from.line <= CHUNK_SIZE ||\n        cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0)\n      return reset(cm);\n\n    while (cmp(from, coverStart.from) > 0) {\n      array.shift().clear();\n      coverStart = array[0].find();\n    }\n    if (cmp(from, coverStart.from) < 0) {\n      if (coverStart.to.line - from.line < CHUNK_SIZE) {\n        array.shift().clear();\n        coverRange(cm, from, coverStart.to, 0);\n      } else {\n        coverRange(cm, from, coverStart.from, 0);\n      }\n    }\n\n    while (cmp(to, coverEnd.to) < 0) {\n      array.pop().clear();\n      coverEnd = array[array.length - 1].find();\n    }\n    if (cmp(to, coverEnd.to) > 0) {\n      if (to.line - coverEnd.from.line < CHUNK_SIZE) {\n        array.pop().clear();\n        coverRange(cm, coverEnd.from, to);\n      } else {\n        coverRange(cm, coverEnd.to, to);\n      }\n    }\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/selection/selection-pointer.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineOption(\"selectionPointer\", false, function(cm, val) {\n    var data = cm.state.selectionPointer;\n    if (data) {\n      CodeMirror.off(cm.getWrapperElement(), \"mousemove\", data.mousemove);\n      CodeMirror.off(cm.getWrapperElement(), \"mouseout\", data.mouseout);\n      CodeMirror.off(window, \"scroll\", data.windowScroll);\n      cm.off(\"cursorActivity\", reset);\n      cm.off(\"scroll\", reset);\n      cm.state.selectionPointer = null;\n      cm.display.lineDiv.style.cursor = \"\";\n    }\n    if (val) {\n      data = cm.state.selectionPointer = {\n        value: typeof val == \"string\" ? val : \"default\",\n        mousemove: function(event) { mousemove(cm, event); },\n        mouseout: function(event) { mouseout(cm, event); },\n        windowScroll: function() { reset(cm); },\n        rects: null,\n        mouseX: null, mouseY: null,\n        willUpdate: false\n      };\n      CodeMirror.on(cm.getWrapperElement(), \"mousemove\", data.mousemove);\n      CodeMirror.on(cm.getWrapperElement(), \"mouseout\", data.mouseout);\n      CodeMirror.on(window, \"scroll\", data.windowScroll);\n      cm.on(\"cursorActivity\", reset);\n      cm.on(\"scroll\", reset);\n    }\n  });\n\n  function mousemove(cm, event) {\n    var data = cm.state.selectionPointer;\n    if (event.buttons == null ? event.which : event.buttons) {\n      data.mouseX = data.mouseY = null;\n    } else {\n      data.mouseX = event.clientX;\n      data.mouseY = event.clientY;\n    }\n    scheduleUpdate(cm);\n  }\n\n  function mouseout(cm, event) {\n    if (!cm.getWrapperElement().contains(event.relatedTarget)) {\n      var data = cm.state.selectionPointer;\n      data.mouseX = data.mouseY = null;\n      scheduleUpdate(cm);\n    }\n  }\n\n  function reset(cm) {\n    cm.state.selectionPointer.rects = null;\n    scheduleUpdate(cm);\n  }\n\n  function scheduleUpdate(cm) {\n    if (!cm.state.selectionPointer.willUpdate) {\n      cm.state.selectionPointer.willUpdate = true;\n      setTimeout(function() {\n        update(cm);\n        cm.state.selectionPointer.willUpdate = false;\n      }, 50);\n    }\n  }\n\n  function update(cm) {\n    var data = cm.state.selectionPointer;\n    if (!data) return;\n    if (data.rects == null && data.mouseX != null) {\n      data.rects = [];\n      if (cm.somethingSelected()) {\n        for (var sel = cm.display.selectionDiv.firstChild; sel; sel = sel.nextSibling)\n          data.rects.push(sel.getBoundingClientRect());\n      }\n    }\n    var inside = false;\n    if (data.mouseX != null) for (var i = 0; i < data.rects.length; i++) {\n      var rect = data.rects[i];\n      if (rect.left <= data.mouseX && rect.right >= data.mouseX &&\n          rect.top <= data.mouseY && rect.bottom >= data.mouseY)\n        inside = true;\n    }\n    var cursor = inside ? data.value : \"\";\n    if (cm.display.lineDiv.style.cursor != cursor)\n      cm.display.lineDiv.style.cursor = cursor;\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/simple.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineSimpleMode = function(name, states) {\n    CodeMirror.defineMode(name, function(config) {\n      return CodeMirror.simpleMode(config, states);\n    });\n  };\n\n  CodeMirror.simpleMode = function(config, states) {\n    ensureState(states, \"start\");\n    var states_ = {}, meta = states.meta || {}, hasIndentation = false;\n    for (var state in states) if (state != meta && states.hasOwnProperty(state)) {\n      var list = states_[state] = [], orig = states[state];\n      for (var i = 0; i < orig.length; i++) {\n        var data = orig[i];\n        list.push(new Rule(data, states));\n        if (data.indent || data.dedent) hasIndentation = true;\n      }\n    }\n    var mode = {\n      startState: function() {\n        return {state: \"start\", pending: null,\n                local: null, localState: null,\n                indent: hasIndentation ? [] : null};\n      },\n      copyState: function(state) {\n        var s = {state: state.state, pending: state.pending,\n                 local: state.local, localState: null,\n                 indent: state.indent && state.indent.slice(0)};\n        if (state.localState)\n          s.localState = CodeMirror.copyState(state.local.mode, state.localState);\n        if (state.stack)\n          s.stack = state.stack.slice(0);\n        for (var pers = state.persistentStates; pers; pers = pers.next)\n          s.persistentStates = {mode: pers.mode,\n                                spec: pers.spec,\n                                state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),\n                                next: s.persistentStates};\n        return s;\n      },\n      token: tokenFunction(states_, config),\n      innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },\n      indent: indentFunction(states_, meta)\n    };\n    if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))\n      mode[prop] = meta[prop];\n    return mode;\n  };\n\n  function ensureState(states, name) {\n    if (!states.hasOwnProperty(name))\n      throw new Error(\"Undefined state \" + name + \" in simple mode\");\n  }\n\n  function toRegex(val, caret) {\n    if (!val) return /(?:)/;\n    var flags = \"\";\n    if (val instanceof RegExp) {\n      if (val.ignoreCase) flags = \"i\";\n      val = val.source;\n    } else {\n      val = String(val);\n    }\n    return new RegExp((caret === false ? \"\" : \"^\") + \"(?:\" + val + \")\", flags);\n  }\n\n  function asToken(val) {\n    if (!val) return null;\n    if (val.apply) return val\n    if (typeof val == \"string\") return val.replace(/\\./g, \" \");\n    var result = [];\n    for (var i = 0; i < val.length; i++)\n      result.push(val[i] && val[i].replace(/\\./g, \" \"));\n    return result;\n  }\n\n  function Rule(data, states) {\n    if (data.next || data.push) ensureState(states, data.next || data.push);\n    this.regex = toRegex(data.regex);\n    this.token = asToken(data.token);\n    this.data = data;\n  }\n\n  function tokenFunction(states, config) {\n    return function(stream, state) {\n      if (state.pending) {\n        var pend = state.pending.shift();\n        if (state.pending.length == 0) state.pending = null;\n        stream.pos += pend.text.length;\n        return pend.token;\n      }\n\n      if (state.local) {\n        if (state.local.end && stream.match(state.local.end)) {\n          var tok = state.local.endToken || null;\n          state.local = state.localState = null;\n          return tok;\n        } else {\n          var tok = state.local.mode.token(stream, state.localState), m;\n          if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))\n            stream.pos = stream.start + m.index;\n          return tok;\n        }\n      }\n\n      var curState = states[state.state];\n      for (var i = 0; i < curState.length; i++) {\n        var rule = curState[i];\n        var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);\n        if (matches) {\n          if (rule.data.next) {\n            state.state = rule.data.next;\n          } else if (rule.data.push) {\n            (state.stack || (state.stack = [])).push(state.state);\n            state.state = rule.data.push;\n          } else if (rule.data.pop && state.stack && state.stack.length) {\n            state.state = state.stack.pop();\n          }\n\n          if (rule.data.mode)\n            enterLocalMode(config, state, rule.data.mode, rule.token);\n          if (rule.data.indent)\n            state.indent.push(stream.indentation() + config.indentUnit);\n          if (rule.data.dedent)\n            state.indent.pop();\n          var token = rule.token\n          if (token && token.apply) token = token(matches)\n          if (matches.length > 2 && rule.token && typeof rule.token != \"string\") {\n            state.pending = [];\n            for (var j = 2; j < matches.length; j++)\n              if (matches[j])\n                state.pending.push({text: matches[j], token: rule.token[j - 1]});\n            stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));\n            return token[0];\n          } else if (token && token.join) {\n            return token[0];\n          } else {\n            return token;\n          }\n        }\n      }\n      stream.next();\n      return null;\n    };\n  }\n\n  function cmp(a, b) {\n    if (a === b) return true;\n    if (!a || typeof a != \"object\" || !b || typeof b != \"object\") return false;\n    var props = 0;\n    for (var prop in a) if (a.hasOwnProperty(prop)) {\n      if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;\n      props++;\n    }\n    for (var prop in b) if (b.hasOwnProperty(prop)) props--;\n    return props == 0;\n  }\n\n  function enterLocalMode(config, state, spec, token) {\n    var pers;\n    if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)\n      if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;\n    var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);\n    var lState = pers ? pers.state : CodeMirror.startState(mode);\n    if (spec.persistent && !pers)\n      state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};\n\n    state.localState = lState;\n    state.local = {mode: mode,\n                   end: spec.end && toRegex(spec.end),\n                   endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),\n                   endToken: token && token.join ? token[token.length - 1] : token};\n  }\n\n  function indexOf(val, arr) {\n    for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;\n  }\n\n  function indentFunction(states, meta) {\n    return function(state, textAfter, line) {\n      if (state.local && state.local.mode.indent)\n        return state.local.mode.indent(state.localState, textAfter, line);\n      if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)\n        return CodeMirror.Pass;\n\n      var pos = state.indent.length - 1, rules = states[state.state];\n      scan: for (;;) {\n        for (var i = 0; i < rules.length; i++) {\n          var rule = rules[i];\n          if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {\n            var m = rule.regex.exec(textAfter);\n            if (m && m[0]) {\n              pos--;\n              if (rule.next || rule.push) rules = states[rule.next || rule.push];\n              textAfter = textAfter.slice(m[0].length);\n              continue scan;\n            }\n          }\n        }\n        break;\n      }\n      return pos < 0 ? 0 : state.indent[pos];\n    };\n  }\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/extension/sublime.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n// A rough approximation of Sublime Text's keybindings\n// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../lib/codemirror\"), require(\"../addon/search/searchcursor\"), require(\"../addon/edit/matchbrackets\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../lib/codemirror\", \"../addon/search/searchcursor\", \"../addon/edit/matchbrackets\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var cmds = CodeMirror.commands;\n  var Pos = CodeMirror.Pos;\n\n  // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.\n  function findPosSubword(doc, start, dir) {\n    if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));\n    var line = doc.getLine(start.line);\n    if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));\n    var state = \"start\", type, startPos = start.ch;\n    for (var pos = startPos, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {\n      var next = line.charAt(dir < 0 ? pos - 1 : pos);\n      var cat = next != \"_\" && CodeMirror.isWordChar(next) ? \"w\" : \"o\";\n      if (cat == \"w\" && next.toUpperCase() == next) cat = \"W\";\n      if (state == \"start\") {\n        if (cat != \"o\") { state = \"in\"; type = cat; }\n        else startPos = pos + dir\n      } else if (state == \"in\") {\n        if (type != cat) {\n          if (type == \"w\" && cat == \"W\" && dir < 0) pos--;\n          if (type == \"W\" && cat == \"w\" && dir > 0) { // From uppercase to lowercase\n            if (pos == startPos + 1) { type = \"w\"; continue; }\n            else pos--;\n          }\n          break;\n        }\n      }\n    }\n    return Pos(start.line, pos);\n  }\n\n  function moveSubword(cm, dir) {\n    cm.extendSelectionsBy(function(range) {\n      if (cm.display.shift || cm.doc.extend || range.empty())\n        return findPosSubword(cm.doc, range.head, dir);\n      else\n        return dir < 0 ? range.from() : range.to();\n    });\n  }\n\n  cmds.goSubwordLeft = function(cm) { moveSubword(cm, -1); };\n  cmds.goSubwordRight = function(cm) { moveSubword(cm, 1); };\n\n  cmds.scrollLineUp = function(cm) {\n    var info = cm.getScrollInfo();\n    if (!cm.somethingSelected()) {\n      var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, \"local\");\n      if (cm.getCursor().line >= visibleBottomLine)\n        cm.execCommand(\"goLineUp\");\n    }\n    cm.scrollTo(null, info.top - cm.defaultTextHeight());\n  };\n  cmds.scrollLineDown = function(cm) {\n    var info = cm.getScrollInfo();\n    if (!cm.somethingSelected()) {\n      var visibleTopLine = cm.lineAtHeight(info.top, \"local\")+1;\n      if (cm.getCursor().line <= visibleTopLine)\n        cm.execCommand(\"goLineDown\");\n    }\n    cm.scrollTo(null, info.top + cm.defaultTextHeight());\n  };\n\n  cmds.splitSelectionByLine = function(cm) {\n    var ranges = cm.listSelections(), lineRanges = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var from = ranges[i].from(), to = ranges[i].to();\n      for (var line = from.line; line <= to.line; ++line)\n        if (!(to.line > from.line && line == to.line && to.ch == 0))\n          lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),\n                           head: line == to.line ? to : Pos(line)});\n    }\n    cm.setSelections(lineRanges, 0);\n  };\n\n  cmds.singleSelectionTop = function(cm) {\n    var range = cm.listSelections()[0];\n    cm.setSelection(range.anchor, range.head, {scroll: false});\n  };\n\n  cmds.selectLine = function(cm) {\n    var ranges = cm.listSelections(), extended = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      extended.push({anchor: Pos(range.from().line, 0),\n                     head: Pos(range.to().line + 1, 0)});\n    }\n    cm.setSelections(extended);\n  };\n\n  function insertLine(cm, above) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    cm.operation(function() {\n      var len = cm.listSelections().length, newSelection = [], last = -1;\n      for (var i = 0; i < len; i++) {\n        var head = cm.listSelections()[i].head;\n        if (head.line <= last) continue;\n        var at = Pos(head.line + (above ? 0 : 1), 0);\n        cm.replaceRange(\"\\n\", at, null, \"+insertLine\");\n        cm.indentLine(at.line, null, true);\n        newSelection.push({head: at, anchor: at});\n        last = head.line + 1;\n      }\n      cm.setSelections(newSelection);\n    });\n    cm.execCommand(\"indentAuto\");\n  }\n\n  cmds.insertLineAfter = function(cm) { return insertLine(cm, false); };\n\n  cmds.insertLineBefore = function(cm) { return insertLine(cm, true); };\n\n  function wordAt(cm, pos) {\n    var start = pos.ch, end = start, line = cm.getLine(pos.line);\n    while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;\n    while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;\n    return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};\n  }\n\n  cmds.selectNextOccurrence = function(cm) {\n    var from = cm.getCursor(\"from\"), to = cm.getCursor(\"to\");\n    var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;\n    if (CodeMirror.cmpPos(from, to) == 0) {\n      var word = wordAt(cm, from);\n      if (!word.word) return;\n      cm.setSelection(word.from, word.to);\n      fullWord = true;\n    } else {\n      var text = cm.getRange(from, to);\n      var query = fullWord ? new RegExp(\"\\\\b\" + text + \"\\\\b\") : text;\n      var cur = cm.getSearchCursor(query, to);\n      var found = cur.findNext();\n      if (!found) {\n        cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0));\n        found = cur.findNext();\n      }\n      if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to())) return\n      cm.addSelection(cur.from(), cur.to());\n    }\n    if (fullWord)\n      cm.state.sublimeFindFullWord = cm.doc.sel;\n  };\n\n  cmds.skipAndSelectNextOccurrence = function(cm) {\n    var prevAnchor = cm.getCursor(\"anchor\"), prevHead = cm.getCursor(\"head\");\n    cmds.selectNextOccurrence(cm);\n    if (CodeMirror.cmpPos(prevAnchor, prevHead) != 0) {\n      cm.doc.setSelections(cm.doc.listSelections()\n          .filter(function (sel) {\n            return sel.anchor != prevAnchor || sel.head != prevHead;\n          }));\n    }\n  }\n\n  function addCursorToSelection(cm, dir) {\n    var ranges = cm.listSelections(), newRanges = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      var newAnchor = cm.findPosV(\n          range.anchor, dir, \"line\", range.anchor.goalColumn);\n      var newHead = cm.findPosV(\n          range.head, dir, \"line\", range.head.goalColumn);\n      newAnchor.goalColumn = range.anchor.goalColumn != null ?\n          range.anchor.goalColumn : cm.cursorCoords(range.anchor, \"div\").left;\n      newHead.goalColumn = range.head.goalColumn != null ?\n          range.head.goalColumn : cm.cursorCoords(range.head, \"div\").left;\n      var newRange = {anchor: newAnchor, head: newHead};\n      newRanges.push(range);\n      newRanges.push(newRange);\n    }\n    cm.setSelections(newRanges);\n  }\n  cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };\n  cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };\n\n  function isSelectedRange(ranges, from, to) {\n    for (var i = 0; i < ranges.length; i++)\n      if (CodeMirror.cmpPos(ranges[i].from(), from) == 0 &&\n          CodeMirror.cmpPos(ranges[i].to(), to) == 0) return true\n    return false\n  }\n\n  var mirror = \"(){}[]\";\n  function selectBetweenBrackets(cm) {\n    var ranges = cm.listSelections(), newRanges = []\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], pos = range.head, opening = cm.scanForBracket(pos, -1);\n      if (!opening) return false;\n      for (;;) {\n        var closing = cm.scanForBracket(pos, 1);\n        if (!closing) return false;\n        if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) {\n          var startPos = Pos(opening.pos.line, opening.pos.ch + 1);\n          if (CodeMirror.cmpPos(startPos, range.from()) == 0 &&\n              CodeMirror.cmpPos(closing.pos, range.to()) == 0) {\n            opening = cm.scanForBracket(opening.pos, -1);\n            if (!opening) return false;\n          } else {\n            newRanges.push({anchor: startPos, head: closing.pos});\n            break;\n          }\n        }\n        pos = Pos(closing.pos.line, closing.pos.ch + 1);\n      }\n    }\n    cm.setSelections(newRanges);\n    return true;\n  }\n\n  cmds.selectScope = function(cm) {\n    selectBetweenBrackets(cm) || cm.execCommand(\"selectAll\");\n  };\n  cmds.selectBetweenBrackets = function(cm) {\n    if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;\n  };\n\n  function puncType(type) {\n    return !type ? null : /\\bpunctuation\\b/.test(type) ? type : undefined\n  }\n\n  cmds.goToBracket = function(cm) {\n    cm.extendSelectionsBy(function(range) {\n      var next = cm.scanForBracket(range.head, 1, puncType(cm.getTokenTypeAt(range.head)));\n      if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;\n      var prev = cm.scanForBracket(range.head, -1, puncType(cm.getTokenTypeAt(Pos(range.head.line, range.head.ch + 1))));\n      return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;\n    });\n  };\n\n  cmds.swapLineUp = function(cm) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], from = range.from().line - 1, to = range.to().line;\n      newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch),\n                    head: Pos(range.head.line - 1, range.head.ch)});\n      if (range.to().ch == 0 && !range.empty()) --to;\n      if (from > at) linesToMove.push(from, to);\n      else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;\n      at = to;\n    }\n    cm.operation(function() {\n      for (var i = 0; i < linesToMove.length; i += 2) {\n        var from = linesToMove[i], to = linesToMove[i + 1];\n        var line = cm.getLine(from);\n        cm.replaceRange(\"\", Pos(from, 0), Pos(from + 1, 0), \"+swapLine\");\n        if (to > cm.lastLine())\n          cm.replaceRange(\"\\n\" + line, Pos(cm.lastLine()), null, \"+swapLine\");\n        else\n          cm.replaceRange(line + \"\\n\", Pos(to, 0), null, \"+swapLine\");\n      }\n      cm.setSelections(newSels);\n      cm.scrollIntoView();\n    });\n  };\n\n  cmds.swapLineDown = function(cm) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;\n    for (var i = ranges.length - 1; i >= 0; i--) {\n      var range = ranges[i], from = range.to().line + 1, to = range.from().line;\n      if (range.to().ch == 0 && !range.empty()) from--;\n      if (from < at) linesToMove.push(from, to);\n      else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;\n      at = to;\n    }\n    cm.operation(function() {\n      for (var i = linesToMove.length - 2; i >= 0; i -= 2) {\n        var from = linesToMove[i], to = linesToMove[i + 1];\n        var line = cm.getLine(from);\n        if (from == cm.lastLine())\n          cm.replaceRange(\"\", Pos(from - 1), Pos(from), \"+swapLine\");\n        else\n          cm.replaceRange(\"\", Pos(from, 0), Pos(from + 1, 0), \"+swapLine\");\n        cm.replaceRange(line + \"\\n\", Pos(to, 0), null, \"+swapLine\");\n      }\n      cm.scrollIntoView();\n    });\n  };\n\n  cmds.toggleCommentIndented = function(cm) {\n    cm.toggleComment({ indent: true });\n  }\n\n  cmds.joinLines = function(cm) {\n    var ranges = cm.listSelections(), joined = [];\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i], from = range.from();\n      var start = from.line, end = range.to().line;\n      while (i < ranges.length - 1 && ranges[i + 1].from().line == end)\n        end = ranges[++i].to().line;\n      joined.push({start: start, end: end, anchor: !range.empty() && from});\n    }\n    cm.operation(function() {\n      var offset = 0, ranges = [];\n      for (var i = 0; i < joined.length; i++) {\n        var obj = joined[i];\n        var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head;\n        for (var line = obj.start; line <= obj.end; line++) {\n          var actual = line - offset;\n          if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1);\n          if (actual < cm.lastLine()) {\n            cm.replaceRange(\" \", Pos(actual), Pos(actual + 1, /^\\s*/.exec(cm.getLine(actual + 1))[0].length));\n            ++offset;\n          }\n        }\n        ranges.push({anchor: anchor || head, head: head});\n      }\n      cm.setSelections(ranges, 0);\n    });\n  };\n\n  cmds.duplicateLine = function(cm) {\n    cm.operation(function() {\n      var rangeCount = cm.listSelections().length;\n      for (var i = 0; i < rangeCount; i++) {\n        var range = cm.listSelections()[i];\n        if (range.empty())\n          cm.replaceRange(cm.getLine(range.head.line) + \"\\n\", Pos(range.head.line, 0));\n        else\n          cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());\n      }\n      cm.scrollIntoView();\n    });\n  };\n\n\n  function sortLines(cm, caseSensitive) {\n    if (cm.isReadOnly()) return CodeMirror.Pass\n    var ranges = cm.listSelections(), toSort = [], selected;\n    for (var i = 0; i < ranges.length; i++) {\n      var range = ranges[i];\n      if (range.empty()) continue;\n      var from = range.from().line, to = range.to().line;\n      while (i < ranges.length - 1 && ranges[i + 1].from().line == to)\n        to = ranges[++i].to().line;\n      if (!ranges[i].to().ch) to--;\n      toSort.push(from, to);\n    }\n    if (toSort.length) selected = true;\n    else toSort.push(cm.firstLine(), cm.lastLine());\n\n    cm.operation(function() {\n      var ranges = [];\n      for (var i = 0; i < toSort.length; i += 2) {\n        var from = toSort[i], to = toSort[i + 1];\n        var start = Pos(from, 0), end = Pos(to);\n        var lines = cm.getRange(start, end, false);\n        if (caseSensitive)\n          lines.sort();\n        else\n          lines.sort(function(a, b) {\n            var au = a.toUpperCase(), bu = b.toUpperCase();\n            if (au != bu) { a = au; b = bu; }\n            return a < b ? -1 : a == b ? 0 : 1;\n          });\n        cm.replaceRange(lines, start, end);\n        if (selected) ranges.push({anchor: start, head: Pos(to + 1, 0)});\n      }\n      if (selected) cm.setSelections(ranges, 0);\n    });\n  }\n\n  cmds.sortLines = function(cm) { sortLines(cm, true); };\n  cmds.sortLinesInsensitive = function(cm) { sortLines(cm, false); };\n\n  cmds.nextBookmark = function(cm) {\n    var marks = cm.state.sublimeBookmarks;\n    if (marks) while (marks.length) {\n      var current = marks.shift();\n      var found = current.find();\n      if (found) {\n        marks.push(current);\n        return cm.setSelection(found.from, found.to);\n      }\n    }\n  };\n\n  cmds.prevBookmark = function(cm) {\n    var marks = cm.state.sublimeBookmarks;\n    if (marks) while (marks.length) {\n      marks.unshift(marks.pop());\n      var found = marks[marks.length - 1].find();\n      if (!found)\n        marks.pop();\n      else\n        return cm.setSelection(found.from, found.to);\n    }\n  };\n\n  cmds.toggleBookmark = function(cm) {\n    var ranges = cm.listSelections();\n    var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);\n    for (var i = 0; i < ranges.length; i++) {\n      var from = ranges[i].from(), to = ranges[i].to();\n      var found = ranges[i].empty() ? cm.findMarksAt(from) : cm.findMarks(from, to);\n      for (var j = 0; j < found.length; j++) {\n        if (found[j].sublimeBookmark) {\n          found[j].clear();\n          for (var k = 0; k < marks.length; k++)\n            if (marks[k] == found[j])\n              marks.splice(k--, 1);\n          break;\n        }\n      }\n      if (j == found.length)\n        marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));\n    }\n  };\n\n  cmds.clearBookmarks = function(cm) {\n    var marks = cm.state.sublimeBookmarks;\n    if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();\n    marks.length = 0;\n  };\n\n  cmds.selectBookmarks = function(cm) {\n    var marks = cm.state.sublimeBookmarks, ranges = [];\n    if (marks) for (var i = 0; i < marks.length; i++) {\n      var found = marks[i].find();\n      if (!found)\n        marks.splice(i--, 0);\n      else\n        ranges.push({anchor: found.from, head: found.to});\n    }\n    if (ranges.length)\n      cm.setSelections(ranges, 0);\n  };\n\n  function modifyWordOrSelection(cm, mod) {\n    cm.operation(function() {\n      var ranges = cm.listSelections(), indices = [], replacements = [];\n      for (var i = 0; i < ranges.length; i++) {\n        var range = ranges[i];\n        if (range.empty()) { indices.push(i); replacements.push(\"\"); }\n        else replacements.push(mod(cm.getRange(range.from(), range.to())));\n      }\n      cm.replaceSelections(replacements, \"around\", \"case\");\n      for (var i = indices.length - 1, at; i >= 0; i--) {\n        var range = ranges[indices[i]];\n        if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;\n        var word = wordAt(cm, range.head);\n        at = word.from;\n        cm.replaceRange(mod(word.word), word.from, word.to);\n      }\n    });\n  }\n\n  cmds.smartBackspace = function(cm) {\n    if (cm.somethingSelected()) return CodeMirror.Pass;\n\n    cm.operation(function() {\n      var cursors = cm.listSelections();\n      var indentUnit = cm.getOption(\"indentUnit\");\n\n      for (var i = cursors.length - 1; i >= 0; i--) {\n        var cursor = cursors[i].head;\n        var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);\n        var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption(\"tabSize\"));\n\n        // Delete by one character by default\n        var deletePos = cm.findPosH(cursor, -1, \"char\", false);\n\n        if (toStartOfLine && !/\\S/.test(toStartOfLine) && column % indentUnit == 0) {\n          var prevIndent = new Pos(cursor.line,\n            CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));\n\n          // Smart delete only if we found a valid prevIndent location\n          if (prevIndent.ch != cursor.ch) deletePos = prevIndent;\n        }\n\n        cm.replaceRange(\"\", deletePos, cursor, \"+delete\");\n      }\n    });\n  };\n\n  cmds.delLineRight = function(cm) {\n    cm.operation(function() {\n      var ranges = cm.listSelections();\n      for (var i = ranges.length - 1; i >= 0; i--)\n        cm.replaceRange(\"\", ranges[i].anchor, Pos(ranges[i].to().line), \"+delete\");\n      cm.scrollIntoView();\n    });\n  };\n\n  cmds.upcaseAtCursor = function(cm) {\n    modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });\n  };\n  cmds.downcaseAtCursor = function(cm) {\n    modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });\n  };\n\n  cmds.setSublimeMark = function(cm) {\n    if (cm.state.sublimeMark) cm.state.sublimeMark.clear();\n    cm.state.sublimeMark = cm.setBookmark(cm.getCursor());\n  };\n  cmds.selectToSublimeMark = function(cm) {\n    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();\n    if (found) cm.setSelection(cm.getCursor(), found);\n  };\n  cmds.deleteToSublimeMark = function(cm) {\n    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();\n    if (found) {\n      var from = cm.getCursor(), to = found;\n      if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }\n      cm.state.sublimeKilled = cm.getRange(from, to);\n      cm.replaceRange(\"\", from, to);\n    }\n  };\n  cmds.swapWithSublimeMark = function(cm) {\n    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();\n    if (found) {\n      cm.state.sublimeMark.clear();\n      cm.state.sublimeMark = cm.setBookmark(cm.getCursor());\n      cm.setCursor(found);\n    }\n  };\n  cmds.sublimeYank = function(cm) {\n    if (cm.state.sublimeKilled != null)\n      cm.replaceSelection(cm.state.sublimeKilled, null, \"paste\");\n  };\n\n  cmds.showInCenter = function(cm) {\n    var pos = cm.cursorCoords(null, \"local\");\n    cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);\n  };\n\n  function getTarget(cm) {\n    var from = cm.getCursor(\"from\"), to = cm.getCursor(\"to\");\n    if (CodeMirror.cmpPos(from, to) == 0) {\n      var word = wordAt(cm, from);\n      if (!word.word) return;\n      from = word.from;\n      to = word.to;\n    }\n    return {from: from, to: to, query: cm.getRange(from, to), word: word};\n  }\n\n  function findAndGoTo(cm, forward) {\n    var target = getTarget(cm);\n    if (!target) return;\n    var query = target.query;\n    var cur = cm.getSearchCursor(query, forward ? target.to : target.from);\n\n    if (forward ? cur.findNext() : cur.findPrevious()) {\n      cm.setSelection(cur.from(), cur.to());\n    } else {\n      cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)\n                                              : cm.clipPos(Pos(cm.lastLine())));\n      if (forward ? cur.findNext() : cur.findPrevious())\n        cm.setSelection(cur.from(), cur.to());\n      else if (target.word)\n        cm.setSelection(target.from, target.to);\n    }\n  };\n  cmds.findUnder = function(cm) { findAndGoTo(cm, true); };\n  cmds.findUnderPrevious = function(cm) { findAndGoTo(cm,false); };\n  cmds.findAllUnder = function(cm) {\n    var target = getTarget(cm);\n    if (!target) return;\n    var cur = cm.getSearchCursor(target.query);\n    var matches = [];\n    var primaryIndex = -1;\n    while (cur.findNext()) {\n      matches.push({anchor: cur.from(), head: cur.to()});\n      if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)\n        primaryIndex++;\n    }\n    cm.setSelections(matches, primaryIndex);\n  };\n\n\n  var keyMap = CodeMirror.keyMap;\n  keyMap.macSublime = {\n    \"Cmd-Left\": \"goLineStartSmart\",\n    \"Shift-Tab\": \"indentLess\",\n    \"Shift-Ctrl-K\": \"deleteLine\",\n    \"Alt-Q\": \"wrapLines\",\n    \"Ctrl-Left\": \"goSubwordLeft\",\n    \"Ctrl-Right\": \"goSubwordRight\",\n    \"Ctrl-Alt-Up\": \"scrollLineUp\",\n    \"Ctrl-Alt-Down\": \"scrollLineDown\",\n    \"Cmd-L\": \"selectLine\",\n    \"Shift-Cmd-L\": \"splitSelectionByLine\",\n    \"Esc\": \"singleSelectionTop\",\n    \"Cmd-Enter\": \"insertLineAfter\",\n    \"Shift-Cmd-Enter\": \"insertLineBefore\",\n    \"Cmd-D\": \"selectNextOccurrence\",\n    \"Shift-Cmd-Space\": \"selectScope\",\n    \"Shift-Cmd-M\": \"selectBetweenBrackets\",\n    \"Cmd-M\": \"goToBracket\",\n    \"Cmd-Ctrl-Up\": \"swapLineUp\",\n    \"Cmd-Ctrl-Down\": \"swapLineDown\",\n    \"Cmd-/\": \"toggleCommentIndented\",\n    \"Cmd-J\": \"joinLines\",\n    \"Shift-Cmd-D\": \"duplicateLine\",\n    \"F5\": \"sortLines\",\n    \"Cmd-F5\": \"sortLinesInsensitive\",\n    \"F2\": \"nextBookmark\",\n    \"Shift-F2\": \"prevBookmark\",\n    \"Cmd-F2\": \"toggleBookmark\",\n    \"Shift-Cmd-F2\": \"clearBookmarks\",\n    \"Alt-F2\": \"selectBookmarks\",\n    \"Backspace\": \"smartBackspace\",\n    \"Cmd-K Cmd-D\": \"skipAndSelectNextOccurrence\",\n    \"Cmd-K Cmd-K\": \"delLineRight\",\n    \"Cmd-K Cmd-U\": \"upcaseAtCursor\",\n    \"Cmd-K Cmd-L\": \"downcaseAtCursor\",\n    \"Cmd-K Cmd-Space\": \"setSublimeMark\",\n    \"Cmd-K Cmd-A\": \"selectToSublimeMark\",\n    \"Cmd-K Cmd-W\": \"deleteToSublimeMark\",\n    \"Cmd-K Cmd-X\": \"swapWithSublimeMark\",\n    \"Cmd-K Cmd-Y\": \"sublimeYank\",\n    \"Cmd-K Cmd-C\": \"showInCenter\",\n    \"Cmd-K Cmd-G\": \"clearBookmarks\",\n    \"Cmd-K Cmd-Backspace\": \"delLineLeft\",\n    \"Cmd-K Cmd-1\": \"foldAll\",\n    \"Cmd-K Cmd-0\": \"unfoldAll\",\n    \"Cmd-K Cmd-J\": \"unfoldAll\",\n    \"Ctrl-Shift-Up\": \"addCursorToPrevLine\",\n    \"Ctrl-Shift-Down\": \"addCursorToNextLine\",\n    \"Cmd-F3\": \"findUnder\",\n    \"Shift-Cmd-F3\": \"findUnderPrevious\",\n    \"Alt-F3\": \"findAllUnder\",\n    \"Shift-Cmd-[\": \"fold\",\n    \"Shift-Cmd-]\": \"unfold\",\n    \"Cmd-I\": \"findIncremental\",\n    \"Shift-Cmd-I\": \"findIncrementalReverse\",\n    \"Cmd-H\": \"replace\",\n    \"F3\": \"findNext\",\n    \"Shift-F3\": \"findPrev\",\n    \"fallthrough\": \"macDefault\"\n  };\n  CodeMirror.normalizeKeyMap(keyMap.macSublime);\n\n  keyMap.pcSublime = {\n    \"Shift-Tab\": \"indentLess\",\n    \"Shift-Ctrl-K\": \"deleteLine\",\n    \"Alt-Q\": \"wrapLines\",\n    \"Ctrl-T\": \"transposeChars\",\n    \"Alt-Left\": \"goSubwordLeft\",\n    \"Alt-Right\": \"goSubwordRight\",\n    \"Ctrl-Up\": \"scrollLineUp\",\n    \"Ctrl-Down\": \"scrollLineDown\",\n    \"Ctrl-L\": \"selectLine\",\n    \"Shift-Ctrl-L\": \"splitSelectionByLine\",\n    \"Esc\": \"singleSelectionTop\",\n    \"Ctrl-Enter\": \"insertLineAfter\",\n    \"Shift-Ctrl-Enter\": \"insertLineBefore\",\n    \"Ctrl-D\": \"selectNextOccurrence\",\n    \"Shift-Ctrl-Space\": \"selectScope\",\n    \"Shift-Ctrl-M\": \"selectBetweenBrackets\",\n    \"Ctrl-M\": \"goToBracket\",\n    \"Shift-Ctrl-Up\": \"swapLineUp\",\n    \"Shift-Ctrl-Down\": \"swapLineDown\",\n    \"Ctrl-/\": \"toggleCommentIndented\",\n    \"Ctrl-J\": \"joinLines\",\n    \"Shift-Ctrl-D\": \"duplicateLine\",\n    \"F9\": \"sortLines\",\n    \"Ctrl-F9\": \"sortLinesInsensitive\",\n    \"F2\": \"nextBookmark\",\n    \"Shift-F2\": \"prevBookmark\",\n    \"Ctrl-F2\": \"toggleBookmark\",\n    \"Shift-Ctrl-F2\": \"clearBookmarks\",\n    \"Alt-F2\": \"selectBookmarks\",\n    \"Backspace\": \"smartBackspace\",\n    \"Ctrl-K Ctrl-D\": \"skipAndSelectNextOccurrence\",\n    \"Ctrl-K Ctrl-K\": \"delLineRight\",\n    \"Ctrl-K Ctrl-U\": \"upcaseAtCursor\",\n    \"Ctrl-K Ctrl-L\": \"downcaseAtCursor\",\n    \"Ctrl-K Ctrl-Space\": \"setSublimeMark\",\n    \"Ctrl-K Ctrl-A\": \"selectToSublimeMark\",\n    \"Ctrl-K Ctrl-W\": \"deleteToSublimeMark\",\n    \"Ctrl-K Ctrl-X\": \"swapWithSublimeMark\",\n    \"Ctrl-K Ctrl-Y\": \"sublimeYank\",\n    \"Ctrl-K Ctrl-C\": \"showInCenter\",\n    \"Ctrl-K Ctrl-G\": \"clearBookmarks\",\n    \"Ctrl-K Ctrl-Backspace\": \"delLineLeft\",\n    \"Ctrl-K Ctrl-1\": \"foldAll\",\n    \"Ctrl-K Ctrl-0\": \"unfoldAll\",\n    \"Ctrl-K Ctrl-J\": \"unfoldAll\",\n    \"Ctrl-Alt-Up\": \"addCursorToPrevLine\",\n    \"Ctrl-Alt-Down\": \"addCursorToNextLine\",\n    \"Ctrl-F3\": \"findUnder\",\n    \"Shift-Ctrl-F3\": \"findUnderPrevious\",\n    \"Alt-F3\": \"findAllUnder\",\n    \"Shift-Ctrl-[\": \"fold\",\n    \"Shift-Ctrl-]\": \"unfold\",\n    \"Ctrl-I\": \"findIncremental\",\n    \"Shift-Ctrl-I\": \"findIncrementalReverse\",\n    \"Ctrl-H\": \"replace\",\n    \"F3\": \"findNext\",\n    \"Shift-F3\": \"findPrev\",\n    \"fallthrough\": \"pcDefault\"\n  };\n  CodeMirror.normalizeKeyMap(keyMap.pcSublime);\n\n  var mac = keyMap.default == keyMap.macDefault;\n  keyMap.sublime = mac ? keyMap.macSublime : keyMap.pcSublime;\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/coffeescript.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n/**\n * Link to the project's GitHub page:\n * https://github.com/pickhardt/coffeescript-codemirror-mode\n */\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"coffeescript\", function(conf, parserConf) {\n  var ERRORCLASS = \"error\";\n\n  function wordRegexp(words) {\n    return new RegExp(\"^((\" + words.join(\")|(\") + \"))\\\\b\");\n  }\n\n  var operators = /^(?:->|=>|\\+[+=]?|-[\\-=]?|\\*[\\*=]?|\\/[\\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\\|=?|\\^=?|\\~|!|\\?|(or|and|\\|\\||&&|\\?)=)/;\n  var delimiters = /^(?:[()\\[\\]{},:`=;]|\\.\\.?\\.?)/;\n  var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;\n  var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;\n\n  var wordOperators = wordRegexp([\"and\", \"or\", \"not\",\n                                  \"is\", \"isnt\", \"in\",\n                                  \"instanceof\", \"typeof\"]);\n  var indentKeywords = [\"for\", \"while\", \"loop\", \"if\", \"unless\", \"else\",\n                        \"switch\", \"try\", \"catch\", \"finally\", \"class\"];\n  var commonKeywords = [\"break\", \"by\", \"continue\", \"debugger\", \"delete\",\n                        \"do\", \"in\", \"of\", \"new\", \"return\", \"then\",\n                        \"this\", \"@\", \"throw\", \"when\", \"until\", \"extends\"];\n\n  var keywords = wordRegexp(indentKeywords.concat(commonKeywords));\n\n  indentKeywords = wordRegexp(indentKeywords);\n\n\n  var stringPrefixes = /^('{3}|\\\"{3}|['\\\"])/;\n  var regexPrefixes = /^(\\/{3}|\\/)/;\n  var commonConstants = [\"Infinity\", \"NaN\", \"undefined\", \"null\", \"true\", \"false\", \"on\", \"off\", \"yes\", \"no\"];\n  var constants = wordRegexp(commonConstants);\n\n  // Tokenizers\n  function tokenBase(stream, state) {\n    // Handle scope changes\n    if (stream.sol()) {\n      if (state.scope.align === null) state.scope.align = false;\n      var scopeOffset = state.scope.offset;\n      if (stream.eatSpace()) {\n        var lineOffset = stream.indentation();\n        if (lineOffset > scopeOffset && state.scope.type == \"coffee\") {\n          return \"indent\";\n        } else if (lineOffset < scopeOffset) {\n          return \"dedent\";\n        }\n        return null;\n      } else {\n        if (scopeOffset > 0) {\n          dedent(stream, state);\n        }\n      }\n    }\n    if (stream.eatSpace()) {\n      return null;\n    }\n\n    var ch = stream.peek();\n\n    // Handle docco title comment (single line)\n    if (stream.match(\"####\")) {\n      stream.skipToEnd();\n      return \"comment\";\n    }\n\n    // Handle multi line comments\n    if (stream.match(\"###\")) {\n      state.tokenize = longComment;\n      return state.tokenize(stream, state);\n    }\n\n    // Single line comment\n    if (ch === \"#\") {\n      stream.skipToEnd();\n      return \"comment\";\n    }\n\n    // Handle number literals\n    if (stream.match(/^-?[0-9\\.]/, false)) {\n      var floatLiteral = false;\n      // Floats\n      if (stream.match(/^-?\\d*\\.\\d+(e[\\+\\-]?\\d+)?/i)) {\n        floatLiteral = true;\n      }\n      if (stream.match(/^-?\\d+\\.\\d*/)) {\n        floatLiteral = true;\n      }\n      if (stream.match(/^-?\\.\\d+/)) {\n        floatLiteral = true;\n      }\n\n      if (floatLiteral) {\n        // prevent from getting extra . on 1..\n        if (stream.peek() == \".\"){\n          stream.backUp(1);\n        }\n        return \"number\";\n      }\n      // Integers\n      var intLiteral = false;\n      // Hex\n      if (stream.match(/^-?0x[0-9a-f]+/i)) {\n        intLiteral = true;\n      }\n      // Decimal\n      if (stream.match(/^-?[1-9]\\d*(e[\\+\\-]?\\d+)?/)) {\n        intLiteral = true;\n      }\n      // Zero by itself with no other piece of number.\n      if (stream.match(/^-?0(?![\\dx])/i)) {\n        intLiteral = true;\n      }\n      if (intLiteral) {\n        return \"number\";\n      }\n    }\n\n    // Handle strings\n    if (stream.match(stringPrefixes)) {\n      state.tokenize = tokenFactory(stream.current(), false, \"string\");\n      return state.tokenize(stream, state);\n    }\n    // Handle regex literals\n    if (stream.match(regexPrefixes)) {\n      if (stream.current() != \"/\" || stream.match(/^.*\\//, false)) { // prevent highlight of division\n        state.tokenize = tokenFactory(stream.current(), true, \"string-2\");\n        return state.tokenize(stream, state);\n      } else {\n        stream.backUp(1);\n      }\n    }\n\n\n\n    // Handle operators and delimiters\n    if (stream.match(operators) || stream.match(wordOperators)) {\n      return \"operator\";\n    }\n    if (stream.match(delimiters)) {\n      return \"punctuation\";\n    }\n\n    if (stream.match(constants)) {\n      return \"atom\";\n    }\n\n    if (stream.match(atProp) || state.prop && stream.match(identifiers)) {\n      return \"property\";\n    }\n\n    if (stream.match(keywords)) {\n      return \"keyword\";\n    }\n\n    if (stream.match(identifiers)) {\n      return \"variable\";\n    }\n\n    // Handle non-detected items\n    stream.next();\n    return ERRORCLASS;\n  }\n\n  function tokenFactory(delimiter, singleline, outclass) {\n    return function(stream, state) {\n      while (!stream.eol()) {\n        stream.eatWhile(/[^'\"\\/\\\\]/);\n        if (stream.eat(\"\\\\\")) {\n          stream.next();\n          if (singleline && stream.eol()) {\n            return outclass;\n          }\n        } else if (stream.match(delimiter)) {\n          state.tokenize = tokenBase;\n          return outclass;\n        } else {\n          stream.eat(/['\"\\/]/);\n        }\n      }\n      if (singleline) {\n        if (parserConf.singleLineStringErrors) {\n          outclass = ERRORCLASS;\n        } else {\n          state.tokenize = tokenBase;\n        }\n      }\n      return outclass;\n    };\n  }\n\n  function longComment(stream, state) {\n    while (!stream.eol()) {\n      stream.eatWhile(/[^#]/);\n      if (stream.match(\"###\")) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      stream.eatWhile(\"#\");\n    }\n    return \"comment\";\n  }\n\n  function indent(stream, state, type) {\n    type = type || \"coffee\";\n    var offset = 0, align = false, alignOffset = null;\n    for (var scope = state.scope; scope; scope = scope.prev) {\n      if (scope.type === \"coffee\" || scope.type == \"}\") {\n        offset = scope.offset + conf.indentUnit;\n        break;\n      }\n    }\n    if (type !== \"coffee\") {\n      align = null;\n      alignOffset = stream.column() + stream.current().length;\n    } else if (state.scope.align) {\n      state.scope.align = false;\n    }\n    state.scope = {\n      offset: offset,\n      type: type,\n      prev: state.scope,\n      align: align,\n      alignOffset: alignOffset\n    };\n  }\n\n  function dedent(stream, state) {\n    if (!state.scope.prev) return;\n    if (state.scope.type === \"coffee\") {\n      var _indent = stream.indentation();\n      var matched = false;\n      for (var scope = state.scope; scope; scope = scope.prev) {\n        if (_indent === scope.offset) {\n          matched = true;\n          break;\n        }\n      }\n      if (!matched) {\n        return true;\n      }\n      while (state.scope.prev && state.scope.offset !== _indent) {\n        state.scope = state.scope.prev;\n      }\n      return false;\n    } else {\n      state.scope = state.scope.prev;\n      return false;\n    }\n  }\n\n  function tokenLexer(stream, state) {\n    var style = state.tokenize(stream, state);\n    var current = stream.current();\n\n    // Handle scope changes.\n    if (current === \"return\") {\n      state.dedent = true;\n    }\n    if (((current === \"->\" || current === \"=>\") && stream.eol())\n        || style === \"indent\") {\n      indent(stream, state);\n    }\n    var delimiter_index = \"[({\".indexOf(current);\n    if (delimiter_index !== -1) {\n      indent(stream, state, \"])}\".slice(delimiter_index, delimiter_index+1));\n    }\n    if (indentKeywords.exec(current)){\n      indent(stream, state);\n    }\n    if (current == \"then\"){\n      dedent(stream, state);\n    }\n\n\n    if (style === \"dedent\") {\n      if (dedent(stream, state)) {\n        return ERRORCLASS;\n      }\n    }\n    delimiter_index = \"])}\".indexOf(current);\n    if (delimiter_index !== -1) {\n      while (state.scope.type == \"coffee\" && state.scope.prev)\n        state.scope = state.scope.prev;\n      if (state.scope.type == current)\n        state.scope = state.scope.prev;\n    }\n    if (state.dedent && stream.eol()) {\n      if (state.scope.type == \"coffee\" && state.scope.prev)\n        state.scope = state.scope.prev;\n      state.dedent = false;\n    }\n\n    return style;\n  }\n\n  var external = {\n    startState: function(basecolumn) {\n      return {\n        tokenize: tokenBase,\n        scope: {offset:basecolumn || 0, type:\"coffee\", prev: null, align: false},\n        prop: false,\n        dedent: 0\n      };\n    },\n\n    token: function(stream, state) {\n      var fillAlign = state.scope.align === null && state.scope;\n      if (fillAlign && stream.sol()) fillAlign.align = false;\n\n      var style = tokenLexer(stream, state);\n      if (style && style != \"comment\") {\n        if (fillAlign) fillAlign.align = true;\n        state.prop = style == \"punctuation\" && stream.current() == \".\"\n      }\n\n      return style;\n    },\n\n    indent: function(state, text) {\n      if (state.tokenize != tokenBase) return 0;\n      var scope = state.scope;\n      var closer = text && \"])}\".indexOf(text.charAt(0)) > -1;\n      if (closer) while (scope.type == \"coffee\" && scope.prev) scope = scope.prev;\n      var closes = closer && scope.type === text.charAt(0);\n      if (scope.align)\n        return scope.alignOffset - (closes ? 1 : 0);\n      else\n        return (closes ? scope.prev : scope).offset;\n    },\n\n    lineComment: \"#\",\n    fold: \"indent\"\n  };\n  return external;\n});\n\n// IANA registered media type\n// https://www.iana.org/assignments/media-types/\nCodeMirror.defineMIME(\"application/vnd.coffeescript\", \"coffeescript\");\n\nCodeMirror.defineMIME(\"text/x-coffeescript\", \"coffeescript\");\nCodeMirror.defineMIME(\"text/coffeescript\", \"coffeescript\");\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/css.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"css\", function(config, parserConfig) {\n  var inline = parserConfig.inline\n  if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode(\"text/css\");\n\n  var indentUnit = config.indentUnit,\n      tokenHooks = parserConfig.tokenHooks,\n      documentTypes = parserConfig.documentTypes || {},\n      mediaTypes = parserConfig.mediaTypes || {},\n      mediaFeatures = parserConfig.mediaFeatures || {},\n      mediaValueKeywords = parserConfig.mediaValueKeywords || {},\n      propertyKeywords = parserConfig.propertyKeywords || {},\n      nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},\n      fontProperties = parserConfig.fontProperties || {},\n      counterDescriptors = parserConfig.counterDescriptors || {},\n      colorKeywords = parserConfig.colorKeywords || {},\n      valueKeywords = parserConfig.valueKeywords || {},\n      allowNested = parserConfig.allowNested,\n      lineComment = parserConfig.lineComment,\n      supportsAtComponent = parserConfig.supportsAtComponent === true;\n\n  var type, override;\n  function ret(style, tp) { type = tp; return style; }\n\n  // Tokenizers\n\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (tokenHooks[ch]) {\n      var result = tokenHooks[ch](stream, state);\n      if (result !== false) return result;\n    }\n    if (ch == \"@\") {\n      stream.eatWhile(/[\\w\\\\\\-]/);\n      return ret(\"def\", stream.current());\n    } else if (ch == \"=\" || (ch == \"~\" || ch == \"|\") && stream.eat(\"=\")) {\n      return ret(null, \"compare\");\n    } else if (ch == \"\\\"\" || ch == \"'\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    } else if (ch == \"#\") {\n      stream.eatWhile(/[\\w\\\\\\-]/);\n      return ret(\"atom\", \"hash\");\n    } else if (ch == \"!\") {\n      stream.match(/^\\s*\\w*/);\n      return ret(\"keyword\", \"important\");\n    } else if (/\\d/.test(ch) || ch == \".\" && stream.eat(/\\d/)) {\n      stream.eatWhile(/[\\w.%]/);\n      return ret(\"number\", \"unit\");\n    } else if (ch === \"-\") {\n      if (/[\\d.]/.test(stream.peek())) {\n        stream.eatWhile(/[\\w.%]/);\n        return ret(\"number\", \"unit\");\n      } else if (stream.match(/^-[\\w\\\\\\-]*/)) {\n        stream.eatWhile(/[\\w\\\\\\-]/);\n        if (stream.match(/^\\s*:/, false))\n          return ret(\"variable-2\", \"variable-definition\");\n        return ret(\"variable-2\", \"variable\");\n      } else if (stream.match(/^\\w+-/)) {\n        return ret(\"meta\", \"meta\");\n      }\n    } else if (/[,+>*\\/]/.test(ch)) {\n      return ret(null, \"select-op\");\n    } else if (ch == \".\" && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {\n      return ret(\"qualifier\", \"qualifier\");\n    } else if (/[:;{}\\[\\]\\(\\)]/.test(ch)) {\n      return ret(null, ch);\n    } else if (stream.match(/[\\w-.]+(?=\\()/)) {\n      if (/^(url(-prefix)?|domain|regexp)$/.test(stream.current().toLowerCase())) {\n        state.tokenize = tokenParenthesized;\n      }\n      return ret(\"variable callee\", \"variable\");\n    } else if (/[\\w\\\\\\-]/.test(ch)) {\n      stream.eatWhile(/[\\w\\\\\\-]/);\n      return ret(\"property\", \"word\");\n    } else {\n      return ret(null, null);\n    }\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, ch;\n      while ((ch = stream.next()) != null) {\n        if (ch == quote && !escaped) {\n          if (quote == \")\") stream.backUp(1);\n          break;\n        }\n        escaped = !escaped && ch == \"\\\\\";\n      }\n      if (ch == quote || !escaped && quote != \")\") state.tokenize = null;\n      return ret(\"string\", \"string\");\n    };\n  }\n\n  function tokenParenthesized(stream, state) {\n    stream.next(); // Must be '('\n    if (!stream.match(/\\s*[\\\"\\')]/, false))\n      state.tokenize = tokenString(\")\");\n    else\n      state.tokenize = null;\n    return ret(null, \"(\");\n  }\n\n  // Context management\n\n  function Context(type, indent, prev) {\n    this.type = type;\n    this.indent = indent;\n    this.prev = prev;\n  }\n\n  function pushContext(state, stream, type, indent) {\n    state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);\n    return type;\n  }\n\n  function popContext(state) {\n    if (state.context.prev)\n      state.context = state.context.prev;\n    return state.context.type;\n  }\n\n  function pass(type, stream, state) {\n    return states[state.context.type](type, stream, state);\n  }\n  function popAndPass(type, stream, state, n) {\n    for (var i = n || 1; i > 0; i--)\n      state.context = state.context.prev;\n    return pass(type, stream, state);\n  }\n\n  // Parser\n\n  function wordAsValue(stream) {\n    var word = stream.current().toLowerCase();\n    if (valueKeywords.hasOwnProperty(word))\n      override = \"atom\";\n    else if (colorKeywords.hasOwnProperty(word))\n      override = \"keyword\";\n    else\n      override = \"variable\";\n  }\n\n  var states = {};\n\n  states.top = function(type, stream, state) {\n    if (type == \"{\") {\n      return pushContext(state, stream, \"block\");\n    } else if (type == \"}\" && state.context.prev) {\n      return popContext(state);\n    } else if (supportsAtComponent && /@component/i.test(type)) {\n      return pushContext(state, stream, \"atComponentBlock\");\n    } else if (/^@(-moz-)?document$/i.test(type)) {\n      return pushContext(state, stream, \"documentTypes\");\n    } else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {\n      return pushContext(state, stream, \"atBlock\");\n    } else if (/^@(font-face|counter-style)/i.test(type)) {\n      state.stateArg = type;\n      return \"restricted_atBlock_before\";\n    } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {\n      return \"keyframes\";\n    } else if (type && type.charAt(0) == \"@\") {\n      return pushContext(state, stream, \"at\");\n    } else if (type == \"hash\") {\n      override = \"builtin\";\n    } else if (type == \"word\") {\n      override = \"tag\";\n    } else if (type == \"variable-definition\") {\n      return \"maybeprop\";\n    } else if (type == \"interpolation\") {\n      return pushContext(state, stream, \"interpolation\");\n    } else if (type == \":\") {\n      return \"pseudo\";\n    } else if (allowNested && type == \"(\") {\n      return pushContext(state, stream, \"parens\");\n    }\n    return state.context.type;\n  };\n\n  states.block = function(type, stream, state) {\n    if (type == \"word\") {\n      var word = stream.current().toLowerCase();\n      if (propertyKeywords.hasOwnProperty(word)) {\n        override = \"property\";\n        return \"maybeprop\";\n      } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {\n        override = \"string-2\";\n        return \"maybeprop\";\n      } else if (allowNested) {\n        override = stream.match(/^\\s*:(?:\\s|$)/, false) ? \"property\" : \"tag\";\n        return \"block\";\n      } else {\n        override += \" error\";\n        return \"maybeprop\";\n      }\n    } else if (type == \"meta\") {\n      return \"block\";\n    } else if (!allowNested && (type == \"hash\" || type == \"qualifier\")) {\n      override = \"error\";\n      return \"block\";\n    } else {\n      return states.top(type, stream, state);\n    }\n  };\n\n  states.maybeprop = function(type, stream, state) {\n    if (type == \":\") return pushContext(state, stream, \"prop\");\n    return pass(type, stream, state);\n  };\n\n  states.prop = function(type, stream, state) {\n    if (type == \";\") return popContext(state);\n    if (type == \"{\" && allowNested) return pushContext(state, stream, \"propBlock\");\n    if (type == \"}\" || type == \"{\") return popAndPass(type, stream, state);\n    if (type == \"(\") return pushContext(state, stream, \"parens\");\n\n    if (type == \"hash\" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {\n      override += \" error\";\n    } else if (type == \"word\") {\n      wordAsValue(stream);\n    } else if (type == \"interpolation\") {\n      return pushContext(state, stream, \"interpolation\");\n    }\n    return \"prop\";\n  };\n\n  states.propBlock = function(type, _stream, state) {\n    if (type == \"}\") return popContext(state);\n    if (type == \"word\") { override = \"property\"; return \"maybeprop\"; }\n    return state.context.type;\n  };\n\n  states.parens = function(type, stream, state) {\n    if (type == \"{\" || type == \"}\") return popAndPass(type, stream, state);\n    if (type == \")\") return popContext(state);\n    if (type == \"(\") return pushContext(state, stream, \"parens\");\n    if (type == \"interpolation\") return pushContext(state, stream, \"interpolation\");\n    if (type == \"word\") wordAsValue(stream);\n    return \"parens\";\n  };\n\n  states.pseudo = function(type, stream, state) {\n    if (type == \"meta\") return \"pseudo\";\n\n    if (type == \"word\") {\n      override = \"variable-3\";\n      return state.context.type;\n    }\n    return pass(type, stream, state);\n  };\n\n  states.documentTypes = function(type, stream, state) {\n    if (type == \"word\" && documentTypes.hasOwnProperty(stream.current())) {\n      override = \"tag\";\n      return state.context.type;\n    } else {\n      return states.atBlock(type, stream, state);\n    }\n  };\n\n  states.atBlock = function(type, stream, state) {\n    if (type == \"(\") return pushContext(state, stream, \"atBlock_parens\");\n    if (type == \"}\" || type == \";\") return popAndPass(type, stream, state);\n    if (type == \"{\") return popContext(state) && pushContext(state, stream, allowNested ? \"block\" : \"top\");\n\n    if (type == \"interpolation\") return pushContext(state, stream, \"interpolation\");\n\n    if (type == \"word\") {\n      var word = stream.current().toLowerCase();\n      if (word == \"only\" || word == \"not\" || word == \"and\" || word == \"or\")\n        override = \"keyword\";\n      else if (mediaTypes.hasOwnProperty(word))\n        override = \"attribute\";\n      else if (mediaFeatures.hasOwnProperty(word))\n        override = \"property\";\n      else if (mediaValueKeywords.hasOwnProperty(word))\n        override = \"keyword\";\n      else if (propertyKeywords.hasOwnProperty(word))\n        override = \"property\";\n      else if (nonStandardPropertyKeywords.hasOwnProperty(word))\n        override = \"string-2\";\n      else if (valueKeywords.hasOwnProperty(word))\n        override = \"atom\";\n      else if (colorKeywords.hasOwnProperty(word))\n        override = \"keyword\";\n      else\n        override = \"error\";\n    }\n    return state.context.type;\n  };\n\n  states.atComponentBlock = function(type, stream, state) {\n    if (type == \"}\")\n      return popAndPass(type, stream, state);\n    if (type == \"{\")\n      return popContext(state) && pushContext(state, stream, allowNested ? \"block\" : \"top\", false);\n    if (type == \"word\")\n      override = \"error\";\n    return state.context.type;\n  };\n\n  states.atBlock_parens = function(type, stream, state) {\n    if (type == \")\") return popContext(state);\n    if (type == \"{\" || type == \"}\") return popAndPass(type, stream, state, 2);\n    return states.atBlock(type, stream, state);\n  };\n\n  states.restricted_atBlock_before = function(type, stream, state) {\n    if (type == \"{\")\n      return pushContext(state, stream, \"restricted_atBlock\");\n    if (type == \"word\" && state.stateArg == \"@counter-style\") {\n      override = \"variable\";\n      return \"restricted_atBlock_before\";\n    }\n    return pass(type, stream, state);\n  };\n\n  states.restricted_atBlock = function(type, stream, state) {\n    if (type == \"}\") {\n      state.stateArg = null;\n      return popContext(state);\n    }\n    if (type == \"word\") {\n      if ((state.stateArg == \"@font-face\" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||\n          (state.stateArg == \"@counter-style\" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))\n        override = \"error\";\n      else\n        override = \"property\";\n      return \"maybeprop\";\n    }\n    return \"restricted_atBlock\";\n  };\n\n  states.keyframes = function(type, stream, state) {\n    if (type == \"word\") { override = \"variable\"; return \"keyframes\"; }\n    if (type == \"{\") return pushContext(state, stream, \"top\");\n    return pass(type, stream, state);\n  };\n\n  states.at = function(type, stream, state) {\n    if (type == \";\") return popContext(state);\n    if (type == \"{\" || type == \"}\") return popAndPass(type, stream, state);\n    if (type == \"word\") override = \"tag\";\n    else if (type == \"hash\") override = \"builtin\";\n    return \"at\";\n  };\n\n  states.interpolation = function(type, stream, state) {\n    if (type == \"}\") return popContext(state);\n    if (type == \"{\" || type == \";\") return popAndPass(type, stream, state);\n    if (type == \"word\") override = \"variable\";\n    else if (type != \"variable\" && type != \"(\" && type != \")\") override = \"error\";\n    return \"interpolation\";\n  };\n\n  return {\n    startState: function(base) {\n      return {tokenize: null,\n              state: inline ? \"block\" : \"top\",\n              stateArg: null,\n              context: new Context(inline ? \"block\" : \"top\", base || 0, null)};\n    },\n\n    token: function(stream, state) {\n      if (!state.tokenize && stream.eatSpace()) return null;\n      var style = (state.tokenize || tokenBase)(stream, state);\n      if (style && typeof style == \"object\") {\n        type = style[1];\n        style = style[0];\n      }\n      override = style;\n      if (type != \"comment\")\n        state.state = states[state.state](type, stream, state);\n      return override;\n    },\n\n    indent: function(state, textAfter) {\n      var cx = state.context, ch = textAfter && textAfter.charAt(0);\n      var indent = cx.indent;\n      if (cx.type == \"prop\" && (ch == \"}\" || ch == \")\")) cx = cx.prev;\n      if (cx.prev) {\n        if (ch == \"}\" && (cx.type == \"block\" || cx.type == \"top\" ||\n                          cx.type == \"interpolation\" || cx.type == \"restricted_atBlock\")) {\n          // Resume indentation from parent context.\n          cx = cx.prev;\n          indent = cx.indent;\n        } else if (ch == \")\" && (cx.type == \"parens\" || cx.type == \"atBlock_parens\") ||\n            ch == \"{\" && (cx.type == \"at\" || cx.type == \"atBlock\")) {\n          // Dedent relative to current context.\n          indent = Math.max(0, cx.indent - indentUnit);\n        }\n      }\n      return indent;\n    },\n\n    electricChars: \"}\",\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    blockCommentContinue: \" * \",\n    lineComment: lineComment,\n    fold: \"brace\"\n  };\n});\n\n  function keySet(array) {\n    var keys = {};\n    for (var i = 0; i < array.length; ++i) {\n      keys[array[i].toLowerCase()] = true;\n    }\n    return keys;\n  }\n\n  var documentTypes_ = [\n    \"domain\", \"regexp\", \"url\", \"url-prefix\"\n  ], documentTypes = keySet(documentTypes_);\n\n  var mediaTypes_ = [\n    \"all\", \"aural\", \"braille\", \"handheld\", \"print\", \"projection\", \"screen\",\n    \"tty\", \"tv\", \"embossed\"\n  ], mediaTypes = keySet(mediaTypes_);\n\n  var mediaFeatures_ = [\n    \"width\", \"min-width\", \"max-width\", \"height\", \"min-height\", \"max-height\",\n    \"device-width\", \"min-device-width\", \"max-device-width\", \"device-height\",\n    \"min-device-height\", \"max-device-height\", \"aspect-ratio\",\n    \"min-aspect-ratio\", \"max-aspect-ratio\", \"device-aspect-ratio\",\n    \"min-device-aspect-ratio\", \"max-device-aspect-ratio\", \"color\", \"min-color\",\n    \"max-color\", \"color-index\", \"min-color-index\", \"max-color-index\",\n    \"monochrome\", \"min-monochrome\", \"max-monochrome\", \"resolution\",\n    \"min-resolution\", \"max-resolution\", \"scan\", \"grid\", \"orientation\",\n    \"device-pixel-ratio\", \"min-device-pixel-ratio\", \"max-device-pixel-ratio\",\n    \"pointer\", \"any-pointer\", \"hover\", \"any-hover\"\n  ], mediaFeatures = keySet(mediaFeatures_);\n\n  var mediaValueKeywords_ = [\n    \"landscape\", \"portrait\", \"none\", \"coarse\", \"fine\", \"on-demand\", \"hover\",\n    \"interlace\", \"progressive\"\n  ], mediaValueKeywords = keySet(mediaValueKeywords_);\n\n  var propertyKeywords_ = [\n    \"align-content\", \"align-items\", \"align-self\", \"alignment-adjust\",\n    \"alignment-baseline\", \"anchor-point\", \"animation\", \"animation-delay\",\n    \"animation-direction\", \"animation-duration\", \"animation-fill-mode\",\n    \"animation-iteration-count\", \"animation-name\", \"animation-play-state\",\n    \"animation-timing-function\", \"appearance\", \"azimuth\", \"backdrop-filter\",\n    \"backface-visibility\", \"background\", \"background-attachment\",\n    \"background-blend-mode\", \"background-clip\", \"background-color\",\n    \"background-image\", \"background-origin\", \"background-position\",\n    \"background-position-x\", \"background-position-y\", \"background-repeat\",\n    \"background-size\", \"baseline-shift\", \"binding\", \"bleed\", \"block-size\",\n    \"bookmark-label\", \"bookmark-level\", \"bookmark-state\", \"bookmark-target\",\n    \"border\", \"border-bottom\", \"border-bottom-color\", \"border-bottom-left-radius\",\n    \"border-bottom-right-radius\", \"border-bottom-style\", \"border-bottom-width\",\n    \"border-collapse\", \"border-color\", \"border-image\", \"border-image-outset\",\n    \"border-image-repeat\", \"border-image-slice\", \"border-image-source\",\n    \"border-image-width\", \"border-left\", \"border-left-color\", \"border-left-style\",\n    \"border-left-width\", \"border-radius\", \"border-right\", \"border-right-color\",\n    \"border-right-style\", \"border-right-width\", \"border-spacing\", \"border-style\",\n    \"border-top\", \"border-top-color\", \"border-top-left-radius\",\n    \"border-top-right-radius\", \"border-top-style\", \"border-top-width\",\n    \"border-width\", \"bottom\", \"box-decoration-break\", \"box-shadow\", \"box-sizing\",\n    \"break-after\", \"break-before\", \"break-inside\", \"caption-side\", \"caret-color\",\n    \"clear\", \"clip\", \"color\", \"color-profile\", \"column-count\", \"column-fill\",\n    \"column-gap\", \"column-rule\", \"column-rule-color\", \"column-rule-style\",\n    \"column-rule-width\", \"column-span\", \"column-width\", \"columns\", \"contain\",\n    \"content\", \"counter-increment\", \"counter-reset\", \"crop\", \"cue\", \"cue-after\",\n    \"cue-before\", \"cursor\", \"direction\", \"display\", \"dominant-baseline\",\n    \"drop-initial-after-adjust\", \"drop-initial-after-align\",\n    \"drop-initial-before-adjust\", \"drop-initial-before-align\", \"drop-initial-size\",\n    \"drop-initial-value\", \"elevation\", \"empty-cells\", \"fit\", \"fit-position\",\n    \"flex\", \"flex-basis\", \"flex-direction\", \"flex-flow\", \"flex-grow\",\n    \"flex-shrink\", \"flex-wrap\", \"float\", \"float-offset\", \"flow-from\", \"flow-into\",\n    \"font\", \"font-family\", \"font-feature-settings\", \"font-kerning\",\n    \"font-language-override\", \"font-optical-sizing\", \"font-size\",\n    \"font-size-adjust\", \"font-stretch\", \"font-style\", \"font-synthesis\",\n    \"font-variant\", \"font-variant-alternates\", \"font-variant-caps\",\n    \"font-variant-east-asian\", \"font-variant-ligatures\", \"font-variant-numeric\",\n    \"font-variant-position\", \"font-variation-settings\", \"font-weight\", \"gap\",\n    \"grid\", \"grid-area\", \"grid-auto-columns\", \"grid-auto-flow\", \"grid-auto-rows\",\n    \"grid-column\", \"grid-column-end\", \"grid-column-gap\", \"grid-column-start\",\n    \"grid-gap\", \"grid-row\", \"grid-row-end\", \"grid-row-gap\", \"grid-row-start\",\n    \"grid-template\", \"grid-template-areas\", \"grid-template-columns\",\n    \"grid-template-rows\", \"hanging-punctuation\", \"height\", \"hyphens\", \"icon\",\n    \"image-orientation\", \"image-rendering\", \"image-resolution\", \"inline-box-align\",\n    \"inset\", \"inset-block\", \"inset-block-end\", \"inset-block-start\", \"inset-inline\",\n    \"inset-inline-end\", \"inset-inline-start\", \"isolation\", \"justify-content\",\n    \"justify-items\", \"justify-self\", \"left\", \"letter-spacing\", \"line-break\",\n    \"line-height\", \"line-height-step\", \"line-stacking\", \"line-stacking-ruby\",\n    \"line-stacking-shift\", \"line-stacking-strategy\", \"list-style\",\n    \"list-style-image\", \"list-style-position\", \"list-style-type\", \"margin\",\n    \"margin-bottom\", \"margin-left\", \"margin-right\", \"margin-top\", \"marks\",\n    \"marquee-direction\", \"marquee-loop\", \"marquee-play-count\", \"marquee-speed\",\n    \"marquee-style\", \"max-block-size\", \"max-height\", \"max-inline-size\",\n    \"max-width\", \"min-block-size\", \"min-height\", \"min-inline-size\", \"min-width\",\n    \"mix-blend-mode\", \"move-to\", \"nav-down\", \"nav-index\", \"nav-left\", \"nav-right\",\n    \"nav-up\", \"object-fit\", \"object-position\", \"offset\", \"offset-anchor\",\n    \"offset-distance\", \"offset-path\", \"offset-position\", \"offset-rotate\",\n    \"opacity\", \"order\", \"orphans\", \"outline\", \"outline-color\", \"outline-offset\",\n    \"outline-style\", \"outline-width\", \"overflow\", \"overflow-style\",\n    \"overflow-wrap\", \"overflow-x\", \"overflow-y\", \"padding\", \"padding-bottom\",\n    \"padding-left\", \"padding-right\", \"padding-top\", \"page\", \"page-break-after\",\n    \"page-break-before\", \"page-break-inside\", \"page-policy\", \"pause\",\n    \"pause-after\", \"pause-before\", \"perspective\", \"perspective-origin\", \"pitch\",\n    \"pitch-range\", \"place-content\", \"place-items\", \"place-self\", \"play-during\",\n    \"position\", \"presentation-level\", \"punctuation-trim\", \"quotes\",\n    \"region-break-after\", \"region-break-before\", \"region-break-inside\",\n    \"region-fragment\", \"rendering-intent\", \"resize\", \"rest\", \"rest-after\",\n    \"rest-before\", \"richness\", \"right\", \"rotate\", \"rotation\", \"rotation-point\",\n    \"row-gap\", \"ruby-align\", \"ruby-overhang\", \"ruby-position\", \"ruby-span\",\n    \"scale\", \"scroll-behavior\", \"scroll-margin\", \"scroll-margin-block\",\n    \"scroll-margin-block-end\", \"scroll-margin-block-start\", \"scroll-margin-bottom\",\n    \"scroll-margin-inline\", \"scroll-margin-inline-end\",\n    \"scroll-margin-inline-start\", \"scroll-margin-left\", \"scroll-margin-right\",\n    \"scroll-margin-top\", \"scroll-padding\", \"scroll-padding-block\",\n    \"scroll-padding-block-end\", \"scroll-padding-block-start\",\n    \"scroll-padding-bottom\", \"scroll-padding-inline\", \"scroll-padding-inline-end\",\n    \"scroll-padding-inline-start\", \"scroll-padding-left\", \"scroll-padding-right\",\n    \"scroll-padding-top\", \"scroll-snap-align\", \"scroll-snap-type\",\n    \"shape-image-threshold\", \"shape-inside\", \"shape-margin\", \"shape-outside\",\n    \"size\", \"speak\", \"speak-as\", \"speak-header\", \"speak-numeral\",\n    \"speak-punctuation\", \"speech-rate\", \"stress\", \"string-set\", \"tab-size\",\n    \"table-layout\", \"target\", \"target-name\", \"target-new\", \"target-position\",\n    \"text-align\", \"text-align-last\", \"text-combine-upright\", \"text-decoration\",\n    \"text-decoration-color\", \"text-decoration-line\", \"text-decoration-skip\",\n    \"text-decoration-skip-ink\", \"text-decoration-style\", \"text-emphasis\",\n    \"text-emphasis-color\", \"text-emphasis-position\", \"text-emphasis-style\",\n    \"text-height\", \"text-indent\", \"text-justify\", \"text-orientation\",\n    \"text-outline\", \"text-overflow\", \"text-rendering\", \"text-shadow\",\n    \"text-size-adjust\", \"text-space-collapse\", \"text-transform\",\n    \"text-underline-position\", \"text-wrap\", \"top\", \"transform\", \"transform-origin\",\n    \"transform-style\", \"transition\", \"transition-delay\", \"transition-duration\",\n    \"transition-property\", \"transition-timing-function\", \"translate\",\n    \"unicode-bidi\", \"user-select\", \"vertical-align\", \"visibility\", \"voice-balance\",\n    \"voice-duration\", \"voice-family\", \"voice-pitch\", \"voice-range\", \"voice-rate\",\n    \"voice-stress\", \"voice-volume\", \"volume\", \"white-space\", \"widows\", \"width\",\n    \"will-change\", \"word-break\", \"word-spacing\", \"word-wrap\", \"writing-mode\", \"z-index\",\n    // SVG-specific\n    \"clip-path\", \"clip-rule\", \"mask\", \"enable-background\", \"filter\", \"flood-color\",\n    \"flood-opacity\", \"lighting-color\", \"stop-color\", \"stop-opacity\", \"pointer-events\",\n    \"color-interpolation\", \"color-interpolation-filters\",\n    \"color-rendering\", \"fill\", \"fill-opacity\", \"fill-rule\", \"image-rendering\",\n    \"marker\", \"marker-end\", \"marker-mid\", \"marker-start\", \"shape-rendering\", \"stroke\",\n    \"stroke-dasharray\", \"stroke-dashoffset\", \"stroke-linecap\", \"stroke-linejoin\",\n    \"stroke-miterlimit\", \"stroke-opacity\", \"stroke-width\", \"text-rendering\",\n    \"baseline-shift\", \"dominant-baseline\", \"glyph-orientation-horizontal\",\n    \"glyph-orientation-vertical\", \"text-anchor\", \"writing-mode\"\n  ], propertyKeywords = keySet(propertyKeywords_);\n\n  var nonStandardPropertyKeywords_ = [\n    \"border-block\", \"border-block-color\", \"border-block-end\",\n    \"border-block-end-color\", \"border-block-end-style\", \"border-block-end-width\",\n    \"border-block-start\", \"border-block-start-color\", \"border-block-start-style\",\n    \"border-block-start-width\", \"border-block-style\", \"border-block-width\",\n    \"border-inline\", \"border-inline-color\", \"border-inline-end\",\n    \"border-inline-end-color\", \"border-inline-end-style\",\n    \"border-inline-end-width\", \"border-inline-start\", \"border-inline-start-color\",\n    \"border-inline-start-style\", \"border-inline-start-width\",\n    \"border-inline-style\", \"border-inline-width\", \"margin-block\",\n    \"margin-block-end\", \"margin-block-start\", \"margin-inline\", \"margin-inline-end\",\n    \"margin-inline-start\", \"padding-block\", \"padding-block-end\",\n    \"padding-block-start\", \"padding-inline\", \"padding-inline-end\",\n    \"padding-inline-start\", \"scroll-snap-stop\", \"scrollbar-3d-light-color\",\n    \"scrollbar-arrow-color\", \"scrollbar-base-color\", \"scrollbar-dark-shadow-color\",\n    \"scrollbar-face-color\", \"scrollbar-highlight-color\", \"scrollbar-shadow-color\",\n    \"scrollbar-track-color\", \"searchfield-cancel-button\", \"searchfield-decoration\",\n    \"searchfield-results-button\", \"searchfield-results-decoration\", \"shape-inside\", \"zoom\"\n  ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);\n\n  var fontProperties_ = [\n    \"font-display\", \"font-family\", \"src\", \"unicode-range\", \"font-variant\",\n     \"font-feature-settings\", \"font-stretch\", \"font-weight\", \"font-style\"\n  ], fontProperties = keySet(fontProperties_);\n\n  var counterDescriptors_ = [\n    \"additive-symbols\", \"fallback\", \"negative\", \"pad\", \"prefix\", \"range\",\n    \"speak-as\", \"suffix\", \"symbols\", \"system\"\n  ], counterDescriptors = keySet(counterDescriptors_);\n\n  var colorKeywords_ = [\n    \"aliceblue\", \"antiquewhite\", \"aqua\", \"aquamarine\", \"azure\", \"beige\",\n    \"bisque\", \"black\", \"blanchedalmond\", \"blue\", \"blueviolet\", \"brown\",\n    \"burlywood\", \"cadetblue\", \"chartreuse\", \"chocolate\", \"coral\", \"cornflowerblue\",\n    \"cornsilk\", \"crimson\", \"cyan\", \"darkblue\", \"darkcyan\", \"darkgoldenrod\",\n    \"darkgray\", \"darkgreen\", \"darkkhaki\", \"darkmagenta\", \"darkolivegreen\",\n    \"darkorange\", \"darkorchid\", \"darkred\", \"darksalmon\", \"darkseagreen\",\n    \"darkslateblue\", \"darkslategray\", \"darkturquoise\", \"darkviolet\",\n    \"deeppink\", \"deepskyblue\", \"dimgray\", \"dodgerblue\", \"firebrick\",\n    \"floralwhite\", \"forestgreen\", \"fuchsia\", \"gainsboro\", \"ghostwhite\",\n    \"gold\", \"goldenrod\", \"gray\", \"grey\", \"green\", \"greenyellow\", \"honeydew\",\n    \"hotpink\", \"indianred\", \"indigo\", \"ivory\", \"khaki\", \"lavender\",\n    \"lavenderblush\", \"lawngreen\", \"lemonchiffon\", \"lightblue\", \"lightcoral\",\n    \"lightcyan\", \"lightgoldenrodyellow\", \"lightgray\", \"lightgreen\", \"lightpink\",\n    \"lightsalmon\", \"lightseagreen\", \"lightskyblue\", \"lightslategray\",\n    \"lightsteelblue\", \"lightyellow\", \"lime\", \"limegreen\", \"linen\", \"magenta\",\n    \"maroon\", \"mediumaquamarine\", \"mediumblue\", \"mediumorchid\", \"mediumpurple\",\n    \"mediumseagreen\", \"mediumslateblue\", \"mediumspringgreen\", \"mediumturquoise\",\n    \"mediumvioletred\", \"midnightblue\", \"mintcream\", \"mistyrose\", \"moccasin\",\n    \"navajowhite\", \"navy\", \"oldlace\", \"olive\", \"olivedrab\", \"orange\", \"orangered\",\n    \"orchid\", \"palegoldenrod\", \"palegreen\", \"paleturquoise\", \"palevioletred\",\n    \"papayawhip\", \"peachpuff\", \"peru\", \"pink\", \"plum\", \"powderblue\",\n    \"purple\", \"rebeccapurple\", \"red\", \"rosybrown\", \"royalblue\", \"saddlebrown\",\n    \"salmon\", \"sandybrown\", \"seagreen\", \"seashell\", \"sienna\", \"silver\", \"skyblue\",\n    \"slateblue\", \"slategray\", \"snow\", \"springgreen\", \"steelblue\", \"tan\",\n    \"teal\", \"thistle\", \"tomato\", \"turquoise\", \"violet\", \"wheat\", \"white\",\n    \"whitesmoke\", \"yellow\", \"yellowgreen\"\n  ], colorKeywords = keySet(colorKeywords_);\n\n  var valueKeywords_ = [\n    \"above\", \"absolute\", \"activeborder\", \"additive\", \"activecaption\", \"afar\",\n    \"after-white-space\", \"ahead\", \"alias\", \"all\", \"all-scroll\", \"alphabetic\", \"alternate\",\n    \"always\", \"amharic\", \"amharic-abegede\", \"antialiased\", \"appworkspace\",\n    \"arabic-indic\", \"armenian\", \"asterisks\", \"attr\", \"auto\", \"auto-flow\", \"avoid\", \"avoid-column\", \"avoid-page\",\n    \"avoid-region\", \"background\", \"backwards\", \"baseline\", \"below\", \"bidi-override\", \"binary\",\n    \"bengali\", \"blink\", \"block\", \"block-axis\", \"bold\", \"bolder\", \"border\", \"border-box\",\n    \"both\", \"bottom\", \"break\", \"break-all\", \"break-word\", \"bullets\", \"button\", \"button-bevel\",\n    \"buttonface\", \"buttonhighlight\", \"buttonshadow\", \"buttontext\", \"calc\", \"cambodian\",\n    \"capitalize\", \"caps-lock-indicator\", \"caption\", \"captiontext\", \"caret\",\n    \"cell\", \"center\", \"checkbox\", \"circle\", \"cjk-decimal\", \"cjk-earthly-branch\",\n    \"cjk-heavenly-stem\", \"cjk-ideographic\", \"clear\", \"clip\", \"close-quote\",\n    \"col-resize\", \"collapse\", \"color\", \"color-burn\", \"color-dodge\", \"column\", \"column-reverse\",\n    \"compact\", \"condensed\", \"contain\", \"content\", \"contents\",\n    \"content-box\", \"context-menu\", \"continuous\", \"copy\", \"counter\", \"counters\", \"cover\", \"crop\",\n    \"cross\", \"crosshair\", \"currentcolor\", \"cursive\", \"cyclic\", \"darken\", \"dashed\", \"decimal\",\n    \"decimal-leading-zero\", \"default\", \"default-button\", \"dense\", \"destination-atop\",\n    \"destination-in\", \"destination-out\", \"destination-over\", \"devanagari\", \"difference\",\n    \"disc\", \"discard\", \"disclosure-closed\", \"disclosure-open\", \"document\",\n    \"dot-dash\", \"dot-dot-dash\",\n    \"dotted\", \"double\", \"down\", \"e-resize\", \"ease\", \"ease-in\", \"ease-in-out\", \"ease-out\",\n    \"element\", \"ellipse\", \"ellipsis\", \"embed\", \"end\", \"ethiopic\", \"ethiopic-abegede\",\n    \"ethiopic-abegede-am-et\", \"ethiopic-abegede-gez\", \"ethiopic-abegede-ti-er\",\n    \"ethiopic-abegede-ti-et\", \"ethiopic-halehame-aa-er\",\n    \"ethiopic-halehame-aa-et\", \"ethiopic-halehame-am-et\",\n    \"ethiopic-halehame-gez\", \"ethiopic-halehame-om-et\",\n    \"ethiopic-halehame-sid-et\", \"ethiopic-halehame-so-et\",\n    \"ethiopic-halehame-ti-er\", \"ethiopic-halehame-ti-et\", \"ethiopic-halehame-tig\",\n    \"ethiopic-numeric\", \"ew-resize\", \"exclusion\", \"expanded\", \"extends\", \"extra-condensed\",\n    \"extra-expanded\", \"fantasy\", \"fast\", \"fill\", \"fixed\", \"flat\", \"flex\", \"flex-end\", \"flex-start\", \"footnotes\",\n    \"forwards\", \"from\", \"geometricPrecision\", \"georgian\", \"graytext\", \"grid\", \"groove\",\n    \"gujarati\", \"gurmukhi\", \"hand\", \"hangul\", \"hangul-consonant\", \"hard-light\", \"hebrew\",\n    \"help\", \"hidden\", \"hide\", \"higher\", \"highlight\", \"highlighttext\",\n    \"hiragana\", \"hiragana-iroha\", \"horizontal\", \"hsl\", \"hsla\", \"hue\", \"icon\", \"ignore\",\n    \"inactiveborder\", \"inactivecaption\", \"inactivecaptiontext\", \"infinite\",\n    \"infobackground\", \"infotext\", \"inherit\", \"initial\", \"inline\", \"inline-axis\",\n    \"inline-block\", \"inline-flex\", \"inline-grid\", \"inline-table\", \"inset\", \"inside\", \"intrinsic\", \"invert\",\n    \"italic\", \"japanese-formal\", \"japanese-informal\", \"justify\", \"kannada\",\n    \"katakana\", \"katakana-iroha\", \"keep-all\", \"khmer\",\n    \"korean-hangul-formal\", \"korean-hanja-formal\", \"korean-hanja-informal\",\n    \"landscape\", \"lao\", \"large\", \"larger\", \"left\", \"level\", \"lighter\", \"lighten\",\n    \"line-through\", \"linear\", \"linear-gradient\", \"lines\", \"list-item\", \"listbox\", \"listitem\",\n    \"local\", \"logical\", \"loud\", \"lower\", \"lower-alpha\", \"lower-armenian\",\n    \"lower-greek\", \"lower-hexadecimal\", \"lower-latin\", \"lower-norwegian\",\n    \"lower-roman\", \"lowercase\", \"ltr\", \"luminosity\", \"malayalam\", \"match\", \"matrix\", \"matrix3d\",\n    \"media-controls-background\", \"media-current-time-display\",\n    \"media-fullscreen-button\", \"media-mute-button\", \"media-play-button\",\n    \"media-return-to-realtime-button\", \"media-rewind-button\",\n    \"media-seek-back-button\", \"media-seek-forward-button\", \"media-slider\",\n    \"media-sliderthumb\", \"media-time-remaining-display\", \"media-volume-slider\",\n    \"media-volume-slider-container\", \"media-volume-sliderthumb\", \"medium\",\n    \"menu\", \"menulist\", \"menulist-button\", \"menulist-text\",\n    \"menulist-textfield\", \"menutext\", \"message-box\", \"middle\", \"min-intrinsic\",\n    \"mix\", \"mongolian\", \"monospace\", \"move\", \"multiple\", \"multiply\", \"myanmar\", \"n-resize\",\n    \"narrower\", \"ne-resize\", \"nesw-resize\", \"no-close-quote\", \"no-drop\",\n    \"no-open-quote\", \"no-repeat\", \"none\", \"normal\", \"not-allowed\", \"nowrap\",\n    \"ns-resize\", \"numbers\", \"numeric\", \"nw-resize\", \"nwse-resize\", \"oblique\", \"octal\", \"opacity\", \"open-quote\",\n    \"optimizeLegibility\", \"optimizeSpeed\", \"oriya\", \"oromo\", \"outset\",\n    \"outside\", \"outside-shape\", \"overlay\", \"overline\", \"padding\", \"padding-box\",\n    \"painted\", \"page\", \"paused\", \"persian\", \"perspective\", \"plus-darker\", \"plus-lighter\",\n    \"pointer\", \"polygon\", \"portrait\", \"pre\", \"pre-line\", \"pre-wrap\", \"preserve-3d\",\n    \"progress\", \"push-button\", \"radial-gradient\", \"radio\", \"read-only\",\n    \"read-write\", \"read-write-plaintext-only\", \"rectangle\", \"region\",\n    \"relative\", \"repeat\", \"repeating-linear-gradient\",\n    \"repeating-radial-gradient\", \"repeat-x\", \"repeat-y\", \"reset\", \"reverse\",\n    \"rgb\", \"rgba\", \"ridge\", \"right\", \"rotate\", \"rotate3d\", \"rotateX\", \"rotateY\",\n    \"rotateZ\", \"round\", \"row\", \"row-resize\", \"row-reverse\", \"rtl\", \"run-in\", \"running\",\n    \"s-resize\", \"sans-serif\", \"saturation\", \"scale\", \"scale3d\", \"scaleX\", \"scaleY\", \"scaleZ\", \"screen\",\n    \"scroll\", \"scrollbar\", \"scroll-position\", \"se-resize\", \"searchfield\",\n    \"searchfield-cancel-button\", \"searchfield-decoration\",\n    \"searchfield-results-button\", \"searchfield-results-decoration\", \"self-start\", \"self-end\",\n    \"semi-condensed\", \"semi-expanded\", \"separate\", \"serif\", \"show\", \"sidama\",\n    \"simp-chinese-formal\", \"simp-chinese-informal\", \"single\",\n    \"skew\", \"skewX\", \"skewY\", \"skip-white-space\", \"slide\", \"slider-horizontal\",\n    \"slider-vertical\", \"sliderthumb-horizontal\", \"sliderthumb-vertical\", \"slow\",\n    \"small\", \"small-caps\", \"small-caption\", \"smaller\", \"soft-light\", \"solid\", \"somali\",\n    \"source-atop\", \"source-in\", \"source-out\", \"source-over\", \"space\", \"space-around\", \"space-between\", \"space-evenly\", \"spell-out\", \"square\",\n    \"square-button\", \"start\", \"static\", \"status-bar\", \"stretch\", \"stroke\", \"sub\",\n    \"subpixel-antialiased\", \"super\", \"sw-resize\", \"symbolic\", \"symbols\", \"system-ui\", \"table\",\n    \"table-caption\", \"table-cell\", \"table-column\", \"table-column-group\",\n    \"table-footer-group\", \"table-header-group\", \"table-row\", \"table-row-group\",\n    \"tamil\",\n    \"telugu\", \"text\", \"text-bottom\", \"text-top\", \"textarea\", \"textfield\", \"thai\",\n    \"thick\", \"thin\", \"threeddarkshadow\", \"threedface\", \"threedhighlight\",\n    \"threedlightshadow\", \"threedshadow\", \"tibetan\", \"tigre\", \"tigrinya-er\",\n    \"tigrinya-er-abegede\", \"tigrinya-et\", \"tigrinya-et-abegede\", \"to\", \"top\",\n    \"trad-chinese-formal\", \"trad-chinese-informal\", \"transform\",\n    \"translate\", \"translate3d\", \"translateX\", \"translateY\", \"translateZ\",\n    \"transparent\", \"ultra-condensed\", \"ultra-expanded\", \"underline\", \"unset\", \"up\",\n    \"upper-alpha\", \"upper-armenian\", \"upper-greek\", \"upper-hexadecimal\",\n    \"upper-latin\", \"upper-norwegian\", \"upper-roman\", \"uppercase\", \"urdu\", \"url\",\n    \"var\", \"vertical\", \"vertical-text\", \"visible\", \"visibleFill\", \"visiblePainted\",\n    \"visibleStroke\", \"visual\", \"w-resize\", \"wait\", \"wave\", \"wider\",\n    \"window\", \"windowframe\", \"windowtext\", \"words\", \"wrap\", \"wrap-reverse\", \"x-large\", \"x-small\", \"xor\",\n    \"xx-large\", \"xx-small\"\n  ], valueKeywords = keySet(valueKeywords_);\n\n  var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)\n    .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)\n    .concat(valueKeywords_);\n  CodeMirror.registerHelper(\"hintWords\", \"css\", allWords);\n\n  function tokenCComment(stream, state) {\n    var maybeEnd = false, ch;\n    while ((ch = stream.next()) != null) {\n      if (maybeEnd && ch == \"/\") {\n        state.tokenize = null;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return [\"comment\", \"comment\"];\n  }\n\n  CodeMirror.defineMIME(\"text/css\", {\n    documentTypes: documentTypes,\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    mediaValueKeywords: mediaValueKeywords,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    fontProperties: fontProperties,\n    counterDescriptors: counterDescriptors,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (!stream.eat(\"*\")) return false;\n        state.tokenize = tokenCComment;\n        return tokenCComment(stream, state);\n      }\n    },\n    name: \"css\"\n  });\n\n  CodeMirror.defineMIME(\"text/x-scss\", {\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    mediaValueKeywords: mediaValueKeywords,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    fontProperties: fontProperties,\n    allowNested: true,\n    lineComment: \"//\",\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (stream.eat(\"/\")) {\n          stream.skipToEnd();\n          return [\"comment\", \"comment\"];\n        } else if (stream.eat(\"*\")) {\n          state.tokenize = tokenCComment;\n          return tokenCComment(stream, state);\n        } else {\n          return [\"operator\", \"operator\"];\n        }\n      },\n      \":\": function(stream) {\n        if (stream.match(/\\s*\\{/, false))\n          return [null, null]\n        return false;\n      },\n      \"$\": function(stream) {\n        stream.match(/^[\\w-]+/);\n        if (stream.match(/^\\s*:/, false))\n          return [\"variable-2\", \"variable-definition\"];\n        return [\"variable-2\", \"variable\"];\n      },\n      \"#\": function(stream) {\n        if (!stream.eat(\"{\")) return false;\n        return [null, \"interpolation\"];\n      }\n    },\n    name: \"css\",\n    helperType: \"scss\"\n  });\n\n  CodeMirror.defineMIME(\"text/x-less\", {\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    mediaValueKeywords: mediaValueKeywords,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    fontProperties: fontProperties,\n    allowNested: true,\n    lineComment: \"//\",\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (stream.eat(\"/\")) {\n          stream.skipToEnd();\n          return [\"comment\", \"comment\"];\n        } else if (stream.eat(\"*\")) {\n          state.tokenize = tokenCComment;\n          return tokenCComment(stream, state);\n        } else {\n          return [\"operator\", \"operator\"];\n        }\n      },\n      \"@\": function(stream) {\n        if (stream.eat(\"{\")) return [null, \"interpolation\"];\n        if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\\b/i, false)) return false;\n        stream.eatWhile(/[\\w\\\\\\-]/);\n        if (stream.match(/^\\s*:/, false))\n          return [\"variable-2\", \"variable-definition\"];\n        return [\"variable-2\", \"variable\"];\n      },\n      \"&\": function() {\n        return [\"atom\", \"atom\"];\n      }\n    },\n    name: \"css\",\n    helperType: \"less\"\n  });\n\n  CodeMirror.defineMIME(\"text/x-gss\", {\n    documentTypes: documentTypes,\n    mediaTypes: mediaTypes,\n    mediaFeatures: mediaFeatures,\n    propertyKeywords: propertyKeywords,\n    nonStandardPropertyKeywords: nonStandardPropertyKeywords,\n    fontProperties: fontProperties,\n    counterDescriptors: counterDescriptors,\n    colorKeywords: colorKeywords,\n    valueKeywords: valueKeywords,\n    supportsAtComponent: true,\n    tokenHooks: {\n      \"/\": function(stream, state) {\n        if (!stream.eat(\"*\")) return false;\n        state.tokenize = tokenCComment;\n        return tokenCComment(stream, state);\n      }\n    },\n    name: \"css\",\n    helperType: \"gss\"\n  });\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/go.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"go\", function(config) {\n  var indentUnit = config.indentUnit;\n\n  var keywords = {\n    \"break\":true, \"case\":true, \"chan\":true, \"const\":true, \"continue\":true,\n    \"default\":true, \"defer\":true, \"else\":true, \"fallthrough\":true, \"for\":true,\n    \"func\":true, \"go\":true, \"goto\":true, \"if\":true, \"import\":true,\n    \"interface\":true, \"map\":true, \"package\":true, \"range\":true, \"return\":true,\n    \"select\":true, \"struct\":true, \"switch\":true, \"type\":true, \"var\":true,\n    \"bool\":true, \"byte\":true, \"complex64\":true, \"complex128\":true,\n    \"float32\":true, \"float64\":true, \"int8\":true, \"int16\":true, \"int32\":true,\n    \"int64\":true, \"string\":true, \"uint8\":true, \"uint16\":true, \"uint32\":true,\n    \"uint64\":true, \"int\":true, \"uint\":true, \"uintptr\":true, \"error\": true,\n    \"rune\":true\n  };\n\n  var atoms = {\n    \"true\":true, \"false\":true, \"iota\":true, \"nil\":true, \"append\":true,\n    \"cap\":true, \"close\":true, \"complex\":true, \"copy\":true, \"delete\":true, \"imag\":true,\n    \"len\":true, \"make\":true, \"new\":true, \"panic\":true, \"print\":true,\n    \"println\":true, \"real\":true, \"recover\":true\n  };\n\n  var isOperatorChar = /[+\\-*&^%:=<>!|\\/]/;\n\n  var curPunc;\n\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (ch == '\"' || ch == \"'\" || ch == \"`\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    }\n    if (/[\\d\\.]/.test(ch)) {\n      if (ch == \".\") {\n        stream.match(/^[0-9]+([eE][\\-+]?[0-9]+)?/);\n      } else if (ch == \"0\") {\n        stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/);\n      } else {\n        stream.match(/^[0-9]*\\.?[0-9]*([eE][\\-+]?[0-9]+)?/);\n      }\n      return \"number\";\n    }\n    if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch)) {\n      curPunc = ch;\n      return null;\n    }\n    if (ch == \"/\") {\n      if (stream.eat(\"*\")) {\n        state.tokenize = tokenComment;\n        return tokenComment(stream, state);\n      }\n      if (stream.eat(\"/\")) {\n        stream.skipToEnd();\n        return \"comment\";\n      }\n    }\n    if (isOperatorChar.test(ch)) {\n      stream.eatWhile(isOperatorChar);\n      return \"operator\";\n    }\n    stream.eatWhile(/[\\w\\$_\\xa1-\\uffff]/);\n    var cur = stream.current();\n    if (keywords.propertyIsEnumerable(cur)) {\n      if (cur == \"case\" || cur == \"default\") curPunc = \"case\";\n      return \"keyword\";\n    }\n    if (atoms.propertyIsEnumerable(cur)) return \"atom\";\n    return \"variable\";\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, next, end = false;\n      while ((next = stream.next()) != null) {\n        if (next == quote && !escaped) {end = true; break;}\n        escaped = !escaped && quote != \"`\" && next == \"\\\\\";\n      }\n      if (end || !(escaped || quote == \"`\"))\n        state.tokenize = tokenBase;\n      return \"string\";\n    };\n  }\n\n  function tokenComment(stream, state) {\n    var maybeEnd = false, ch;\n    while (ch = stream.next()) {\n      if (ch == \"/\" && maybeEnd) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return \"comment\";\n  }\n\n  function Context(indented, column, type, align, prev) {\n    this.indented = indented;\n    this.column = column;\n    this.type = type;\n    this.align = align;\n    this.prev = prev;\n  }\n  function pushContext(state, col, type) {\n    return state.context = new Context(state.indented, col, type, null, state.context);\n  }\n  function popContext(state) {\n    if (!state.context.prev) return;\n    var t = state.context.type;\n    if (t == \")\" || t == \"]\" || t == \"}\")\n      state.indented = state.context.indented;\n    return state.context = state.context.prev;\n  }\n\n  // Interface\n\n  return {\n    startState: function(basecolumn) {\n      return {\n        tokenize: null,\n        context: new Context((basecolumn || 0) - indentUnit, 0, \"top\", false),\n        indented: 0,\n        startOfLine: true\n      };\n    },\n\n    token: function(stream, state) {\n      var ctx = state.context;\n      if (stream.sol()) {\n        if (ctx.align == null) ctx.align = false;\n        state.indented = stream.indentation();\n        state.startOfLine = true;\n        if (ctx.type == \"case\") ctx.type = \"}\";\n      }\n      if (stream.eatSpace()) return null;\n      curPunc = null;\n      var style = (state.tokenize || tokenBase)(stream, state);\n      if (style == \"comment\") return style;\n      if (ctx.align == null) ctx.align = true;\n\n      if (curPunc == \"{\") pushContext(state, stream.column(), \"}\");\n      else if (curPunc == \"[\") pushContext(state, stream.column(), \"]\");\n      else if (curPunc == \"(\") pushContext(state, stream.column(), \")\");\n      else if (curPunc == \"case\") ctx.type = \"case\";\n      else if (curPunc == \"}\" && ctx.type == \"}\") popContext(state);\n      else if (curPunc == ctx.type) popContext(state);\n      state.startOfLine = false;\n      return style;\n    },\n\n    indent: function(state, textAfter) {\n      if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;\n      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);\n      if (ctx.type == \"case\" && /^(?:case|default)\\b/.test(textAfter)) {\n        state.context.type = \"}\";\n        return ctx.indented;\n      }\n      var closing = firstChar == ctx.type;\n      if (ctx.align) return ctx.column + (closing ? 0 : 1);\n      else return ctx.indented + (closing ? 0 : indentUnit);\n    },\n\n    electricChars: \"{}):\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n    fold: \"brace\",\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    lineComment: \"//\"\n  };\n});\n\nCodeMirror.defineMIME(\"text/x-go\", \"go\");\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/htmlembedded.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../htmlmixed/htmlmixed\"),\n        require(\"../../addon/mode/multiplex\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../htmlmixed/htmlmixed\",\n            \"../../addon/mode/multiplex\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  CodeMirror.defineMode(\"htmlembedded\", function(config, parserConfig) {\n    var closeComment = parserConfig.closeComment || \"--%>\"\n    return CodeMirror.multiplexingMode(CodeMirror.getMode(config, \"htmlmixed\"), {\n      open: parserConfig.openComment || \"<%--\",\n      close: closeComment,\n      delimStyle: \"comment\",\n      mode: {token: function(stream) {\n        stream.skipTo(closeComment) || stream.skipToEnd()\n        return \"comment\"\n      }}\n    }, {\n      open: parserConfig.open || parserConfig.scriptStartRegex || \"<%\",\n      close: parserConfig.close || parserConfig.scriptEndRegex || \"%>\",\n      mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec)\n    });\n  }, \"htmlmixed\");\n\n  CodeMirror.defineMIME(\"application/x-ejs\", {name: \"htmlembedded\", scriptingModeSpec:\"javascript\"});\n  CodeMirror.defineMIME(\"application/x-aspx\", {name: \"htmlembedded\", scriptingModeSpec:\"text/x-csharp\"});\n  CodeMirror.defineMIME(\"application/x-jsp\", {name: \"htmlembedded\", scriptingModeSpec:\"text/x-java\"});\n  CodeMirror.defineMIME(\"application/x-erb\", {name: \"htmlembedded\", scriptingModeSpec:\"ruby\"});\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/htmlmixed.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../xml/xml\"), require(\"../javascript/javascript\"), require(\"../css/css\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../xml/xml\", \"../javascript/javascript\", \"../css/css\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  var defaultTags = {\n    script: [\n      [\"lang\", /(javascript|babel)/i, \"javascript\"],\n      [\"type\", /^(?:text|application)\\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, \"javascript\"],\n      [\"type\", /./, \"text/plain\"],\n      [null, null, \"javascript\"]\n    ],\n    style:  [\n      [\"lang\", /^css$/i, \"css\"],\n      [\"type\", /^(text\\/)?(x-)?(stylesheet|css)$/i, \"css\"],\n      [\"type\", /./, \"text/plain\"],\n      [null, null, \"css\"]\n    ]\n  };\n\n  function maybeBackup(stream, pat, style) {\n    var cur = stream.current(), close = cur.search(pat);\n    if (close > -1) {\n      stream.backUp(cur.length - close);\n    } else if (cur.match(/<\\/?$/)) {\n      stream.backUp(cur.length);\n      if (!stream.match(pat, false)) stream.match(cur);\n    }\n    return style;\n  }\n\n  var attrRegexpCache = {};\n  function getAttrRegexp(attr) {\n    var regexp = attrRegexpCache[attr];\n    if (regexp) return regexp;\n    return attrRegexpCache[attr] = new RegExp(\"\\\\s+\" + attr + \"\\\\s*=\\\\s*('|\\\")?([^'\\\"]+)('|\\\")?\\\\s*\");\n  }\n\n  function getAttrValue(text, attr) {\n    var match = text.match(getAttrRegexp(attr))\n    return match ? /^\\s*(.*?)\\s*$/.exec(match[2])[1] : \"\"\n  }\n\n  function getTagRegexp(tagName, anchored) {\n    return new RegExp((anchored ? \"^\" : \"\") + \"<\\/\\s*\" + tagName + \"\\s*>\", \"i\");\n  }\n\n  function addTags(from, to) {\n    for (var tag in from) {\n      var dest = to[tag] || (to[tag] = []);\n      var source = from[tag];\n      for (var i = source.length - 1; i >= 0; i--)\n        dest.unshift(source[i])\n    }\n  }\n\n  function findMatchingMode(tagInfo, tagText) {\n    for (var i = 0; i < tagInfo.length; i++) {\n      var spec = tagInfo[i];\n      if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];\n    }\n  }\n\n  CodeMirror.defineMode(\"htmlmixed\", function (config, parserConfig) {\n    var htmlMode = CodeMirror.getMode(config, {\n      name: \"xml\",\n      htmlMode: true,\n      multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,\n      multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag\n    });\n\n    var tags = {};\n    var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;\n    addTags(defaultTags, tags);\n    if (configTags) addTags(configTags, tags);\n    if (configScript) for (var i = configScript.length - 1; i >= 0; i--)\n      tags.script.unshift([\"type\", configScript[i].matches, configScript[i].mode])\n\n    function html(stream, state) {\n      var style = htmlMode.token(stream, state.htmlState), tag = /\\btag\\b/.test(style), tagName\n      if (tag && !/[<>\\s\\/]/.test(stream.current()) &&\n          (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&\n          tags.hasOwnProperty(tagName)) {\n        state.inTag = tagName + \" \"\n      } else if (state.inTag && tag && />$/.test(stream.current())) {\n        var inTag = /^([\\S]+) (.*)/.exec(state.inTag)\n        state.inTag = null\n        var modeSpec = stream.current() == \">\" && findMatchingMode(tags[inTag[1]], inTag[2])\n        var mode = CodeMirror.getMode(config, modeSpec)\n        var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);\n        state.token = function (stream, state) {\n          if (stream.match(endTagA, false)) {\n            state.token = html;\n            state.localState = state.localMode = null;\n            return null;\n          }\n          return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));\n        };\n        state.localMode = mode;\n        state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, \"\", \"\"));\n      } else if (state.inTag) {\n        state.inTag += stream.current()\n        if (stream.eol()) state.inTag += \" \"\n      }\n      return style;\n    };\n\n    return {\n      startState: function () {\n        var state = CodeMirror.startState(htmlMode);\n        return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};\n      },\n\n      copyState: function (state) {\n        var local;\n        if (state.localState) {\n          local = CodeMirror.copyState(state.localMode, state.localState);\n        }\n        return {token: state.token, inTag: state.inTag,\n                localMode: state.localMode, localState: local,\n                htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};\n      },\n\n      token: function (stream, state) {\n        return state.token(stream, state);\n      },\n\n      indent: function (state, textAfter, line) {\n        if (!state.localMode || /^\\s*<\\//.test(textAfter))\n          return htmlMode.indent(state.htmlState, textAfter, line);\n        else if (state.localMode.indent)\n          return state.localMode.indent(state.localState, textAfter, line);\n        else\n          return CodeMirror.Pass;\n      },\n\n      innerMode: function (state) {\n        return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};\n      }\n    };\n  }, \"xml\", \"javascript\", \"css\");\n\n  CodeMirror.defineMIME(\"text/html\", \"htmlmixed\");\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/javascript.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"javascript\", function(config, parserConfig) {\n  var indentUnit = config.indentUnit;\n  var statementIndent = parserConfig.statementIndent;\n  var jsonldMode = parserConfig.jsonld;\n  var jsonMode = parserConfig.json || jsonldMode;\n  var isTS = parserConfig.typescript;\n  var wordRE = parserConfig.wordCharacters || /[\\w$\\xa1-\\uffff]/;\n\n  // Tokenizer\n\n  var keywords = function(){\n    function kw(type) {return {type: type, style: \"keyword\"};}\n    var A = kw(\"keyword a\"), B = kw(\"keyword b\"), C = kw(\"keyword c\"), D = kw(\"keyword d\");\n    var operator = kw(\"operator\"), atom = {type: \"atom\", style: \"atom\"};\n\n    return {\n      \"if\": kw(\"if\"), \"while\": A, \"with\": A, \"else\": B, \"do\": B, \"try\": B, \"finally\": B,\n      \"return\": D, \"break\": D, \"continue\": D, \"new\": kw(\"new\"), \"delete\": C, \"void\": C, \"throw\": C,\n      \"debugger\": kw(\"debugger\"), \"var\": kw(\"var\"), \"const\": kw(\"var\"), \"let\": kw(\"var\"),\n      \"function\": kw(\"function\"), \"catch\": kw(\"catch\"),\n      \"for\": kw(\"for\"), \"switch\": kw(\"switch\"), \"case\": kw(\"case\"), \"default\": kw(\"default\"),\n      \"in\": operator, \"typeof\": operator, \"instanceof\": operator,\n      \"true\": atom, \"false\": atom, \"null\": atom, \"undefined\": atom, \"NaN\": atom, \"Infinity\": atom,\n      \"this\": kw(\"this\"), \"class\": kw(\"class\"), \"super\": kw(\"atom\"),\n      \"yield\": C, \"export\": kw(\"export\"), \"import\": kw(\"import\"), \"extends\": C,\n      \"await\": C\n    };\n  }();\n\n  var isOperatorChar = /[+\\-*&%=<>!?|~^@]/;\n  var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)\"/;\n\n  function readRegexp(stream) {\n    var escaped = false, next, inSet = false;\n    while ((next = stream.next()) != null) {\n      if (!escaped) {\n        if (next == \"/\" && !inSet) return;\n        if (next == \"[\") inSet = true;\n        else if (inSet && next == \"]\") inSet = false;\n      }\n      escaped = !escaped && next == \"\\\\\";\n    }\n  }\n\n  // Used as scratch variables to communicate multiple values without\n  // consing up tons of objects.\n  var type, content;\n  function ret(tp, style, cont) {\n    type = tp; content = cont;\n    return style;\n  }\n  function tokenBase(stream, state) {\n    var ch = stream.next();\n    if (ch == '\"' || ch == \"'\") {\n      state.tokenize = tokenString(ch);\n      return state.tokenize(stream, state);\n    } else if (ch == \".\" && stream.match(/^\\d[\\d_]*(?:[eE][+\\-]?[\\d_]+)?/)) {\n      return ret(\"number\", \"number\");\n    } else if (ch == \".\" && stream.match(\"..\")) {\n      return ret(\"spread\", \"meta\");\n    } else if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch)) {\n      return ret(ch);\n    } else if (ch == \"=\" && stream.eat(\">\")) {\n      return ret(\"=>\", \"operator\");\n    } else if (ch == \"0\" && stream.match(/^(?:x[\\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {\n      return ret(\"number\", \"number\");\n    } else if (/\\d/.test(ch)) {\n      stream.match(/^[\\d_]*(?:n|(?:\\.[\\d_]*)?(?:[eE][+\\-]?[\\d_]+)?)?/);\n      return ret(\"number\", \"number\");\n    } else if (ch == \"/\") {\n      if (stream.eat(\"*\")) {\n        state.tokenize = tokenComment;\n        return tokenComment(stream, state);\n      } else if (stream.eat(\"/\")) {\n        stream.skipToEnd();\n        return ret(\"comment\", \"comment\");\n      } else if (expressionAllowed(stream, state, 1)) {\n        readRegexp(stream);\n        stream.match(/^\\b(([gimyus])(?![gimyus]*\\2))+\\b/);\n        return ret(\"regexp\", \"string-2\");\n      } else {\n        stream.eat(\"=\");\n        return ret(\"operator\", \"operator\", stream.current());\n      }\n    } else if (ch == \"`\") {\n      state.tokenize = tokenQuasi;\n      return tokenQuasi(stream, state);\n    } else if (ch == \"#\" && stream.peek() == \"!\") {\n      stream.skipToEnd();\n      return ret(\"meta\", \"meta\");\n    } else if (ch == \"#\" && stream.eatWhile(wordRE)) {\n      return ret(\"variable\", \"property\")\n    } else if (ch == \"<\" && stream.match(\"!--\") ||\n               (ch == \"-\" && stream.match(\"->\") && !/\\S/.test(stream.string.slice(0, stream.start)))) {\n      stream.skipToEnd()\n      return ret(\"comment\", \"comment\")\n    } else if (isOperatorChar.test(ch)) {\n      if (ch != \">\" || !state.lexical || state.lexical.type != \">\") {\n        if (stream.eat(\"=\")) {\n          if (ch == \"!\" || ch == \"=\") stream.eat(\"=\")\n        } else if (/[<>*+\\-]/.test(ch)) {\n          stream.eat(ch)\n          if (ch == \">\") stream.eat(ch)\n        }\n      }\n      if (ch == \"?\" && stream.eat(\".\")) return ret(\".\")\n      return ret(\"operator\", \"operator\", stream.current());\n    } else if (wordRE.test(ch)) {\n      stream.eatWhile(wordRE);\n      var word = stream.current()\n      if (state.lastType != \".\") {\n        if (keywords.propertyIsEnumerable(word)) {\n          var kw = keywords[word]\n          return ret(kw.type, kw.style, word)\n        }\n        if (word == \"async\" && stream.match(/^(\\s|\\/\\*.*?\\*\\/)*[\\[\\(\\w]/, false))\n          return ret(\"async\", \"keyword\", word)\n      }\n      return ret(\"variable\", \"variable\", word)\n    }\n  }\n\n  function tokenString(quote) {\n    return function(stream, state) {\n      var escaped = false, next;\n      if (jsonldMode && stream.peek() == \"@\" && stream.match(isJsonldKeyword)){\n        state.tokenize = tokenBase;\n        return ret(\"jsonld-keyword\", \"meta\");\n      }\n      while ((next = stream.next()) != null) {\n        if (next == quote && !escaped) break;\n        escaped = !escaped && next == \"\\\\\";\n      }\n      if (!escaped) state.tokenize = tokenBase;\n      return ret(\"string\", \"string\");\n    };\n  }\n\n  function tokenComment(stream, state) {\n    var maybeEnd = false, ch;\n    while (ch = stream.next()) {\n      if (ch == \"/\" && maybeEnd) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      maybeEnd = (ch == \"*\");\n    }\n    return ret(\"comment\", \"comment\");\n  }\n\n  function tokenQuasi(stream, state) {\n    var escaped = false, next;\n    while ((next = stream.next()) != null) {\n      if (!escaped && (next == \"`\" || next == \"$\" && stream.eat(\"{\"))) {\n        state.tokenize = tokenBase;\n        break;\n      }\n      escaped = !escaped && next == \"\\\\\";\n    }\n    return ret(\"quasi\", \"string-2\", stream.current());\n  }\n\n  var brackets = \"([{}])\";\n  // This is a crude lookahead trick to try and notice that we're\n  // parsing the argument patterns for a fat-arrow function before we\n  // actually hit the arrow token. It only works if the arrow is on\n  // the same line as the arguments and there's no strange noise\n  // (comments) in between. Fallback is to only notice when we hit the\n  // arrow, and not declare the arguments as locals for the arrow\n  // body.\n  function findFatArrow(stream, state) {\n    if (state.fatArrowAt) state.fatArrowAt = null;\n    var arrow = stream.string.indexOf(\"=>\", stream.start);\n    if (arrow < 0) return;\n\n    if (isTS) { // Try to skip TypeScript return type declarations after the arguments\n      var m = /:\\s*(?:\\w+(?:<[^>]*>|\\[\\])?|\\{[^}]*\\})\\s*$/.exec(stream.string.slice(stream.start, arrow))\n      if (m) arrow = m.index\n    }\n\n    var depth = 0, sawSomething = false;\n    for (var pos = arrow - 1; pos >= 0; --pos) {\n      var ch = stream.string.charAt(pos);\n      var bracket = brackets.indexOf(ch);\n      if (bracket >= 0 && bracket < 3) {\n        if (!depth) { ++pos; break; }\n        if (--depth == 0) { if (ch == \"(\") sawSomething = true; break; }\n      } else if (bracket >= 3 && bracket < 6) {\n        ++depth;\n      } else if (wordRE.test(ch)) {\n        sawSomething = true;\n      } else if (/[\"'\\/`]/.test(ch)) {\n        for (;; --pos) {\n          if (pos == 0) return\n          var next = stream.string.charAt(pos - 1)\n          if (next == ch && stream.string.charAt(pos - 2) != \"\\\\\") { pos--; break }\n        }\n      } else if (sawSomething && !depth) {\n        ++pos;\n        break;\n      }\n    }\n    if (sawSomething && !depth) state.fatArrowAt = pos;\n  }\n\n  // Parser\n\n  var atomicTypes = {\"atom\": true, \"number\": true, \"variable\": true, \"string\": true, \"regexp\": true, \"this\": true, \"jsonld-keyword\": true};\n\n  function JSLexical(indented, column, type, align, prev, info) {\n    this.indented = indented;\n    this.column = column;\n    this.type = type;\n    this.prev = prev;\n    this.info = info;\n    if (align != null) this.align = align;\n  }\n\n  function inScope(state, varname) {\n    for (var v = state.localVars; v; v = v.next)\n      if (v.name == varname) return true;\n    for (var cx = state.context; cx; cx = cx.prev) {\n      for (var v = cx.vars; v; v = v.next)\n        if (v.name == varname) return true;\n    }\n  }\n\n  function parseJS(state, style, type, content, stream) {\n    var cc = state.cc;\n    // Communicate our context to the combinators.\n    // (Less wasteful than consing up a hundred closures on every call.)\n    cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;\n\n    if (!state.lexical.hasOwnProperty(\"align\"))\n      state.lexical.align = true;\n\n    while(true) {\n      var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;\n      if (combinator(type, content)) {\n        while(cc.length && cc[cc.length - 1].lex)\n          cc.pop()();\n        if (cx.marked) return cx.marked;\n        if (type == \"variable\" && inScope(state, content)) return \"variable-2\";\n        return style;\n      }\n    }\n  }\n\n  // Combinator utils\n\n  var cx = {state: null, column: null, marked: null, cc: null};\n  function pass() {\n    for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);\n  }\n  function cont() {\n    pass.apply(null, arguments);\n    return true;\n  }\n  function inList(name, list) {\n    for (var v = list; v; v = v.next) if (v.name == name) return true\n    return false;\n  }\n  function register(varname) {\n    var state = cx.state;\n    cx.marked = \"def\";\n    if (state.context) {\n      if (state.lexical.info == \"var\" && state.context && state.context.block) {\n        // FIXME function decls are also not block scoped\n        var newContext = registerVarScoped(varname, state.context)\n        if (newContext != null) {\n          state.context = newContext\n          return\n        }\n      } else if (!inList(varname, state.localVars)) {\n        state.localVars = new Var(varname, state.localVars)\n        return\n      }\n    }\n    // Fall through means this is global\n    if (parserConfig.globalVars && !inList(varname, state.globalVars))\n      state.globalVars = new Var(varname, state.globalVars)\n  }\n  function registerVarScoped(varname, context) {\n    if (!context) {\n      return null\n    } else if (context.block) {\n      var inner = registerVarScoped(varname, context.prev)\n      if (!inner) return null\n      if (inner == context.prev) return context\n      return new Context(inner, context.vars, true)\n    } else if (inList(varname, context.vars)) {\n      return context\n    } else {\n      return new Context(context.prev, new Var(varname, context.vars), false)\n    }\n  }\n\n  function isModifier(name) {\n    return name == \"public\" || name == \"private\" || name == \"protected\" || name == \"abstract\" || name == \"readonly\"\n  }\n\n  // Combinators\n\n  function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }\n  function Var(name, next) { this.name = name; this.next = next }\n\n  var defaultVars = new Var(\"this\", new Var(\"arguments\", null))\n  function pushcontext() {\n    cx.state.context = new Context(cx.state.context, cx.state.localVars, false)\n    cx.state.localVars = defaultVars\n  }\n  function pushblockcontext() {\n    cx.state.context = new Context(cx.state.context, cx.state.localVars, true)\n    cx.state.localVars = null\n  }\n  function popcontext() {\n    cx.state.localVars = cx.state.context.vars\n    cx.state.context = cx.state.context.prev\n  }\n  popcontext.lex = true\n  function pushlex(type, info) {\n    var result = function() {\n      var state = cx.state, indent = state.indented;\n      if (state.lexical.type == \"stat\") indent = state.lexical.indented;\n      else for (var outer = state.lexical; outer && outer.type == \")\" && outer.align; outer = outer.prev)\n        indent = outer.indented;\n      state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);\n    };\n    result.lex = true;\n    return result;\n  }\n  function poplex() {\n    var state = cx.state;\n    if (state.lexical.prev) {\n      if (state.lexical.type == \")\")\n        state.indented = state.lexical.indented;\n      state.lexical = state.lexical.prev;\n    }\n  }\n  poplex.lex = true;\n\n  function expect(wanted) {\n    function exp(type) {\n      if (type == wanted) return cont();\n      else if (wanted == \";\" || type == \"}\" || type == \")\" || type == \"]\") return pass();\n      else return cont(exp);\n    };\n    return exp;\n  }\n\n  function statement(type, value) {\n    if (type == \"var\") return cont(pushlex(\"vardef\", value), vardef, expect(\";\"), poplex);\n    if (type == \"keyword a\") return cont(pushlex(\"form\"), parenExpr, statement, poplex);\n    if (type == \"keyword b\") return cont(pushlex(\"form\"), statement, poplex);\n    if (type == \"keyword d\") return cx.stream.match(/^\\s*$/, false) ? cont() : cont(pushlex(\"stat\"), maybeexpression, expect(\";\"), poplex);\n    if (type == \"debugger\") return cont(expect(\";\"));\n    if (type == \"{\") return cont(pushlex(\"}\"), pushblockcontext, block, poplex, popcontext);\n    if (type == \";\") return cont();\n    if (type == \"if\") {\n      if (cx.state.lexical.info == \"else\" && cx.state.cc[cx.state.cc.length - 1] == poplex)\n        cx.state.cc.pop()();\n      return cont(pushlex(\"form\"), parenExpr, statement, poplex, maybeelse);\n    }\n    if (type == \"function\") return cont(functiondef);\n    if (type == \"for\") return cont(pushlex(\"form\"), forspec, statement, poplex);\n    if (type == \"class\" || (isTS && value == \"interface\")) {\n      cx.marked = \"keyword\"\n      return cont(pushlex(\"form\", type == \"class\" ? type : value), className, poplex)\n    }\n    if (type == \"variable\") {\n      if (isTS && value == \"declare\") {\n        cx.marked = \"keyword\"\n        return cont(statement)\n      } else if (isTS && (value == \"module\" || value == \"enum\" || value == \"type\") && cx.stream.match(/^\\s*\\w/, false)) {\n        cx.marked = \"keyword\"\n        if (value == \"enum\") return cont(enumdef);\n        else if (value == \"type\") return cont(typename, expect(\"operator\"), typeexpr, expect(\";\"));\n        else return cont(pushlex(\"form\"), pattern, expect(\"{\"), pushlex(\"}\"), block, poplex, poplex)\n      } else if (isTS && value == \"namespace\") {\n        cx.marked = \"keyword\"\n        return cont(pushlex(\"form\"), expression, statement, poplex)\n      } else if (isTS && value == \"abstract\") {\n        cx.marked = \"keyword\"\n        return cont(statement)\n      } else {\n        return cont(pushlex(\"stat\"), maybelabel);\n      }\n    }\n    if (type == \"switch\") return cont(pushlex(\"form\"), parenExpr, expect(\"{\"), pushlex(\"}\", \"switch\"), pushblockcontext,\n                                      block, poplex, poplex, popcontext);\n    if (type == \"case\") return cont(expression, expect(\":\"));\n    if (type == \"default\") return cont(expect(\":\"));\n    if (type == \"catch\") return cont(pushlex(\"form\"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);\n    if (type == \"export\") return cont(pushlex(\"stat\"), afterExport, poplex);\n    if (type == \"import\") return cont(pushlex(\"stat\"), afterImport, poplex);\n    if (type == \"async\") return cont(statement)\n    if (value == \"@\") return cont(expression, statement)\n    return pass(pushlex(\"stat\"), expression, expect(\";\"), poplex);\n  }\n  function maybeCatchBinding(type) {\n    if (type == \"(\") return cont(funarg, expect(\")\"))\n  }\n  function expression(type, value) {\n    return expressionInner(type, value, false);\n  }\n  function expressionNoComma(type, value) {\n    return expressionInner(type, value, true);\n  }\n  function parenExpr(type) {\n    if (type != \"(\") return pass()\n    return cont(pushlex(\")\"), maybeexpression, expect(\")\"), poplex)\n  }\n  function expressionInner(type, value, noComma) {\n    if (cx.state.fatArrowAt == cx.stream.start) {\n      var body = noComma ? arrowBodyNoComma : arrowBody;\n      if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, expect(\"=>\"), body, popcontext);\n      else if (type == \"variable\") return pass(pushcontext, pattern, expect(\"=>\"), body, popcontext);\n    }\n\n    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;\n    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);\n    if (type == \"function\") return cont(functiondef, maybeop);\n    if (type == \"class\" || (isTS && value == \"interface\")) { cx.marked = \"keyword\"; return cont(pushlex(\"form\"), classExpression, poplex); }\n    if (type == \"keyword c\" || type == \"async\") return cont(noComma ? expressionNoComma : expression);\n    if (type == \"(\") return cont(pushlex(\")\"), maybeexpression, expect(\")\"), poplex, maybeop);\n    if (type == \"operator\" || type == \"spread\") return cont(noComma ? expressionNoComma : expression);\n    if (type == \"[\") return cont(pushlex(\"]\"), arrayLiteral, poplex, maybeop);\n    if (type == \"{\") return contCommasep(objprop, \"}\", null, maybeop);\n    if (type == \"quasi\") return pass(quasi, maybeop);\n    if (type == \"new\") return cont(maybeTarget(noComma));\n    if (type == \"import\") return cont(expression);\n    return cont();\n  }\n  function maybeexpression(type) {\n    if (type.match(/[;\\}\\)\\],]/)) return pass();\n    return pass(expression);\n  }\n\n  function maybeoperatorComma(type, value) {\n    if (type == \",\") return cont(maybeexpression);\n    return maybeoperatorNoComma(type, value, false);\n  }\n  function maybeoperatorNoComma(type, value, noComma) {\n    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;\n    var expr = noComma == false ? expression : expressionNoComma;\n    if (type == \"=>\") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);\n    if (type == \"operator\") {\n      if (/\\+\\+|--/.test(value) || isTS && value == \"!\") return cont(me);\n      if (isTS && value == \"<\" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\\s*\\(/, false))\n        return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, me);\n      if (value == \"?\") return cont(expression, expect(\":\"), expr);\n      return cont(expr);\n    }\n    if (type == \"quasi\") { return pass(quasi, me); }\n    if (type == \";\") return;\n    if (type == \"(\") return contCommasep(expressionNoComma, \")\", \"call\", me);\n    if (type == \".\") return cont(property, me);\n    if (type == \"[\") return cont(pushlex(\"]\"), maybeexpression, expect(\"]\"), poplex, me);\n    if (isTS && value == \"as\") { cx.marked = \"keyword\"; return cont(typeexpr, me) }\n    if (type == \"regexp\") {\n      cx.state.lastType = cx.marked = \"operator\"\n      cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)\n      return cont(expr)\n    }\n  }\n  function quasi(type, value) {\n    if (type != \"quasi\") return pass();\n    if (value.slice(value.length - 2) != \"${\") return cont(quasi);\n    return cont(expression, continueQuasi);\n  }\n  function continueQuasi(type) {\n    if (type == \"}\") {\n      cx.marked = \"string-2\";\n      cx.state.tokenize = tokenQuasi;\n      return cont(quasi);\n    }\n  }\n  function arrowBody(type) {\n    findFatArrow(cx.stream, cx.state);\n    return pass(type == \"{\" ? statement : expression);\n  }\n  function arrowBodyNoComma(type) {\n    findFatArrow(cx.stream, cx.state);\n    return pass(type == \"{\" ? statement : expressionNoComma);\n  }\n  function maybeTarget(noComma) {\n    return function(type) {\n      if (type == \".\") return cont(noComma ? targetNoComma : target);\n      else if (type == \"variable\" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)\n      else return pass(noComma ? expressionNoComma : expression);\n    };\n  }\n  function target(_, value) {\n    if (value == \"target\") { cx.marked = \"keyword\"; return cont(maybeoperatorComma); }\n  }\n  function targetNoComma(_, value) {\n    if (value == \"target\") { cx.marked = \"keyword\"; return cont(maybeoperatorNoComma); }\n  }\n  function maybelabel(type) {\n    if (type == \":\") return cont(poplex, statement);\n    return pass(maybeoperatorComma, expect(\";\"), poplex);\n  }\n  function property(type) {\n    if (type == \"variable\") {cx.marked = \"property\"; return cont();}\n  }\n  function objprop(type, value) {\n    if (type == \"async\") {\n      cx.marked = \"property\";\n      return cont(objprop);\n    } else if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\";\n      if (value == \"get\" || value == \"set\") return cont(getterSetter);\n      var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params\n      if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\\s*:\\s*/, false)))\n        cx.state.fatArrowAt = cx.stream.pos + m[0].length\n      return cont(afterprop);\n    } else if (type == \"number\" || type == \"string\") {\n      cx.marked = jsonldMode ? \"property\" : (cx.style + \" property\");\n      return cont(afterprop);\n    } else if (type == \"jsonld-keyword\") {\n      return cont(afterprop);\n    } else if (isTS && isModifier(value)) {\n      cx.marked = \"keyword\"\n      return cont(objprop)\n    } else if (type == \"[\") {\n      return cont(expression, maybetype, expect(\"]\"), afterprop);\n    } else if (type == \"spread\") {\n      return cont(expressionNoComma, afterprop);\n    } else if (value == \"*\") {\n      cx.marked = \"keyword\";\n      return cont(objprop);\n    } else if (type == \":\") {\n      return pass(afterprop)\n    }\n  }\n  function getterSetter(type) {\n    if (type != \"variable\") return pass(afterprop);\n    cx.marked = \"property\";\n    return cont(functiondef);\n  }\n  function afterprop(type) {\n    if (type == \":\") return cont(expressionNoComma);\n    if (type == \"(\") return pass(functiondef);\n  }\n  function commasep(what, end, sep) {\n    function proceed(type, value) {\n      if (sep ? sep.indexOf(type) > -1 : type == \",\") {\n        var lex = cx.state.lexical;\n        if (lex.info == \"call\") lex.pos = (lex.pos || 0) + 1;\n        return cont(function(type, value) {\n          if (type == end || value == end) return pass()\n          return pass(what)\n        }, proceed);\n      }\n      if (type == end || value == end) return cont();\n      if (sep && sep.indexOf(\";\") > -1) return pass(what)\n      return cont(expect(end));\n    }\n    return function(type, value) {\n      if (type == end || value == end) return cont();\n      return pass(what, proceed);\n    };\n  }\n  function contCommasep(what, end, info) {\n    for (var i = 3; i < arguments.length; i++)\n      cx.cc.push(arguments[i]);\n    return cont(pushlex(end, info), commasep(what, end), poplex);\n  }\n  function block(type) {\n    if (type == \"}\") return cont();\n    return pass(statement, block);\n  }\n  function maybetype(type, value) {\n    if (isTS) {\n      if (type == \":\") return cont(typeexpr);\n      if (value == \"?\") return cont(maybetype);\n    }\n  }\n  function maybetypeOrIn(type, value) {\n    if (isTS && (type == \":\" || value == \"in\")) return cont(typeexpr)\n  }\n  function mayberettype(type) {\n    if (isTS && type == \":\") {\n      if (cx.stream.match(/^\\s*\\w+\\s+is\\b/, false)) return cont(expression, isKW, typeexpr)\n      else return cont(typeexpr)\n    }\n  }\n  function isKW(_, value) {\n    if (value == \"is\") {\n      cx.marked = \"keyword\"\n      return cont()\n    }\n  }\n  function typeexpr(type, value) {\n    if (value == \"keyof\" || value == \"typeof\" || value == \"infer\") {\n      cx.marked = \"keyword\"\n      return cont(value == \"typeof\" ? expressionNoComma : typeexpr)\n    }\n    if (type == \"variable\" || value == \"void\") {\n      cx.marked = \"type\"\n      return cont(afterType)\n    }\n    if (value == \"|\" || value == \"&\") return cont(typeexpr)\n    if (type == \"string\" || type == \"number\" || type == \"atom\") return cont(afterType);\n    if (type == \"[\") return cont(pushlex(\"]\"), commasep(typeexpr, \"]\", \",\"), poplex, afterType)\n    if (type == \"{\") return cont(pushlex(\"}\"), commasep(typeprop, \"}\", \",;\"), poplex, afterType)\n    if (type == \"(\") return cont(commasep(typearg, \")\"), maybeReturnType, afterType)\n    if (type == \"<\") return cont(commasep(typeexpr, \">\"), typeexpr)\n  }\n  function maybeReturnType(type) {\n    if (type == \"=>\") return cont(typeexpr)\n  }\n  function typeprop(type, value) {\n    if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\"\n      return cont(typeprop)\n    } else if (value == \"?\" || type == \"number\" || type == \"string\") {\n      return cont(typeprop)\n    } else if (type == \":\") {\n      return cont(typeexpr)\n    } else if (type == \"[\") {\n      return cont(expect(\"variable\"), maybetypeOrIn, expect(\"]\"), typeprop)\n    } else if (type == \"(\") {\n      return pass(functiondecl, typeprop)\n    }\n  }\n  function typearg(type, value) {\n    if (type == \"variable\" && cx.stream.match(/^\\s*[?:]/, false) || value == \"?\") return cont(typearg)\n    if (type == \":\") return cont(typeexpr)\n    if (type == \"spread\") return cont(typearg)\n    return pass(typeexpr)\n  }\n  function afterType(type, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, afterType)\n    if (value == \"|\" || type == \".\" || value == \"&\") return cont(typeexpr)\n    if (type == \"[\") return cont(typeexpr, expect(\"]\"), afterType)\n    if (value == \"extends\" || value == \"implements\") { cx.marked = \"keyword\"; return cont(typeexpr) }\n    if (value == \"?\") return cont(typeexpr, expect(\":\"), typeexpr)\n  }\n  function maybeTypeArgs(_, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeexpr, \">\"), poplex, afterType)\n  }\n  function typeparam() {\n    return pass(typeexpr, maybeTypeDefault)\n  }\n  function maybeTypeDefault(_, value) {\n    if (value == \"=\") return cont(typeexpr)\n  }\n  function vardef(_, value) {\n    if (value == \"enum\") {cx.marked = \"keyword\"; return cont(enumdef)}\n    return pass(pattern, maybetype, maybeAssign, vardefCont);\n  }\n  function pattern(type, value) {\n    if (isTS && isModifier(value)) { cx.marked = \"keyword\"; return cont(pattern) }\n    if (type == \"variable\") { register(value); return cont(); }\n    if (type == \"spread\") return cont(pattern);\n    if (type == \"[\") return contCommasep(eltpattern, \"]\");\n    if (type == \"{\") return contCommasep(proppattern, \"}\");\n  }\n  function proppattern(type, value) {\n    if (type == \"variable\" && !cx.stream.match(/^\\s*:/, false)) {\n      register(value);\n      return cont(maybeAssign);\n    }\n    if (type == \"variable\") cx.marked = \"property\";\n    if (type == \"spread\") return cont(pattern);\n    if (type == \"}\") return pass();\n    if (type == \"[\") return cont(expression, expect(']'), expect(':'), proppattern);\n    return cont(expect(\":\"), pattern, maybeAssign);\n  }\n  function eltpattern() {\n    return pass(pattern, maybeAssign)\n  }\n  function maybeAssign(_type, value) {\n    if (value == \"=\") return cont(expressionNoComma);\n  }\n  function vardefCont(type) {\n    if (type == \",\") return cont(vardef);\n  }\n  function maybeelse(type, value) {\n    if (type == \"keyword b\" && value == \"else\") return cont(pushlex(\"form\", \"else\"), statement, poplex);\n  }\n  function forspec(type, value) {\n    if (value == \"await\") return cont(forspec);\n    if (type == \"(\") return cont(pushlex(\")\"), forspec1, poplex);\n  }\n  function forspec1(type) {\n    if (type == \"var\") return cont(vardef, forspec2);\n    if (type == \"variable\") return cont(forspec2);\n    return pass(forspec2)\n  }\n  function forspec2(type, value) {\n    if (type == \")\") return cont()\n    if (type == \";\") return cont(forspec2)\n    if (value == \"in\" || value == \"of\") { cx.marked = \"keyword\"; return cont(expression, forspec2) }\n    return pass(expression, forspec2)\n  }\n  function functiondef(type, value) {\n    if (value == \"*\") {cx.marked = \"keyword\"; return cont(functiondef);}\n    if (type == \"variable\") {register(value); return cont(functiondef);}\n    if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, mayberettype, statement, popcontext);\n    if (isTS && value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, functiondef)\n  }\n  function functiondecl(type, value) {\n    if (value == \"*\") {cx.marked = \"keyword\"; return cont(functiondecl);}\n    if (type == \"variable\") {register(value); return cont(functiondecl);}\n    if (type == \"(\") return cont(pushcontext, pushlex(\")\"), commasep(funarg, \")\"), poplex, mayberettype, popcontext);\n    if (isTS && value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, functiondecl)\n  }\n  function typename(type, value) {\n    if (type == \"keyword\" || type == \"variable\") {\n      cx.marked = \"type\"\n      return cont(typename)\n    } else if (value == \"<\") {\n      return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex)\n    }\n  }\n  function funarg(type, value) {\n    if (value == \"@\") cont(expression, funarg)\n    if (type == \"spread\") return cont(funarg);\n    if (isTS && isModifier(value)) { cx.marked = \"keyword\"; return cont(funarg); }\n    if (isTS && type == \"this\") return cont(maybetype, maybeAssign)\n    return pass(pattern, maybetype, maybeAssign);\n  }\n  function classExpression(type, value) {\n    // Class expressions may have an optional name.\n    if (type == \"variable\") return className(type, value);\n    return classNameAfter(type, value);\n  }\n  function className(type, value) {\n    if (type == \"variable\") {register(value); return cont(classNameAfter);}\n  }\n  function classNameAfter(type, value) {\n    if (value == \"<\") return cont(pushlex(\">\"), commasep(typeparam, \">\"), poplex, classNameAfter)\n    if (value == \"extends\" || value == \"implements\" || (isTS && type == \",\")) {\n      if (value == \"implements\") cx.marked = \"keyword\";\n      return cont(isTS ? typeexpr : expression, classNameAfter);\n    }\n    if (type == \"{\") return cont(pushlex(\"}\"), classBody, poplex);\n  }\n  function classBody(type, value) {\n    if (type == \"async\" ||\n        (type == \"variable\" &&\n         (value == \"static\" || value == \"get\" || value == \"set\" || (isTS && isModifier(value))) &&\n         cx.stream.match(/^\\s+[\\w$\\xa1-\\uffff]/, false))) {\n      cx.marked = \"keyword\";\n      return cont(classBody);\n    }\n    if (type == \"variable\" || cx.style == \"keyword\") {\n      cx.marked = \"property\";\n      return cont(classfield, classBody);\n    }\n    if (type == \"number\" || type == \"string\") return cont(classfield, classBody);\n    if (type == \"[\")\n      return cont(expression, maybetype, expect(\"]\"), classfield, classBody)\n    if (value == \"*\") {\n      cx.marked = \"keyword\";\n      return cont(classBody);\n    }\n    if (isTS && type == \"(\") return pass(functiondecl, classBody)\n    if (type == \";\" || type == \",\") return cont(classBody);\n    if (type == \"}\") return cont();\n    if (value == \"@\") return cont(expression, classBody)\n  }\n  function classfield(type, value) {\n    if (value == \"?\") return cont(classfield)\n    if (type == \":\") return cont(typeexpr, maybeAssign)\n    if (value == \"=\") return cont(expressionNoComma)\n    var context = cx.state.lexical.prev, isInterface = context && context.info == \"interface\"\n    return pass(isInterface ? functiondecl : functiondef)\n  }\n  function afterExport(type, value) {\n    if (value == \"*\") { cx.marked = \"keyword\"; return cont(maybeFrom, expect(\";\")); }\n    if (value == \"default\") { cx.marked = \"keyword\"; return cont(expression, expect(\";\")); }\n    if (type == \"{\") return cont(commasep(exportField, \"}\"), maybeFrom, expect(\";\"));\n    return pass(statement);\n  }\n  function exportField(type, value) {\n    if (value == \"as\") { cx.marked = \"keyword\"; return cont(expect(\"variable\")); }\n    if (type == \"variable\") return pass(expressionNoComma, exportField);\n  }\n  function afterImport(type) {\n    if (type == \"string\") return cont();\n    if (type == \"(\") return pass(expression);\n    return pass(importSpec, maybeMoreImports, maybeFrom);\n  }\n  function importSpec(type, value) {\n    if (type == \"{\") return contCommasep(importSpec, \"}\");\n    if (type == \"variable\") register(value);\n    if (value == \"*\") cx.marked = \"keyword\";\n    return cont(maybeAs);\n  }\n  function maybeMoreImports(type) {\n    if (type == \",\") return cont(importSpec, maybeMoreImports)\n  }\n  function maybeAs(_type, value) {\n    if (value == \"as\") { cx.marked = \"keyword\"; return cont(importSpec); }\n  }\n  function maybeFrom(_type, value) {\n    if (value == \"from\") { cx.marked = \"keyword\"; return cont(expression); }\n  }\n  function arrayLiteral(type) {\n    if (type == \"]\") return cont();\n    return pass(commasep(expressionNoComma, \"]\"));\n  }\n  function enumdef() {\n    return pass(pushlex(\"form\"), pattern, expect(\"{\"), pushlex(\"}\"), commasep(enummember, \"}\"), poplex, poplex)\n  }\n  function enummember() {\n    return pass(pattern, maybeAssign);\n  }\n\n  function isContinuedStatement(state, textAfter) {\n    return state.lastType == \"operator\" || state.lastType == \",\" ||\n      isOperatorChar.test(textAfter.charAt(0)) ||\n      /[,.]/.test(textAfter.charAt(0));\n  }\n\n  function expressionAllowed(stream, state, backUp) {\n    return state.tokenize == tokenBase &&\n      /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\\[{}\\(,;:]|=>)$/.test(state.lastType) ||\n      (state.lastType == \"quasi\" && /\\{\\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))\n  }\n\n  // Interface\n\n  return {\n    startState: function(basecolumn) {\n      var state = {\n        tokenize: tokenBase,\n        lastType: \"sof\",\n        cc: [],\n        lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, \"block\", false),\n        localVars: parserConfig.localVars,\n        context: parserConfig.localVars && new Context(null, null, false),\n        indented: basecolumn || 0\n      };\n      if (parserConfig.globalVars && typeof parserConfig.globalVars == \"object\")\n        state.globalVars = parserConfig.globalVars;\n      return state;\n    },\n\n    token: function(stream, state) {\n      if (stream.sol()) {\n        if (!state.lexical.hasOwnProperty(\"align\"))\n          state.lexical.align = false;\n        state.indented = stream.indentation();\n        findFatArrow(stream, state);\n      }\n      if (state.tokenize != tokenComment && stream.eatSpace()) return null;\n      var style = state.tokenize(stream, state);\n      if (type == \"comment\") return style;\n      state.lastType = type == \"operator\" && (content == \"++\" || content == \"--\") ? \"incdec\" : type;\n      return parseJS(state, style, type, content, stream);\n    },\n\n    indent: function(state, textAfter) {\n      if (state.tokenize == tokenComment) return CodeMirror.Pass;\n      if (state.tokenize != tokenBase) return 0;\n      var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top\n      // Kludge to prevent 'maybelse' from blocking lexical scope pops\n      if (!/^\\s*else\\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {\n        var c = state.cc[i];\n        if (c == poplex) lexical = lexical.prev;\n        else if (c != maybeelse) break;\n      }\n      while ((lexical.type == \"stat\" || lexical.type == \"form\") &&\n             (firstChar == \"}\" || ((top = state.cc[state.cc.length - 1]) &&\n                                   (top == maybeoperatorComma || top == maybeoperatorNoComma) &&\n                                   !/^[,\\.=+\\-*:?[\\(]/.test(textAfter))))\n        lexical = lexical.prev;\n      if (statementIndent && lexical.type == \")\" && lexical.prev.type == \"stat\")\n        lexical = lexical.prev;\n      var type = lexical.type, closing = firstChar == type;\n\n      if (type == \"vardef\") return lexical.indented + (state.lastType == \"operator\" || state.lastType == \",\" ? lexical.info.length + 1 : 0);\n      else if (type == \"form\" && firstChar == \"{\") return lexical.indented;\n      else if (type == \"form\") return lexical.indented + indentUnit;\n      else if (type == \"stat\")\n        return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);\n      else if (lexical.info == \"switch\" && !closing && parserConfig.doubleIndentSwitch != false)\n        return lexical.indented + (/^(?:case|default)\\b/.test(textAfter) ? indentUnit : 2 * indentUnit);\n      else if (lexical.align) return lexical.column + (closing ? 0 : 1);\n      else return lexical.indented + (closing ? 0 : indentUnit);\n    },\n\n    electricInput: /^\\s*(?:case .*?:|default:|\\{|\\})$/,\n    blockCommentStart: jsonMode ? null : \"/*\",\n    blockCommentEnd: jsonMode ? null : \"*/\",\n    blockCommentContinue: jsonMode ? null : \" * \",\n    lineComment: jsonMode ? null : \"//\",\n    fold: \"brace\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n\n    helperType: jsonMode ? \"json\" : \"javascript\",\n    jsonldMode: jsonldMode,\n    jsonMode: jsonMode,\n\n    expressionAllowed: expressionAllowed,\n\n    skipExpression: function(state) {\n      var top = state.cc[state.cc.length - 1]\n      if (top == expression || top == expressionNoComma) state.cc.pop()\n    }\n  };\n});\n\nCodeMirror.registerHelper(\"wordChars\", \"javascript\", /[\\w$]/);\n\nCodeMirror.defineMIME(\"text/javascript\", \"javascript\");\nCodeMirror.defineMIME(\"text/ecmascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/javascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/x-javascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/ecmascript\", \"javascript\");\nCodeMirror.defineMIME(\"application/json\", {name: \"javascript\", json: true});\nCodeMirror.defineMIME(\"application/x-json\", {name: \"javascript\", json: true});\nCodeMirror.defineMIME(\"application/ld+json\", {name: \"javascript\", jsonld: true});\nCodeMirror.defineMIME(\"text/typescript\", { name: \"javascript\", typescript: true });\nCodeMirror.defineMIME(\"application/typescript\", { name: \"javascript\", typescript: true });\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/markdown.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../xml/xml\"), require(\"../meta\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../xml/xml\", \"../meta\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineMode(\"markdown\", function(cmCfg, modeCfg) {\n\n  var htmlMode = CodeMirror.getMode(cmCfg, \"text/html\");\n  var htmlModeMissing = htmlMode.name == \"null\"\n\n  function getMode(name) {\n    if (CodeMirror.findModeByName) {\n      var found = CodeMirror.findModeByName(name);\n      if (found) name = found.mime || found.mimes[0];\n    }\n    var mode = CodeMirror.getMode(cmCfg, name);\n    return mode.name == \"null\" ? null : mode;\n  }\n\n  // Should characters that affect highlighting be highlighted separate?\n  // Does not include characters that will be output (such as `1.` and `-` for lists)\n  if (modeCfg.highlightFormatting === undefined)\n    modeCfg.highlightFormatting = false;\n\n  // Maximum number of nested blockquotes. Set to 0 for infinite nesting.\n  // Excess `>` will emit `error` token.\n  if (modeCfg.maxBlockquoteDepth === undefined)\n    modeCfg.maxBlockquoteDepth = 0;\n\n  // Turn on task lists? (\"- [ ] \" and \"- [x] \")\n  if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;\n\n  // Turn on strikethrough syntax\n  if (modeCfg.strikethrough === undefined)\n    modeCfg.strikethrough = false;\n\n  if (modeCfg.emoji === undefined)\n    modeCfg.emoji = false;\n\n  if (modeCfg.fencedCodeBlockHighlighting === undefined)\n    modeCfg.fencedCodeBlockHighlighting = true;\n\n  if (modeCfg.fencedCodeBlockDefaultMode === undefined)\n    modeCfg.fencedCodeBlockDefaultMode = 'text/plain';\n\n  if (modeCfg.xml === undefined)\n    modeCfg.xml = true;\n\n  // Allow token types to be overridden by user-provided token types.\n  if (modeCfg.tokenTypeOverrides === undefined)\n    modeCfg.tokenTypeOverrides = {};\n\n  var tokenTypes = {\n    header: \"header\",\n    code: \"comment\",\n    quote: \"quote\",\n    list1: \"variable-2\",\n    list2: \"variable-3\",\n    list3: \"keyword\",\n    hr: \"hr\",\n    image: \"image\",\n    imageAltText: \"image-alt-text\",\n    imageMarker: \"image-marker\",\n    formatting: \"formatting\",\n    linkInline: \"link\",\n    linkEmail: \"link\",\n    linkText: \"link\",\n    linkHref: \"string\",\n    em: \"em\",\n    strong: \"strong\",\n    strikethrough: \"strikethrough\",\n    emoji: \"builtin\"\n  };\n\n  for (var tokenType in tokenTypes) {\n    if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {\n      tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];\n    }\n  }\n\n  var hrRE = /^([*\\-_])(?:\\s*\\1){2,}\\s*$/\n  ,   listRE = /^(?:[*\\-+]|^[0-9]+([.)]))\\s+/\n  ,   taskListRE = /^\\[(x| )\\](?=\\s)/i // Must follow listRE\n  ,   atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/\n  ,   setextHeaderRE = /^ {0,3}(?:\\={1,}|-{2,})\\s*$/\n  ,   textRE = /^[^#!\\[\\]*_\\\\<>` \"'(~:]+/\n  ,   fencedCodeRE = /^(~~~+|```+)[ \\t]*([\\w\\/+#-]*)[^\\n`]*$/\n  ,   linkDefRE = /^\\s*\\[[^\\]]+?\\]:.*$/ // naive link-definition\n  ,   punctuation = /[!\"#$%&'()*+,\\-.\\/:;<=>?@\\[\\\\\\]^_`{|}~\\xA1\\xA7\\xAB\\xB6\\xB7\\xBB\\xBF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2308-\\u230B\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E42\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA8FC\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]|\\uD800[\\uDD00-\\uDD02\\uDF9F\\uDFD0]|\\uD801\\uDD6F|\\uD802[\\uDC57\\uDD1F\\uDD3F\\uDE50-\\uDE58\\uDE7F\\uDEF0-\\uDEF6\\uDF39-\\uDF3F\\uDF99-\\uDF9C]|\\uD804[\\uDC47-\\uDC4D\\uDCBB\\uDCBC\\uDCBE-\\uDCC1\\uDD40-\\uDD43\\uDD74\\uDD75\\uDDC5-\\uDDC9\\uDDCD\\uDDDB\\uDDDD-\\uDDDF\\uDE38-\\uDE3D\\uDEA9]|\\uD805[\\uDCC6\\uDDC1-\\uDDD7\\uDE41-\\uDE43\\uDF3C-\\uDF3E]|\\uD809[\\uDC70-\\uDC74]|\\uD81A[\\uDE6E\\uDE6F\\uDEF5\\uDF37-\\uDF3B\\uDF44]|\\uD82F\\uDC9F|\\uD836[\\uDE87-\\uDE8B]/\n  ,   expandedTab = \"    \" // CommonMark specifies tab as 4 spaces\n\n  function switchInline(stream, state, f) {\n    state.f = state.inline = f;\n    return f(stream, state);\n  }\n\n  function switchBlock(stream, state, f) {\n    state.f = state.block = f;\n    return f(stream, state);\n  }\n\n  function lineIsEmpty(line) {\n    return !line || !/\\S/.test(line.string)\n  }\n\n  // Blocks\n\n  function blankLine(state) {\n    // Reset linkTitle state\n    state.linkTitle = false;\n    state.linkHref = false;\n    state.linkText = false;\n    // Reset EM state\n    state.em = false;\n    // Reset STRONG state\n    state.strong = false;\n    // Reset strikethrough state\n    state.strikethrough = false;\n    // Reset state.quote\n    state.quote = 0;\n    // Reset state.indentedCode\n    state.indentedCode = false;\n    if (state.f == htmlBlock) {\n      var exit = htmlModeMissing\n      if (!exit) {\n        var inner = CodeMirror.innerMode(htmlMode, state.htmlState)\n        exit = inner.mode.name == \"xml\" && inner.state.tagStart === null &&\n          (!inner.state.context && inner.state.tokenize.isInText)\n      }\n      if (exit) {\n        state.f = inlineNormal;\n        state.block = blockNormal;\n        state.htmlState = null;\n      }\n    }\n    // Reset state.trailingSpace\n    state.trailingSpace = 0;\n    state.trailingSpaceNewLine = false;\n    // Mark this line as blank\n    state.prevLine = state.thisLine\n    state.thisLine = {stream: null}\n    return null;\n  }\n\n  function blockNormal(stream, state) {\n    var firstTokenOnLine = stream.column() === state.indentation;\n    var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);\n    var prevLineIsIndentedCode = state.indentedCode;\n    var prevLineIsHr = state.prevLine.hr;\n    var prevLineIsList = state.list !== false;\n    var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3;\n\n    state.indentedCode = false;\n\n    var lineIndentation = state.indentation;\n    // compute once per line (on first token)\n    if (state.indentationDiff === null) {\n      state.indentationDiff = state.indentation;\n      if (prevLineIsList) {\n        state.list = null;\n        // While this list item's marker's indentation is less than the deepest\n        //  list item's content's indentation,pop the deepest list item\n        //  indentation off the stack, and update block indentation state\n        while (lineIndentation < state.listStack[state.listStack.length - 1]) {\n          state.listStack.pop();\n          if (state.listStack.length) {\n            state.indentation = state.listStack[state.listStack.length - 1];\n          // less than the first list's indent -> the line is no longer a list\n          } else {\n            state.list = false;\n          }\n        }\n        if (state.list !== false) {\n          state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1]\n        }\n      }\n    }\n\n    // not comprehensive (currently only for setext detection purposes)\n    var allowsInlineContinuation = (\n        !prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header &&\n        (!prevLineIsList || !prevLineIsIndentedCode) &&\n        !state.prevLine.fencedCodeEnd\n    );\n\n    var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) &&\n      state.indentation <= maxNonCodeIndentation && stream.match(hrRE);\n\n    var match = null;\n    if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||\n         state.prevLine.header || prevLineLineIsEmpty)) {\n      stream.skipToEnd();\n      state.indentedCode = true;\n      return tokenTypes.code;\n    } else if (stream.eatSpace()) {\n      return null;\n    } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) {\n      state.quote = 0;\n      state.header = match[1].length;\n      state.thisLine.header = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"header\";\n      state.f = state.inline;\n      return getType(state);\n    } else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) {\n      state.quote = firstTokenOnLine ? 1 : state.quote + 1;\n      if (modeCfg.highlightFormatting) state.formatting = \"quote\";\n      stream.eatSpace();\n      return getType(state);\n    } else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) {\n      var listType = match[1] ? \"ol\" : \"ul\";\n\n      state.indentation = lineIndentation + stream.current().length;\n      state.list = true;\n      state.quote = 0;\n\n      // Add this list item's content's indentation to the stack\n      state.listStack.push(state.indentation);\n      // Reset inline styles which shouldn't propagate aross list items\n      state.em = false;\n      state.strong = false;\n      state.code = false;\n      state.strikethrough = false;\n\n      if (modeCfg.taskLists && stream.match(taskListRE, false)) {\n        state.taskList = true;\n      }\n      state.f = state.inline;\n      if (modeCfg.highlightFormatting) state.formatting = [\"list\", \"list-\" + listType];\n      return getType(state);\n    } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {\n      state.quote = 0;\n      state.fencedEndRE = new RegExp(match[1] + \"+ *$\");\n      // try switching mode\n      state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2] || modeCfg.fencedCodeBlockDefaultMode );\n      if (state.localMode) state.localState = CodeMirror.startState(state.localMode);\n      state.f = state.block = local;\n      if (modeCfg.highlightFormatting) state.formatting = \"code-block\";\n      state.code = -1\n      return getType(state);\n    // SETEXT has lowest block-scope precedence after HR, so check it after\n    //  the others (code, blockquote, list...)\n    } else if (\n      // if setext set, indicates line after ---/===\n      state.setext || (\n        // line before ---/===\n        (!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false &&\n        !state.code && !isHr && !linkDefRE.test(stream.string) &&\n        (match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE))\n      )\n    ) {\n      if ( !state.setext ) {\n        state.header = match[0].charAt(0) == '=' ? 1 : 2;\n        state.setext = state.header;\n      } else {\n        state.header = state.setext;\n        // has no effect on type so we can reset it now\n        state.setext = 0;\n        stream.skipToEnd();\n        if (modeCfg.highlightFormatting) state.formatting = \"header\";\n      }\n      state.thisLine.header = true;\n      state.f = state.inline;\n      return getType(state);\n    } else if (isHr) {\n      stream.skipToEnd();\n      state.hr = true;\n      state.thisLine.hr = true;\n      return tokenTypes.hr;\n    } else if (stream.peek() === '[') {\n      return switchInline(stream, state, footnoteLink);\n    }\n\n    return switchInline(stream, state, state.inline);\n  }\n\n  function htmlBlock(stream, state) {\n    var style = htmlMode.token(stream, state.htmlState);\n    if (!htmlModeMissing) {\n      var inner = CodeMirror.innerMode(htmlMode, state.htmlState)\n      if ((inner.mode.name == \"xml\" && inner.state.tagStart === null &&\n           (!inner.state.context && inner.state.tokenize.isInText)) ||\n          (state.md_inside && stream.current().indexOf(\">\") > -1)) {\n        state.f = inlineNormal;\n        state.block = blockNormal;\n        state.htmlState = null;\n      }\n    }\n    return style;\n  }\n\n  function local(stream, state) {\n    var currListInd = state.listStack[state.listStack.length - 1] || 0;\n    var hasExitedList = state.indentation < currListInd;\n    var maxFencedEndInd = currListInd + 3;\n    if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) {\n      if (modeCfg.highlightFormatting) state.formatting = \"code-block\";\n      var returnType;\n      if (!hasExitedList) returnType = getType(state)\n      state.localMode = state.localState = null;\n      state.block = blockNormal;\n      state.f = inlineNormal;\n      state.fencedEndRE = null;\n      state.code = 0\n      state.thisLine.fencedCodeEnd = true;\n      if (hasExitedList) return switchBlock(stream, state, state.block);\n      return returnType;\n    } else if (state.localMode) {\n      return state.localMode.token(stream, state.localState);\n    } else {\n      stream.skipToEnd();\n      return tokenTypes.code;\n    }\n  }\n\n  // Inline\n  function getType(state) {\n    var styles = [];\n\n    if (state.formatting) {\n      styles.push(tokenTypes.formatting);\n\n      if (typeof state.formatting === \"string\") state.formatting = [state.formatting];\n\n      for (var i = 0; i < state.formatting.length; i++) {\n        styles.push(tokenTypes.formatting + \"-\" + state.formatting[i]);\n\n        if (state.formatting[i] === \"header\") {\n          styles.push(tokenTypes.formatting + \"-\" + state.formatting[i] + \"-\" + state.header);\n        }\n\n        // Add `formatting-quote` and `formatting-quote-#` for blockquotes\n        // Add `error` instead if the maximum blockquote nesting depth is passed\n        if (state.formatting[i] === \"quote\") {\n          if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {\n            styles.push(tokenTypes.formatting + \"-\" + state.formatting[i] + \"-\" + state.quote);\n          } else {\n            styles.push(\"error\");\n          }\n        }\n      }\n    }\n\n    if (state.taskOpen) {\n      styles.push(\"meta\");\n      return styles.length ? styles.join(' ') : null;\n    }\n    if (state.taskClosed) {\n      styles.push(\"property\");\n      return styles.length ? styles.join(' ') : null;\n    }\n\n    if (state.linkHref) {\n      styles.push(tokenTypes.linkHref, \"url\");\n    } else { // Only apply inline styles to non-url text\n      if (state.strong) { styles.push(tokenTypes.strong); }\n      if (state.em) { styles.push(tokenTypes.em); }\n      if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }\n      if (state.emoji) { styles.push(tokenTypes.emoji); }\n      if (state.linkText) { styles.push(tokenTypes.linkText); }\n      if (state.code) { styles.push(tokenTypes.code); }\n      if (state.image) { styles.push(tokenTypes.image); }\n      if (state.imageAltText) { styles.push(tokenTypes.imageAltText, \"link\"); }\n      if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }\n    }\n\n    if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + \"-\" + state.header); }\n\n    if (state.quote) {\n      styles.push(tokenTypes.quote);\n\n      // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth\n      if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {\n        styles.push(tokenTypes.quote + \"-\" + state.quote);\n      } else {\n        styles.push(tokenTypes.quote + \"-\" + modeCfg.maxBlockquoteDepth);\n      }\n    }\n\n    if (state.list !== false) {\n      var listMod = (state.listStack.length - 1) % 3;\n      if (!listMod) {\n        styles.push(tokenTypes.list1);\n      } else if (listMod === 1) {\n        styles.push(tokenTypes.list2);\n      } else {\n        styles.push(tokenTypes.list3);\n      }\n    }\n\n    if (state.trailingSpaceNewLine) {\n      styles.push(\"trailing-space-new-line\");\n    } else if (state.trailingSpace) {\n      styles.push(\"trailing-space-\" + (state.trailingSpace % 2 ? \"a\" : \"b\"));\n    }\n\n    return styles.length ? styles.join(' ') : null;\n  }\n\n  function handleText(stream, state) {\n    if (stream.match(textRE, true)) {\n      return getType(state);\n    }\n    return undefined;\n  }\n\n  function inlineNormal(stream, state) {\n    var style = state.text(stream, state);\n    if (typeof style !== 'undefined')\n      return style;\n\n    if (state.list) { // List marker (*, +, -, 1., etc)\n      state.list = null;\n      return getType(state);\n    }\n\n    if (state.taskList) {\n      var taskOpen = stream.match(taskListRE, true)[1] === \" \";\n      if (taskOpen) state.taskOpen = true;\n      else state.taskClosed = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"task\";\n      state.taskList = false;\n      return getType(state);\n    }\n\n    state.taskOpen = false;\n    state.taskClosed = false;\n\n    if (state.header && stream.match(/^#+$/, true)) {\n      if (modeCfg.highlightFormatting) state.formatting = \"header\";\n      return getType(state);\n    }\n\n    var ch = stream.next();\n\n    // Matches link titles present on next line\n    if (state.linkTitle) {\n      state.linkTitle = false;\n      var matchCh = ch;\n      if (ch === '(') {\n        matchCh = ')';\n      }\n      matchCh = (matchCh+'').replace(/([.?*+^\\[\\]\\\\(){}|-])/g, \"\\\\$1\");\n      var regex = '^\\\\s*(?:[^' + matchCh + '\\\\\\\\]+|\\\\\\\\\\\\\\\\|\\\\\\\\.)' + matchCh;\n      if (stream.match(new RegExp(regex), true)) {\n        return tokenTypes.linkHref;\n      }\n    }\n\n    // If this block is changed, it may need to be updated in GFM mode\n    if (ch === '`') {\n      var previousFormatting = state.formatting;\n      if (modeCfg.highlightFormatting) state.formatting = \"code\";\n      stream.eatWhile('`');\n      var count = stream.current().length\n      if (state.code == 0 && (!state.quote || count == 1)) {\n        state.code = count\n        return getType(state)\n      } else if (count == state.code) { // Must be exact\n        var t = getType(state)\n        state.code = 0\n        return t\n      } else {\n        state.formatting = previousFormatting\n        return getType(state)\n      }\n    } else if (state.code) {\n      return getType(state);\n    }\n\n    if (ch === '\\\\') {\n      stream.next();\n      if (modeCfg.highlightFormatting) {\n        var type = getType(state);\n        var formattingEscape = tokenTypes.formatting + \"-escape\";\n        return type ? type + \" \" + formattingEscape : formattingEscape;\n      }\n    }\n\n    if (ch === '!' && stream.match(/\\[[^\\]]*\\] ?(?:\\(|\\[)/, false)) {\n      state.imageMarker = true;\n      state.image = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"image\";\n      return getType(state);\n    }\n\n    if (ch === '[' && state.imageMarker && stream.match(/[^\\]]*\\](\\(.*?\\)| ?\\[.*?\\])/, false)) {\n      state.imageMarker = false;\n      state.imageAltText = true\n      if (modeCfg.highlightFormatting) state.formatting = \"image\";\n      return getType(state);\n    }\n\n    if (ch === ']' && state.imageAltText) {\n      if (modeCfg.highlightFormatting) state.formatting = \"image\";\n      var type = getType(state);\n      state.imageAltText = false;\n      state.image = false;\n      state.inline = state.f = linkHref;\n      return type;\n    }\n\n    if (ch === '[' && !state.image) {\n      if (state.linkText && stream.match(/^.*?\\]/)) return getType(state)\n      state.linkText = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      return getType(state);\n    }\n\n    if (ch === ']' && state.linkText) {\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      state.linkText = false;\n      state.inline = state.f = stream.match(/\\(.*?\\)| ?\\[.*?\\]/, false) ? linkHref : inlineNormal\n      return type;\n    }\n\n    if (ch === '<' && stream.match(/^(https?|ftps?):\\/\\/(?:[^\\\\>]|\\\\.)+>/, false)) {\n      state.f = state.inline = linkInline;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      if (type){\n        type += \" \";\n      } else {\n        type = \"\";\n      }\n      return type + tokenTypes.linkInline;\n    }\n\n    if (ch === '<' && stream.match(/^[^> \\\\]+@(?:[^\\\\>]|\\\\.)+>/, false)) {\n      state.f = state.inline = linkInline;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      if (type){\n        type += \" \";\n      } else {\n        type = \"\";\n      }\n      return type + tokenTypes.linkEmail;\n    }\n\n    if (modeCfg.xml && ch === '<' && stream.match(/^(!--|\\?|!\\[CDATA\\[|[a-z][a-z0-9-]*(?:\\s+[a-z_:.\\-]+(?:\\s*=\\s*[^>]+)?)*\\s*(?:>|$))/i, false)) {\n      var end = stream.string.indexOf(\">\", stream.pos);\n      if (end != -1) {\n        var atts = stream.string.substring(stream.start, end);\n        if (/markdown\\s*=\\s*('|\"){0,1}1('|\"){0,1}/.test(atts)) state.md_inside = true;\n      }\n      stream.backUp(1);\n      state.htmlState = CodeMirror.startState(htmlMode);\n      return switchBlock(stream, state, htmlBlock);\n    }\n\n    if (modeCfg.xml && ch === '<' && stream.match(/^\\/\\w*?>/)) {\n      state.md_inside = false;\n      return \"tag\";\n    } else if (ch === \"*\" || ch === \"_\") {\n      var len = 1, before = stream.pos == 1 ? \" \" : stream.string.charAt(stream.pos - 2)\n      while (len < 3 && stream.eat(ch)) len++\n      var after = stream.peek() || \" \"\n      // See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis\n      var leftFlanking = !/\\s/.test(after) && (!punctuation.test(after) || /\\s/.test(before) || punctuation.test(before))\n      var rightFlanking = !/\\s/.test(before) && (!punctuation.test(before) || /\\s/.test(after) || punctuation.test(after))\n      var setEm = null, setStrong = null\n      if (len % 2) { // Em\n        if (!state.em && leftFlanking && (ch === \"*\" || !rightFlanking || punctuation.test(before)))\n          setEm = true\n        else if (state.em == ch && rightFlanking && (ch === \"*\" || !leftFlanking || punctuation.test(after)))\n          setEm = false\n      }\n      if (len > 1) { // Strong\n        if (!state.strong && leftFlanking && (ch === \"*\" || !rightFlanking || punctuation.test(before)))\n          setStrong = true\n        else if (state.strong == ch && rightFlanking && (ch === \"*\" || !leftFlanking || punctuation.test(after)))\n          setStrong = false\n      }\n      if (setStrong != null || setEm != null) {\n        if (modeCfg.highlightFormatting) state.formatting = setEm == null ? \"strong\" : setStrong == null ? \"em\" : \"strong em\"\n        if (setEm === true) state.em = ch\n        if (setStrong === true) state.strong = ch\n        var t = getType(state)\n        if (setEm === false) state.em = false\n        if (setStrong === false) state.strong = false\n        return t\n      }\n    } else if (ch === ' ') {\n      if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces\n        if (stream.peek() === ' ') { // Surrounded by spaces, ignore\n          return getType(state);\n        } else { // Not surrounded by spaces, back up pointer\n          stream.backUp(1);\n        }\n      }\n    }\n\n    if (modeCfg.strikethrough) {\n      if (ch === '~' && stream.eatWhile(ch)) {\n        if (state.strikethrough) {// Remove strikethrough\n          if (modeCfg.highlightFormatting) state.formatting = \"strikethrough\";\n          var t = getType(state);\n          state.strikethrough = false;\n          return t;\n        } else if (stream.match(/^[^\\s]/, false)) {// Add strikethrough\n          state.strikethrough = true;\n          if (modeCfg.highlightFormatting) state.formatting = \"strikethrough\";\n          return getType(state);\n        }\n      } else if (ch === ' ') {\n        if (stream.match(/^~~/, true)) { // Probably surrounded by space\n          if (stream.peek() === ' ') { // Surrounded by spaces, ignore\n            return getType(state);\n          } else { // Not surrounded by spaces, back up pointer\n            stream.backUp(2);\n          }\n        }\n      }\n    }\n\n    if (modeCfg.emoji && ch === \":\" && stream.match(/^(?:[a-z_\\d+][a-z_\\d+-]*|\\-[a-z_\\d+][a-z_\\d+-]*):/)) {\n      state.emoji = true;\n      if (modeCfg.highlightFormatting) state.formatting = \"emoji\";\n      var retType = getType(state);\n      state.emoji = false;\n      return retType;\n    }\n\n    if (ch === ' ') {\n      if (stream.match(/^ +$/, false)) {\n        state.trailingSpace++;\n      } else if (state.trailingSpace) {\n        state.trailingSpaceNewLine = true;\n      }\n    }\n\n    return getType(state);\n  }\n\n  function linkInline(stream, state) {\n    var ch = stream.next();\n\n    if (ch === \">\") {\n      state.f = state.inline = inlineNormal;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var type = getType(state);\n      if (type){\n        type += \" \";\n      } else {\n        type = \"\";\n      }\n      return type + tokenTypes.linkInline;\n    }\n\n    stream.match(/^[^>]+/, true);\n\n    return tokenTypes.linkInline;\n  }\n\n  function linkHref(stream, state) {\n    // Check if space, and return NULL if so (to avoid marking the space)\n    if(stream.eatSpace()){\n      return null;\n    }\n    var ch = stream.next();\n    if (ch === '(' || ch === '[') {\n      state.f = state.inline = getLinkHrefInside(ch === \"(\" ? \")\" : \"]\");\n      if (modeCfg.highlightFormatting) state.formatting = \"link-string\";\n      state.linkHref = true;\n      return getType(state);\n    }\n    return 'error';\n  }\n\n  var linkRE = {\n    \")\": /^(?:[^\\\\\\(\\)]|\\\\.|\\((?:[^\\\\\\(\\)]|\\\\.)*\\))*?(?=\\))/,\n    \"]\": /^(?:[^\\\\\\[\\]]|\\\\.|\\[(?:[^\\\\\\[\\]]|\\\\.)*\\])*?(?=\\])/\n  }\n\n  function getLinkHrefInside(endChar) {\n    return function(stream, state) {\n      var ch = stream.next();\n\n      if (ch === endChar) {\n        state.f = state.inline = inlineNormal;\n        if (modeCfg.highlightFormatting) state.formatting = \"link-string\";\n        var returnState = getType(state);\n        state.linkHref = false;\n        return returnState;\n      }\n\n      stream.match(linkRE[endChar])\n      state.linkHref = true;\n      return getType(state);\n    };\n  }\n\n  function footnoteLink(stream, state) {\n    if (stream.match(/^([^\\]\\\\]|\\\\.)*\\]:/, false)) {\n      state.f = footnoteLinkInside;\n      stream.next(); // Consume [\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      state.linkText = true;\n      return getType(state);\n    }\n    return switchInline(stream, state, inlineNormal);\n  }\n\n  function footnoteLinkInside(stream, state) {\n    if (stream.match(/^\\]:/, true)) {\n      state.f = state.inline = footnoteUrl;\n      if (modeCfg.highlightFormatting) state.formatting = \"link\";\n      var returnType = getType(state);\n      state.linkText = false;\n      return returnType;\n    }\n\n    stream.match(/^([^\\]\\\\]|\\\\.)+/, true);\n\n    return tokenTypes.linkText;\n  }\n\n  function footnoteUrl(stream, state) {\n    // Check if space, and return NULL if so (to avoid marking the space)\n    if(stream.eatSpace()){\n      return null;\n    }\n    // Match URL\n    stream.match(/^[^\\s]+/, true);\n    // Check for link title\n    if (stream.peek() === undefined) { // End of line, set flag to check next line\n      state.linkTitle = true;\n    } else { // More content on line, check if link title\n      stream.match(/^(?:\\s+(?:\"(?:[^\"\\\\]|\\\\\\\\|\\\\.)+\"|'(?:[^'\\\\]|\\\\\\\\|\\\\.)+'|\\((?:[^)\\\\]|\\\\\\\\|\\\\.)+\\)))?/, true);\n    }\n    state.f = state.inline = inlineNormal;\n    return tokenTypes.linkHref + \" url\";\n  }\n\n  var mode = {\n    startState: function() {\n      return {\n        f: blockNormal,\n\n        prevLine: {stream: null},\n        thisLine: {stream: null},\n\n        block: blockNormal,\n        htmlState: null,\n        indentation: 0,\n\n        inline: inlineNormal,\n        text: handleText,\n\n        formatting: false,\n        linkText: false,\n        linkHref: false,\n        linkTitle: false,\n        code: 0,\n        em: false,\n        strong: false,\n        header: 0,\n        setext: 0,\n        hr: false,\n        taskList: false,\n        list: false,\n        listStack: [],\n        quote: 0,\n        trailingSpace: 0,\n        trailingSpaceNewLine: false,\n        strikethrough: false,\n        emoji: false,\n        fencedEndRE: null\n      };\n    },\n\n    copyState: function(s) {\n      return {\n        f: s.f,\n\n        prevLine: s.prevLine,\n        thisLine: s.thisLine,\n\n        block: s.block,\n        htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),\n        indentation: s.indentation,\n\n        localMode: s.localMode,\n        localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,\n\n        inline: s.inline,\n        text: s.text,\n        formatting: false,\n        linkText: s.linkText,\n        linkTitle: s.linkTitle,\n        linkHref: s.linkHref,\n        code: s.code,\n        em: s.em,\n        strong: s.strong,\n        strikethrough: s.strikethrough,\n        emoji: s.emoji,\n        header: s.header,\n        setext: s.setext,\n        hr: s.hr,\n        taskList: s.taskList,\n        list: s.list,\n        listStack: s.listStack.slice(0),\n        quote: s.quote,\n        indentedCode: s.indentedCode,\n        trailingSpace: s.trailingSpace,\n        trailingSpaceNewLine: s.trailingSpaceNewLine,\n        md_inside: s.md_inside,\n        fencedEndRE: s.fencedEndRE\n      };\n    },\n\n    token: function(stream, state) {\n\n      // Reset state.formatting\n      state.formatting = false;\n\n      if (stream != state.thisLine.stream) {\n        state.header = 0;\n        state.hr = false;\n\n        if (stream.match(/^\\s*$/, true)) {\n          blankLine(state);\n          return null;\n        }\n\n        state.prevLine = state.thisLine\n        state.thisLine = {stream: stream}\n\n        // Reset state.taskList\n        state.taskList = false;\n\n        // Reset state.trailingSpace\n        state.trailingSpace = 0;\n        state.trailingSpaceNewLine = false;\n\n        if (!state.localState) {\n          state.f = state.block;\n          if (state.f != htmlBlock) {\n            var indentation = stream.match(/^\\s*/, true)[0].replace(/\\t/g, expandedTab).length;\n            state.indentation = indentation;\n            state.indentationDiff = null;\n            if (indentation > 0) return null;\n          }\n        }\n      }\n      return state.f(stream, state);\n    },\n\n    innerMode: function(state) {\n      if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};\n      if (state.localState) return {state: state.localState, mode: state.localMode};\n      return {state: state, mode: mode};\n    },\n\n    indent: function(state, textAfter, line) {\n      if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line)\n      if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line)\n      return CodeMirror.Pass\n    },\n\n    blankLine: blankLine,\n\n    getType: getType,\n\n    blockCommentStart: \"<!--\",\n    blockCommentEnd: \"-->\",\n    closeBrackets: \"()[]{}''\\\"\\\"``\",\n    fold: \"markdown\"\n  };\n  return mode;\n}, \"xml\");\n\nCodeMirror.defineMIME(\"text/markdown\", \"markdown\");\n\nCodeMirror.defineMIME(\"text/x-markdown\", \"markdown\");\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/python.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n  \"use strict\";\n\n  function wordRegexp(words) {\n    return new RegExp(\"^((\" + words.join(\")|(\") + \"))\\\\b\");\n  }\n\n  var wordOperators = wordRegexp([\"and\", \"or\", \"not\", \"is\"]);\n  var commonKeywords = [\"as\", \"assert\", \"break\", \"class\", \"continue\",\n                        \"def\", \"del\", \"elif\", \"else\", \"except\", \"finally\",\n                        \"for\", \"from\", \"global\", \"if\", \"import\",\n                        \"lambda\", \"pass\", \"raise\", \"return\",\n                        \"try\", \"while\", \"with\", \"yield\", \"in\"];\n  var commonBuiltins = [\"abs\", \"all\", \"any\", \"bin\", \"bool\", \"bytearray\", \"callable\", \"chr\",\n                        \"classmethod\", \"compile\", \"complex\", \"delattr\", \"dict\", \"dir\", \"divmod\",\n                        \"enumerate\", \"eval\", \"filter\", \"float\", \"format\", \"frozenset\",\n                        \"getattr\", \"globals\", \"hasattr\", \"hash\", \"help\", \"hex\", \"id\",\n                        \"input\", \"int\", \"isinstance\", \"issubclass\", \"iter\", \"len\",\n                        \"list\", \"locals\", \"map\", \"max\", \"memoryview\", \"min\", \"next\",\n                        \"object\", \"oct\", \"open\", \"ord\", \"pow\", \"property\", \"range\",\n                        \"repr\", \"reversed\", \"round\", \"set\", \"setattr\", \"slice\",\n                        \"sorted\", \"staticmethod\", \"str\", \"sum\", \"super\", \"tuple\",\n                        \"type\", \"vars\", \"zip\", \"__import__\", \"NotImplemented\",\n                        \"Ellipsis\", \"__debug__\"];\n  CodeMirror.registerHelper(\"hintWords\", \"python\", commonKeywords.concat(commonBuiltins));\n\n  function top(state) {\n    return state.scopes[state.scopes.length - 1];\n  }\n\n  CodeMirror.defineMode(\"python\", function(conf, parserConf) {\n    var ERRORCLASS = \"error\";\n\n    var delimiters = parserConf.delimiters || parserConf.singleDelimiters || /^[\\(\\)\\[\\]\\{\\}@,:`=;\\.\\\\]/;\n    //               (Backwards-compatibility with old, cumbersome config system)\n    var operators = [parserConf.singleOperators, parserConf.doubleOperators, parserConf.doubleDelimiters, parserConf.tripleDelimiters,\n                     parserConf.operators || /^([-+*/%\\/&|^]=?|[<>=]+|\\/\\/=?|\\*\\*=?|!=|[~!@]|\\.\\.\\.)/]\n    for (var i = 0; i < operators.length; i++) if (!operators[i]) operators.splice(i--, 1)\n\n    var hangingIndent = parserConf.hangingIndent || conf.indentUnit;\n\n    var myKeywords = commonKeywords, myBuiltins = commonBuiltins;\n    if (parserConf.extra_keywords != undefined)\n      myKeywords = myKeywords.concat(parserConf.extra_keywords);\n\n    if (parserConf.extra_builtins != undefined)\n      myBuiltins = myBuiltins.concat(parserConf.extra_builtins);\n\n    var py3 = !(parserConf.version && Number(parserConf.version) < 3)\n    if (py3) {\n      // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator\n      var identifiers = parserConf.identifiers|| /^[_A-Za-z\\u00A1-\\uFFFF][_A-Za-z0-9\\u00A1-\\uFFFF]*/;\n      myKeywords = myKeywords.concat([\"nonlocal\", \"False\", \"True\", \"None\", \"async\", \"await\"]);\n      myBuiltins = myBuiltins.concat([\"ascii\", \"bytes\", \"exec\", \"print\"]);\n      var stringPrefixes = new RegExp(\"^(([rbuf]|(br)|(fr))?('{3}|\\\"{3}|['\\\"]))\", \"i\");\n    } else {\n      var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/;\n      myKeywords = myKeywords.concat([\"exec\", \"print\"]);\n      myBuiltins = myBuiltins.concat([\"apply\", \"basestring\", \"buffer\", \"cmp\", \"coerce\", \"execfile\",\n                                      \"file\", \"intern\", \"long\", \"raw_input\", \"reduce\", \"reload\",\n                                      \"unichr\", \"unicode\", \"xrange\", \"False\", \"True\", \"None\"]);\n      var stringPrefixes = new RegExp(\"^(([rubf]|(ur)|(br))?('{3}|\\\"{3}|['\\\"]))\", \"i\");\n    }\n    var keywords = wordRegexp(myKeywords);\n    var builtins = wordRegexp(myBuiltins);\n\n    // tokenizers\n    function tokenBase(stream, state) {\n      var sol = stream.sol() && state.lastToken != \"\\\\\"\n      if (sol) state.indent = stream.indentation()\n      // Handle scope changes\n      if (sol && top(state).type == \"py\") {\n        var scopeOffset = top(state).offset;\n        if (stream.eatSpace()) {\n          var lineOffset = stream.indentation();\n          if (lineOffset > scopeOffset)\n            pushPyScope(state);\n          else if (lineOffset < scopeOffset && dedent(stream, state) && stream.peek() != \"#\")\n            state.errorToken = true;\n          return null;\n        } else {\n          var style = tokenBaseInner(stream, state);\n          if (scopeOffset > 0 && dedent(stream, state))\n            style += \" \" + ERRORCLASS;\n          return style;\n        }\n      }\n      return tokenBaseInner(stream, state);\n    }\n\n    function tokenBaseInner(stream, state, inFormat) {\n      if (stream.eatSpace()) return null;\n\n      // Handle Comments\n      if (!inFormat && stream.match(/^#.*/)) return \"comment\";\n\n      // Handle Number Literals\n      if (stream.match(/^[0-9\\.]/, false)) {\n        var floatLiteral = false;\n        // Floats\n        if (stream.match(/^[\\d_]*\\.\\d+(e[\\+\\-]?\\d+)?/i)) { floatLiteral = true; }\n        if (stream.match(/^[\\d_]+\\.\\d*/)) { floatLiteral = true; }\n        if (stream.match(/^\\.\\d+/)) { floatLiteral = true; }\n        if (floatLiteral) {\n          // Float literals may be \"imaginary\"\n          stream.eat(/J/i);\n          return \"number\";\n        }\n        // Integers\n        var intLiteral = false;\n        // Hex\n        if (stream.match(/^0x[0-9a-f_]+/i)) intLiteral = true;\n        // Binary\n        if (stream.match(/^0b[01_]+/i)) intLiteral = true;\n        // Octal\n        if (stream.match(/^0o[0-7_]+/i)) intLiteral = true;\n        // Decimal\n        if (stream.match(/^[1-9][\\d_]*(e[\\+\\-]?[\\d_]+)?/)) {\n          // Decimal literals may be \"imaginary\"\n          stream.eat(/J/i);\n          // TODO - Can you have imaginary longs?\n          intLiteral = true;\n        }\n        // Zero by itself with no other piece of number.\n        if (stream.match(/^0(?![\\dx])/i)) intLiteral = true;\n        if (intLiteral) {\n          // Integer literals may be \"long\"\n          stream.eat(/L/i);\n          return \"number\";\n        }\n      }\n\n      // Handle Strings\n      if (stream.match(stringPrefixes)) {\n        var isFmtString = stream.current().toLowerCase().indexOf('f') !== -1;\n        if (!isFmtString) {\n          state.tokenize = tokenStringFactory(stream.current(), state.tokenize);\n          return state.tokenize(stream, state);\n        } else {\n          state.tokenize = formatStringFactory(stream.current(), state.tokenize);\n          return state.tokenize(stream, state);\n        }\n      }\n\n      for (var i = 0; i < operators.length; i++)\n        if (stream.match(operators[i])) return \"operator\"\n\n      if (stream.match(delimiters)) return \"punctuation\";\n\n      if (state.lastToken == \".\" && stream.match(identifiers))\n        return \"property\";\n\n      if (stream.match(keywords) || stream.match(wordOperators))\n        return \"keyword\";\n\n      if (stream.match(builtins))\n        return \"builtin\";\n\n      if (stream.match(/^(self|cls)\\b/))\n        return \"variable-2\";\n\n      if (stream.match(identifiers)) {\n        if (state.lastToken == \"def\" || state.lastToken == \"class\")\n          return \"def\";\n        return \"variable\";\n      }\n\n      // Handle non-detected items\n      stream.next();\n      return inFormat ? null :ERRORCLASS;\n    }\n\n    function formatStringFactory(delimiter, tokenOuter) {\n      while (\"rubf\".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)\n        delimiter = delimiter.substr(1);\n\n      var singleline = delimiter.length == 1;\n      var OUTCLASS = \"string\";\n\n      function tokenNestedExpr(depth) {\n        return function(stream, state) {\n          var inner = tokenBaseInner(stream, state, true)\n          if (inner == \"punctuation\") {\n            if (stream.current() == \"{\") {\n              state.tokenize = tokenNestedExpr(depth + 1)\n            } else if (stream.current() == \"}\") {\n              if (depth > 1) state.tokenize = tokenNestedExpr(depth - 1)\n              else state.tokenize = tokenString\n            }\n          }\n          return inner\n        }\n      }\n\n      function tokenString(stream, state) {\n        while (!stream.eol()) {\n          stream.eatWhile(/[^'\"\\{\\}\\\\]/);\n          if (stream.eat(\"\\\\\")) {\n            stream.next();\n            if (singleline && stream.eol())\n              return OUTCLASS;\n          } else if (stream.match(delimiter)) {\n            state.tokenize = tokenOuter;\n            return OUTCLASS;\n          } else if (stream.match('{{')) {\n            // ignore {{ in f-str\n            return OUTCLASS;\n          } else if (stream.match('{', false)) {\n            // switch to nested mode\n            state.tokenize = tokenNestedExpr(0)\n            if (stream.current()) return OUTCLASS;\n            else return state.tokenize(stream, state)\n          } else if (stream.match('}}')) {\n            return OUTCLASS;\n          } else if (stream.match('}')) {\n            // single } in f-string is an error\n            return ERRORCLASS;\n          } else {\n            stream.eat(/['\"]/);\n          }\n        }\n        if (singleline) {\n          if (parserConf.singleLineStringErrors)\n            return ERRORCLASS;\n          else\n            state.tokenize = tokenOuter;\n        }\n        return OUTCLASS;\n      }\n      tokenString.isString = true;\n      return tokenString;\n    }\n\n    function tokenStringFactory(delimiter, tokenOuter) {\n      while (\"rubf\".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)\n        delimiter = delimiter.substr(1);\n\n      var singleline = delimiter.length == 1;\n      var OUTCLASS = \"string\";\n\n      function tokenString(stream, state) {\n        while (!stream.eol()) {\n          stream.eatWhile(/[^'\"\\\\]/);\n          if (stream.eat(\"\\\\\")) {\n            stream.next();\n            if (singleline && stream.eol())\n              return OUTCLASS;\n          } else if (stream.match(delimiter)) {\n            state.tokenize = tokenOuter;\n            return OUTCLASS;\n          } else {\n            stream.eat(/['\"]/);\n          }\n        }\n        if (singleline) {\n          if (parserConf.singleLineStringErrors)\n            return ERRORCLASS;\n          else\n            state.tokenize = tokenOuter;\n        }\n        return OUTCLASS;\n      }\n      tokenString.isString = true;\n      return tokenString;\n    }\n\n    function pushPyScope(state) {\n      while (top(state).type != \"py\") state.scopes.pop()\n      state.scopes.push({offset: top(state).offset + conf.indentUnit,\n                         type: \"py\",\n                         align: null})\n    }\n\n    function pushBracketScope(stream, state, type) {\n      var align = stream.match(/^([\\s\\[\\{\\(]|#.*)*$/, false) ? null : stream.column() + 1\n      state.scopes.push({offset: state.indent + hangingIndent,\n                         type: type,\n                         align: align})\n    }\n\n    function dedent(stream, state) {\n      var indented = stream.indentation();\n      while (state.scopes.length > 1 && top(state).offset > indented) {\n        if (top(state).type != \"py\") return true;\n        state.scopes.pop();\n      }\n      return top(state).offset != indented;\n    }\n\n    function tokenLexer(stream, state) {\n      if (stream.sol()) state.beginningOfLine = true;\n\n      var style = state.tokenize(stream, state);\n      var current = stream.current();\n\n      // Handle decorators\n      if (state.beginningOfLine && current == \"@\")\n        return stream.match(identifiers, false) ? \"meta\" : py3 ? \"operator\" : ERRORCLASS;\n\n      if (/\\S/.test(current)) state.beginningOfLine = false;\n\n      if ((style == \"variable\" || style == \"builtin\")\n          && state.lastToken == \"meta\")\n        style = \"meta\";\n\n      // Handle scope changes.\n      if (current == \"pass\" || current == \"return\")\n        state.dedent += 1;\n\n      if (current == \"lambda\") state.lambda = true;\n      if (current == \":\" && !state.lambda && top(state).type == \"py\")\n        pushPyScope(state);\n\n      if (current.length == 1 && !/string|comment/.test(style)) {\n        var delimiter_index = \"[({\".indexOf(current);\n        if (delimiter_index != -1)\n          pushBracketScope(stream, state, \"])}\".slice(delimiter_index, delimiter_index+1));\n\n        delimiter_index = \"])}\".indexOf(current);\n        if (delimiter_index != -1) {\n          if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent\n          else return ERRORCLASS;\n        }\n      }\n      if (state.dedent > 0 && stream.eol() && top(state).type == \"py\") {\n        if (state.scopes.length > 1) state.scopes.pop();\n        state.dedent -= 1;\n      }\n\n      return style;\n    }\n\n    var external = {\n      startState: function(basecolumn) {\n        return {\n          tokenize: tokenBase,\n          scopes: [{offset: basecolumn || 0, type: \"py\", align: null}],\n          indent: basecolumn || 0,\n          lastToken: null,\n          lambda: false,\n          dedent: 0\n        };\n      },\n\n      token: function(stream, state) {\n        var addErr = state.errorToken;\n        if (addErr) state.errorToken = false;\n        var style = tokenLexer(stream, state);\n\n        if (style && style != \"comment\")\n          state.lastToken = (style == \"keyword\" || style == \"punctuation\") ? stream.current() : style;\n        if (style == \"punctuation\") style = null;\n\n        if (stream.eol() && state.lambda)\n          state.lambda = false;\n        return addErr ? style + \" \" + ERRORCLASS : style;\n      },\n\n      indent: function(state, textAfter) {\n        if (state.tokenize != tokenBase)\n          return state.tokenize.isString ? CodeMirror.Pass : 0;\n\n        var scope = top(state), closing = scope.type == textAfter.charAt(0)\n        if (scope.align != null)\n          return scope.align - (closing ? 1 : 0)\n        else\n          return scope.offset - (closing ? hangingIndent : 0)\n      },\n\n      electricInput: /^\\s*[\\}\\]\\)]$/,\n      closeBrackets: {triples: \"'\\\"\"},\n      lineComment: \"#\",\n      fold: \"indent\"\n    };\n    return external;\n  });\n\n  CodeMirror.defineMIME(\"text/x-python\", \"python\");\n\n  var words = function(str) { return str.split(\" \"); };\n\n  CodeMirror.defineMIME(\"text/x-cython\", {\n    name: \"python\",\n    extra_keywords: words(\"by cdef cimport cpdef ctypedef enum except \"+\n                          \"extern gil include nogil property public \"+\n                          \"readonly struct union DEF IF ELIF ELSE\")\n  });\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/rust.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"), require(\"../../addon/mode/simple\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\", \"../../addon/mode/simple\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nCodeMirror.defineSimpleMode(\"rust\",{\n  start: [\n    // string and byte string\n    {regex: /b?\"/, token: \"string\", next: \"string\"},\n    // raw string and raw byte string\n    {regex: /b?r\"/, token: \"string\", next: \"string_raw\"},\n    {regex: /b?r#+\"/, token: \"string\", next: \"string_raw_hash\"},\n    // character\n    {regex: /'(?:[^'\\\\]|\\\\(?:[nrt0'\"]|x[\\da-fA-F]{2}|u\\{[\\da-fA-F]{6}\\}))'/, token: \"string-2\"},\n    // byte\n    {regex: /b'(?:[^']|\\\\(?:['\\\\nrt0]|x[\\da-fA-F]{2}))'/, token: \"string-2\"},\n\n    {regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/,\n     token: \"number\"},\n    {regex: /(let(?:\\s+mut)?|fn|enum|mod|struct|type|union)(\\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: [\"keyword\", null, \"def\"]},\n    {regex: /(?:abstract|alignof|as|async|await|box|break|continue|const|crate|do|dyn|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\\b/, token: \"keyword\"},\n    {regex: /\\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\\b/, token: \"atom\"},\n    {regex: /\\b(?:true|false|Some|None|Ok|Err)\\b/, token: \"builtin\"},\n    {regex: /\\b(fn)(\\s+)([a-zA-Z_][a-zA-Z0-9_]*)/,\n     token: [\"keyword\", null ,\"def\"]},\n    {regex: /#!?\\[.*\\]/, token: \"meta\"},\n    {regex: /\\/\\/.*/, token: \"comment\"},\n    {regex: /\\/\\*/, token: \"comment\", next: \"comment\"},\n    {regex: /[-+\\/*=<>!]+/, token: \"operator\"},\n    {regex: /[a-zA-Z_]\\w*!/,token: \"variable-3\"},\n    {regex: /[a-zA-Z_]\\w*/, token: \"variable\"},\n    {regex: /[\\{\\[\\(]/, indent: true},\n    {regex: /[\\}\\]\\)]/, dedent: true}\n  ],\n  string: [\n    {regex: /\"/, token: \"string\", next: \"start\"},\n    {regex: /(?:[^\\\\\"]|\\\\(?:.|$))*/, token: \"string\"}\n  ],\n  string_raw: [\n    {regex: /\"/, token: \"string\", next: \"start\"},\n    {regex: /[^\"]*/, token: \"string\"}\n  ],\n  string_raw_hash: [\n    {regex: /\"#+/, token: \"string\", next: \"start\"},\n    {regex: /(?:[^\"]|\"(?!#))*/, token: \"string\"}\n  ],\n  comment: [\n    {regex: /.*?\\*\\//, token: \"comment\", next: \"start\"},\n    {regex: /.*/, token: \"comment\"}\n  ],\n  meta: {\n    dontIndentStates: [\"comment\"],\n    electricInput: /^\\s*\\}$/,\n    blockCommentStart: \"/*\",\n    blockCommentEnd: \"*/\",\n    lineComment: \"//\",\n    fold: \"brace\"\n  }\n});\n\n\nCodeMirror.defineMIME(\"text/x-rustsrc\", \"rust\");\nCodeMirror.defineMIME(\"text/rust\", \"rust\");\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/codemirror/mode/xml.js",
    "content": "// CodeMirror, copyright (c) by Marijn Haverbeke and others\n// Distributed under an MIT license: https://codemirror.net/LICENSE\n\n(function(mod) {\n  if (typeof exports == \"object\" && typeof module == \"object\") // CommonJS\n    mod(require(\"../../lib/codemirror\"));\n  else if (typeof define == \"function\" && define.amd) // AMD\n    define([\"../../lib/codemirror\"], mod);\n  else // Plain browser env\n    mod(CodeMirror);\n})(function(CodeMirror) {\n\"use strict\";\n\nvar htmlConfig = {\n  autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,\n                    'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,\n                    'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,\n                    'track': true, 'wbr': true, 'menuitem': true},\n  implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,\n                     'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,\n                     'th': true, 'tr': true},\n  contextGrabbers: {\n    'dd': {'dd': true, 'dt': true},\n    'dt': {'dd': true, 'dt': true},\n    'li': {'li': true},\n    'option': {'option': true, 'optgroup': true},\n    'optgroup': {'optgroup': true},\n    'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,\n          'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,\n          'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,\n          'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,\n          'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},\n    'rp': {'rp': true, 'rt': true},\n    'rt': {'rp': true, 'rt': true},\n    'tbody': {'tbody': true, 'tfoot': true},\n    'td': {'td': true, 'th': true},\n    'tfoot': {'tbody': true},\n    'th': {'td': true, 'th': true},\n    'thead': {'tbody': true, 'tfoot': true},\n    'tr': {'tr': true}\n  },\n  doNotIndent: {\"pre\": true},\n  allowUnquoted: true,\n  allowMissing: true,\n  caseFold: true\n}\n\nvar xmlConfig = {\n  autoSelfClosers: {},\n  implicitlyClosed: {},\n  contextGrabbers: {},\n  doNotIndent: {},\n  allowUnquoted: false,\n  allowMissing: false,\n  allowMissingTagName: false,\n  caseFold: false\n}\n\nCodeMirror.defineMode(\"xml\", function(editorConf, config_) {\n  var indentUnit = editorConf.indentUnit\n  var config = {}\n  var defaults = config_.htmlMode ? htmlConfig : xmlConfig\n  for (var prop in defaults) config[prop] = defaults[prop]\n  for (var prop in config_) config[prop] = config_[prop]\n\n  // Return variables for tokenizers\n  var type, setStyle;\n\n  function inText(stream, state) {\n    function chain(parser) {\n      state.tokenize = parser;\n      return parser(stream, state);\n    }\n\n    var ch = stream.next();\n    if (ch == \"<\") {\n      if (stream.eat(\"!\")) {\n        if (stream.eat(\"[\")) {\n          if (stream.match(\"CDATA[\")) return chain(inBlock(\"atom\", \"]]>\"));\n          else return null;\n        } else if (stream.match(\"--\")) {\n          return chain(inBlock(\"comment\", \"-->\"));\n        } else if (stream.match(\"DOCTYPE\", true, true)) {\n          stream.eatWhile(/[\\w\\._\\-]/);\n          return chain(doctype(1));\n        } else {\n          return null;\n        }\n      } else if (stream.eat(\"?\")) {\n        stream.eatWhile(/[\\w\\._\\-]/);\n        state.tokenize = inBlock(\"meta\", \"?>\");\n        return \"meta\";\n      } else {\n        type = stream.eat(\"/\") ? \"closeTag\" : \"openTag\";\n        state.tokenize = inTag;\n        return \"tag bracket\";\n      }\n    } else if (ch == \"&\") {\n      var ok;\n      if (stream.eat(\"#\")) {\n        if (stream.eat(\"x\")) {\n          ok = stream.eatWhile(/[a-fA-F\\d]/) && stream.eat(\";\");\n        } else {\n          ok = stream.eatWhile(/[\\d]/) && stream.eat(\";\");\n        }\n      } else {\n        ok = stream.eatWhile(/[\\w\\.\\-:]/) && stream.eat(\";\");\n      }\n      return ok ? \"atom\" : \"error\";\n    } else {\n      stream.eatWhile(/[^&<]/);\n      return null;\n    }\n  }\n  inText.isInText = true;\n\n  function inTag(stream, state) {\n    var ch = stream.next();\n    if (ch == \">\" || (ch == \"/\" && stream.eat(\">\"))) {\n      state.tokenize = inText;\n      type = ch == \">\" ? \"endTag\" : \"selfcloseTag\";\n      return \"tag bracket\";\n    } else if (ch == \"=\") {\n      type = \"equals\";\n      return null;\n    } else if (ch == \"<\") {\n      state.tokenize = inText;\n      state.state = baseState;\n      state.tagName = state.tagStart = null;\n      var next = state.tokenize(stream, state);\n      return next ? next + \" tag error\" : \"tag error\";\n    } else if (/[\\'\\\"]/.test(ch)) {\n      state.tokenize = inAttribute(ch);\n      state.stringStartCol = stream.column();\n      return state.tokenize(stream, state);\n    } else {\n      stream.match(/^[^\\s\\u00a0=<>\\\"\\']*[^\\s\\u00a0=<>\\\"\\'\\/]/);\n      return \"word\";\n    }\n  }\n\n  function inAttribute(quote) {\n    var closure = function(stream, state) {\n      while (!stream.eol()) {\n        if (stream.next() == quote) {\n          state.tokenize = inTag;\n          break;\n        }\n      }\n      return \"string\";\n    };\n    closure.isInAttribute = true;\n    return closure;\n  }\n\n  function inBlock(style, terminator) {\n    return function(stream, state) {\n      while (!stream.eol()) {\n        if (stream.match(terminator)) {\n          state.tokenize = inText;\n          break;\n        }\n        stream.next();\n      }\n      return style;\n    }\n  }\n\n  function doctype(depth) {\n    return function(stream, state) {\n      var ch;\n      while ((ch = stream.next()) != null) {\n        if (ch == \"<\") {\n          state.tokenize = doctype(depth + 1);\n          return state.tokenize(stream, state);\n        } else if (ch == \">\") {\n          if (depth == 1) {\n            state.tokenize = inText;\n            break;\n          } else {\n            state.tokenize = doctype(depth - 1);\n            return state.tokenize(stream, state);\n          }\n        }\n      }\n      return \"meta\";\n    };\n  }\n\n  function Context(state, tagName, startOfLine) {\n    this.prev = state.context;\n    this.tagName = tagName;\n    this.indent = state.indented;\n    this.startOfLine = startOfLine;\n    if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))\n      this.noIndent = true;\n  }\n  function popContext(state) {\n    if (state.context) state.context = state.context.prev;\n  }\n  function maybePopContext(state, nextTagName) {\n    var parentTagName;\n    while (true) {\n      if (!state.context) {\n        return;\n      }\n      parentTagName = state.context.tagName;\n      if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||\n          !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {\n        return;\n      }\n      popContext(state);\n    }\n  }\n\n  function baseState(type, stream, state) {\n    if (type == \"openTag\") {\n      state.tagStart = stream.column();\n      return tagNameState;\n    } else if (type == \"closeTag\") {\n      return closeTagNameState;\n    } else {\n      return baseState;\n    }\n  }\n  function tagNameState(type, stream, state) {\n    if (type == \"word\") {\n      state.tagName = stream.current();\n      setStyle = \"tag\";\n      return attrState;\n    } else if (config.allowMissingTagName && type == \"endTag\") {\n      setStyle = \"tag bracket\";\n      return attrState(type, stream, state);\n    } else {\n      setStyle = \"error\";\n      return tagNameState;\n    }\n  }\n  function closeTagNameState(type, stream, state) {\n    if (type == \"word\") {\n      var tagName = stream.current();\n      if (state.context && state.context.tagName != tagName &&\n          config.implicitlyClosed.hasOwnProperty(state.context.tagName))\n        popContext(state);\n      if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {\n        setStyle = \"tag\";\n        return closeState;\n      } else {\n        setStyle = \"tag error\";\n        return closeStateErr;\n      }\n    } else if (config.allowMissingTagName && type == \"endTag\") {\n      setStyle = \"tag bracket\";\n      return closeState(type, stream, state);\n    } else {\n      setStyle = \"error\";\n      return closeStateErr;\n    }\n  }\n\n  function closeState(type, _stream, state) {\n    if (type != \"endTag\") {\n      setStyle = \"error\";\n      return closeState;\n    }\n    popContext(state);\n    return baseState;\n  }\n  function closeStateErr(type, stream, state) {\n    setStyle = \"error\";\n    return closeState(type, stream, state);\n  }\n\n  function attrState(type, _stream, state) {\n    if (type == \"word\") {\n      setStyle = \"attribute\";\n      return attrEqState;\n    } else if (type == \"endTag\" || type == \"selfcloseTag\") {\n      var tagName = state.tagName, tagStart = state.tagStart;\n      state.tagName = state.tagStart = null;\n      if (type == \"selfcloseTag\" ||\n          config.autoSelfClosers.hasOwnProperty(tagName)) {\n        maybePopContext(state, tagName);\n      } else {\n        maybePopContext(state, tagName);\n        state.context = new Context(state, tagName, tagStart == state.indented);\n      }\n      return baseState;\n    }\n    setStyle = \"error\";\n    return attrState;\n  }\n  function attrEqState(type, stream, state) {\n    if (type == \"equals\") return attrValueState;\n    if (!config.allowMissing) setStyle = \"error\";\n    return attrState(type, stream, state);\n  }\n  function attrValueState(type, stream, state) {\n    if (type == \"string\") return attrContinuedState;\n    if (type == \"word\" && config.allowUnquoted) {setStyle = \"string\"; return attrState;}\n    setStyle = \"error\";\n    return attrState(type, stream, state);\n  }\n  function attrContinuedState(type, stream, state) {\n    if (type == \"string\") return attrContinuedState;\n    return attrState(type, stream, state);\n  }\n\n  return {\n    startState: function(baseIndent) {\n      var state = {tokenize: inText,\n                   state: baseState,\n                   indented: baseIndent || 0,\n                   tagName: null, tagStart: null,\n                   context: null}\n      if (baseIndent != null) state.baseIndent = baseIndent\n      return state\n    },\n\n    token: function(stream, state) {\n      if (!state.tagName && stream.sol())\n        state.indented = stream.indentation();\n\n      if (stream.eatSpace()) return null;\n      type = null;\n      var style = state.tokenize(stream, state);\n      if ((style || type) && style != \"comment\") {\n        setStyle = null;\n        state.state = state.state(type || style, stream, state);\n        if (setStyle)\n          style = setStyle == \"error\" ? style + \" error\" : setStyle;\n      }\n      return style;\n    },\n\n    indent: function(state, textAfter, fullLine) {\n      var context = state.context;\n      // Indent multi-line strings (e.g. css).\n      if (state.tokenize.isInAttribute) {\n        if (state.tagStart == state.indented)\n          return state.stringStartCol + 1;\n        else\n          return state.indented + indentUnit;\n      }\n      if (context && context.noIndent) return CodeMirror.Pass;\n      if (state.tokenize != inTag && state.tokenize != inText)\n        return fullLine ? fullLine.match(/^(\\s*)/)[0].length : 0;\n      // Indent the starts of attribute names.\n      if (state.tagName) {\n        if (config.multilineTagIndentPastTag !== false)\n          return state.tagStart + state.tagName.length + 2;\n        else\n          return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);\n      }\n      if (config.alignCDATA && /<!\\[CDATA\\[/.test(textAfter)) return 0;\n      var tagAfter = textAfter && /^<(\\/)?([\\w_:\\.-]*)/.exec(textAfter);\n      if (tagAfter && tagAfter[1]) { // Closing tag spotted\n        while (context) {\n          if (context.tagName == tagAfter[2]) {\n            context = context.prev;\n            break;\n          } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {\n            context = context.prev;\n          } else {\n            break;\n          }\n        }\n      } else if (tagAfter) { // Opening tag spotted\n        while (context) {\n          var grabbers = config.contextGrabbers[context.tagName];\n          if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))\n            context = context.prev;\n          else\n            break;\n        }\n      }\n      while (context && context.prev && !context.startOfLine)\n        context = context.prev;\n      if (context) return context.indent + indentUnit;\n      else return state.baseIndent || 0;\n    },\n\n    electricInput: /<\\/[\\s\\w:]+>$/,\n    blockCommentStart: \"<!--\",\n    blockCommentEnd: \"-->\",\n\n    configuration: config.htmlMode ? \"html\" : \"xml\",\n    helperType: config.htmlMode ? \"html\" : \"xml\",\n\n    skipAttribute: function(state) {\n      if (state.state == attrValueState)\n        state.state = attrState\n    },\n\n    xmlCurrentTag: function(state) {\n      return state.tagName ? {name: state.tagName, close: state.type == \"closeTag\"} : null\n    },\n\n    xmlCurrentContext: function(state) {\n      var context = []\n      for (var cx = state.context; cx; cx = cx.prev)\n        if (cx.tagName) context.push(cx.tagName)\n      return context.reverse()\n    }\n  };\n});\n\nCodeMirror.defineMIME(\"text/xml\", \"xml\");\nCodeMirror.defineMIME(\"application/xml\", \"xml\");\nif (!CodeMirror.mimeModes.hasOwnProperty(\"text/html\"))\n  CodeMirror.defineMIME(\"text/html\", {name: \"xml\", htmlMode: true});\n\n});\n"
  },
  {
    "path": "plugins/UiFileManager/media/css/Menu.css",
    "content": ".menu {\n\tbackground-color: white; padding: 10px 0px; position: absolute; top: 0px; max-height: 0px; overflow: hidden; transform: translate(-100%, -30px); pointer-events: none;\n\tbox-shadow: 0px 2px 8px rgba(0,0,0,0.3); border-radius: 2px; opacity: 0; transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; z-index: 99;\n\tdisplay: inline-block; z-index: 999; transform-style: preserve-3d;\n}\n.menu.menu-left { transform: translate(0%, -30px); }\n.menu.menu-left.visible { transform: translate(0%, 0px); }\n.menu.visible {\n\topacity: 1; transform: translate(-100%, 0px); pointer-events: all;\n\ttransition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s cubic-bezier(0.86, 0, 0.07, 1);\n}\n\n.menu-item {\n\tdisplay: block; text-decoration: none; color: black; padding: 6px 24px; transition: all 0.2s; border-bottom: none; font-weight: normal;\n    max-height: 150px; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: 6; -webkit-box-orient: vertical; display: -webkit-box;\n}\n.menu-item-separator { margin-top: 3px; margin-bottom: 3px; border-top: 1px solid #eee }\n\n.menu-item.noaction { cursor: default }\n.menu-item:hover:not(.noaction) { background-color: #F6F6F6; transition: none; color: inherit; cursor: pointer; color: black }\n.menu-item:active:not(.noaction), .menu-item:focus:not(.noaction) { background-color: #AF3BFF !important; color: white !important; transition: none }\n.menu-item.selected:before {\n\tcontent: \"L\"; display: inline-block; transform: rotateZ(45deg) scaleX(-1);\n\tfont-weight: bold; position: absolute; margin-left: -14px; font-size: 12px; margin-top: 2px;\n}\n\n.menu-radio { white-space: normal; line-height: 26px }\n.menu-radio a {\n\tbackground-color: #EEE; width: 18.5%;; text-align: center; margin-top: 2px; margin-bottom: 2px; color: #666; font-weight: bold;\n\ttext-decoration: none; font-size: 13px; transition: all 0.3s; text-transform: uppercase; display: inline-block;\n}\n.menu-radio a:hover, .menu-radio a.selected { transition: none; background-color: #AF3BFF !important; color: white !important }\n.menu-radio a.long { font-size: 10px; vertical-align: -1px; }\n"
  },
  {
    "path": "plugins/UiFileManager/media/css/Selectbar.css",
    "content": ".selectbar.visible { margin-top: 0px; visibility: visible }\n.selectbar {\n    position: fixed; top: 0; left: 0; background-color: white; box-shadow: 0px 0px 25px rgba(22, 39, 97, 0.2); margin-top: -75px;\n    transition: all 0.3s; visibility: hidden; z-index: 9999; color: black; border-left: 5px solid #ede1f582; width: 100%;\n    padding: 13px; font-size: 13px; font-weight: lighter; backface-visibility: hidden;\n}\n\n.selectbar .num { margin-left: 15px; min-width: 30px; text-align: right; display: inline-block; }\n.selectbar .size { margin-left: 10px; color: #9f9ba2; min-width: 75px; display: inline-block; }\n.selectbar .actions { display: inline-block; margin-left: 20px; font-size: 13px; text-transform: uppercase; line-height: 20px; }\n.selectbar .action { padding: 5px 20px; border: 1px solid #edd4ff; margin-left: 10px; border-radius: 30px; color: #af3bff; text-decoration: none; transition: all 0.3s }\n.selectbar .action:hover { border-color: #c788f3; transition: none; color: #9700ff }\n.selectbar .delete { color: #AAA; border-color: #DDD; }\n.selectbar .delete:hover { color: #333; border-color: #AAA }\n.selectbar .action:active { background-color: #af3bff; color: white; border-color: #af3bff; transition: none }\n.selectbar .cancel { margin: 20px; font-size: 10px; text-decoration: none; color: #999; text-transform: uppercase; }\n.selectbar .cancel:hover { color: #333; transition: none }"
  },
  {
    "path": "plugins/UiFileManager/media/css/UiFileManager.css",
    "content": "body { background-color: #EEEEF5; font-family: \"Segoe UI\", Helvetica, Arial; height: 95000px; overflow: hidden; }\nbody.loaded { height: auto; overflow: auto }\nh1 { font-weight: lighter; }\n\na { color: #333 }\na:hover { text-decoration: none }\ninput::placeholder { color: rgba(255, 255, 255, 0.3) }\n\nh2 { font-weight: lighter; }\n\n.link { background-color: transparent; outline: 5px solid transparent; transition: all 0.3s }\n.link:active { background-color: #fbf5ff; outline: 5px solid #fbf5ff; transition: none }\n\n.manager.editing .files { float: left; width: 280px; }\n\n.sidebar-button {\n\tdisplay: inline-block; padding: 25px 19px; text-decoration: none; position: absolute;\n\tborder-right: 1px solid #EEE; line-height: 10px; color: #7801F5; transition: all 0.3s\n}\n.sidebar-button:active { background-color: #f5e7ff; transition: none }\n/*.sidebar-button:hover { background-color: #fbf5ff; }*/\n.sidebar-button span { transition: 1s all; transform-origin: 2.5px 7px; display: inline-block; }\n.manager.sidebar_closed .sidebar-button span { transform: rotateZ(180deg); }\n.manager.sidebar_closed .files { margin-left: -300px; }\n.manager.sidebar_closed .editor { width: 100%; }\n\n.button {\n\tpadding: 5px 10px; margin-left: 10px; background-color: #752ff2; border-bottom: 2px solid #caadff; background-position: -50px center;\n\tborder-radius: 2px; text-decoration: none; transition: all 0.5s ease-out; display: inline-block;\n\tcolor: #333; font-size: 12px; vertical-align: 2px; text-transform: uppercase; color: white; max-width: 100px;\n}\n.button:hover { background-color: #9e71ed; transition: none; }\n.button:active { position: relative; top: 1px }\n.button.loading, .button.disabled { color: rgba(255,255,255,0.7);; pointer-events: none; border-bottom: 2px solid #666; background-color: #999; }\n.button.loading { background: #999 url(../img/loading.gif) no-repeat center center; transition: all 0.5s ease-out; color: rgba(0,0,0,0); }\n.button.done { background-color: #4dc758; transition: all 0.3s; border-color: #4dc758; pointer-events: none; }\n.button.hidden { max-width: 0px; display: inline-block; padding-left: 0px; padding-right: 0px; margin: 0px; }\n\n/* List */\n\n.files {\n\twidth: 97%; box-sizing: border-box; color: #555; position: relative; z-index: 1; transition: all 0.6s;\n\tfont-size: 14px; box-shadow: 0px 9px 20px -15px #a5cbec; max-width: 400px; border: 1px solid #EEEEF5;\n}\n.files .tr { white-space: nowrap }\n.files .td { display: inline-block; width: 60px }\n.files .tbody .td { line-height: 18px; vertical-align: bottom; }\n.files .td.name { min-width: 100px }\n.files .td.size { width: 60px; text-align: right; padding-left: 5px; }\n.files .td.status { text-align: right; }\n.files .td.peer { width: 60px }\n.files .td.uploaded { width: 130px; text-align: right; }\n.files .td.added { width: 90px }\n.files .orderby { color: inherit; text-decoration: none; transition: all 0.3s; outline: 5px solid transparent; }\n.files .orderby:hover { text-decoration: underline; }\n.files .orderby .icon-arrow-down { opacity: 0; transition: all 0.3s ease-in-out; }\n.files .orderby.selected .icon-arrow-down { opacity: 0.3; }\n.files .orderby:active { background-color: rgba(133, 239, 255, 0.09); outline: 5px solid rgba(133, 239, 255, 0.09); transition: none; }\n.files .orderby:hover .icon-arrow-down { opacity: 0.5; }\n.files .orderby:not(.desc) .icon-arrow-down { transform: rotateZ(180deg); }\n.files .tr.editing .td { background-color: #ede1f582; border-top-color: #ece9ef; }\n.files .thead { /*background: linear-gradient(358deg, #e7f1f7, #e9f2f72e);*/ }\n.files .thead .td {\n\tborder-top: none; color: #8984c2; background-color: #f7f7fc;\n\tfont-size: 12px; /*text-transform: uppercase; background-color: transparent; font-weight: bold;*/\n}\n.files .thead .td a:last-of-type { font-weight: bold; }\n.files .thead .td a { text-decoration: none; }\n.files .thead .td a:hover { text-decoration: underline; }\n.files .tbody { max-height: calc(100vh - 95px); overflow-y: auto; overflow-x: hidden; }\n.files .tr { background-color: white; }\n.files .td { padding: 10px 20px; border-top: 1px solid #EEE; font-size: 13px; white-space: nowrap; }\n.files .td.full { width: 100%; box-sizing: border-box;  white-space: pre-line; }\n.files .td.pre { width: 0px; color: transparent; padding-left: 0px; border-left: 2px solid transparent; }\n.files .tbody .td { height: 18px; }\n.files .tbody .td.full { height: auto; }\n.files .td.pre .checkbox-outer { opacity: 0.6; margin-left: -11px; margin-top: -15px; width: 18px; height: 12px; display: inline-block; }\n.files .tr.modified .td.pre { border-left-color: #7801F5 }\n.files .tr.added .td.pre { border-left-color: #00ec93 }\n.files .tr.ignored .td.pre { border-left-color: #999; }\n.files .tr.ignored { opacity: 0.5; }\n.files .tr.optional { background: linear-gradient(90deg, #fff6dd, 30%, white, 10%, white); }\n.files .tr.optional_empty { color: #999; font-style: italic; }\n.files .td.error { background-color: #F44336; color: white; }\n.files .td.site { width: 70px }\n.files .td.site .link { color: inherit; text-decoration: none }\n.files .td.status .percent {\n\ttransition: all 1s ease-in-out; display: inline-block; width: 80px; background-color: #EEE; font-size: 10px;\n\theight: 15px; line-height: 15px; text-align: center; margin-right: 20px;\n}\n.files .td.name { padding-left: 10px; width: calc(100% - 167px); max-height: 18px; padding-right: 10px; }\n.files .tr.nobuttons .td.name { width: calc(100% - 127px); }\n.files .tr.nobuttons .td.buttons { width: 0px; }\n.files .td.name .title { color: inherit; text-decoration: none }\n.files .td.name .link { display: inline-block; overflow: hidden; text-overflow: ellipsis; vertical-align: -4px; max-width: 100%; }\n.files .pinned .td.name .link { max-width: calc(100% - 40px); }\n.files .thead .td.uploaded { text-align: left }\n.files .thead .td.uploaded .title { padding-left: 7px; }\n.files .peer .icon-profile { background: currentColor; color: #47d094; font-size: 10px; top: 1px; margin-right: 13px }\n.files .peer .icon-profile:before { background: currentColor }\n.files .peer .num { color: #969696; }\n.files .uploaded .uploaded-text { display: inline-block; text-align: right; }\n.files .uploaded .dots-container { display: inline-block; width: 0px; padding-right: 65px;; }\n.files .td.buttons { width: 40px; padding-left: 0px; padding-right: 0px; }\n.files .td.buttons .edit {\n\tbackground-color: #2196f336; border-radius: 15px; padding: 1px 9px; font-size: 80%; text-decoration: none; color: #1976D2;\n}\n.files .checkbox-outer { padding: 15px; padding-left: 20px; padding-right: 0px; }\n.files .checkbox {\n\tdisplay: inline-block; width: 12px; height: 12px; border: 2px solid #00000014;\n\tborder-radius: 3px; vertical-align: -3px; margin-right: 10px;\n}\n.files .selected .checkbox { border-color: #dedede }\n.files .selected .checkbox:after {\n\tbackground-color: #dedede; content: \"\"; text-decoration: none; display: block; width: 10px; height: 10px; margin-left: 1px; margin-top: 1px;\n}\n.files .tbody .td.size { font-size: 13px }\n.files .tbody .td.added, #PageFiles .files .td.access { font-size: 12px; color: #999 }\n.files .tr.type-dir .name { font-weight: bold; }\n.files .tr.type-parent .name .link { display: inline-block; width: 100%; padding: 5px; margin-top: -5px; }\n.files .foot .td { color: #a4a4a4; background-color: #f7f7fc; }\n.files .foot .create { float: right; text-decoration: none; position: relative; }\n.files .foot .create .link { color: #8c42ed; text-decoration: none; }\n.files .foot .create .link:active { background-color: #8c42ed3b; outline: 5px solid #8c42ed3b; }\n.files .foot .create .menu { top: 40px; }\n\n\n/* Editor */\n\n.editor { background-color: #F7F7FC; float: left; width: calc(100% - 280px); box-sizing: border-box; transition: all 0.6s; }\n.editor .CodeMirror { height: calc(100vh - 79px); visibility: hidden; }\n.editor textarea { width: 100%; height: 800px; white-space: pre; }\n.editor .title { margin-left: 20px; }\n.editor .editor-head {\n\tpadding: 15px 20px; padding-left: 45px; font-size: 18px; font-weight: lighter; border: 1px solid #EEEEF5;\n    white-space: nowrap; overflow: hidden;\n}\n.editor.loaded .CodeMirror { visibility: inherit; }\n.editor.error .CodeMirror { display: none; }\n.editor .button.save { min-width: 30px; text-align: center; transition: all 0.3s; }\n.editor .button.save.done { min-width: 80px; }\n.editor .error-message { text-align: center; padding: 50px; }\n\n.editor .CodeMirror-foldmarker {\n\tline-height: .3; cursor: pointer; background-color: #ffeb3b61; text-shadow: none; font-family: inherit;\n\tcolor: #050505; border: 1px solid #ffdf7f; padding: 0px 5px;\n}\n.editor .CodeMirror-activeline-background { background-color: #F6F6F6 !important; }"
  },
  {
    "path": "plugins/UiFileManager/media/css/all.css",
    "content": "\n/* ---- Menu.css ---- */\n\n\n.menu {\n\tbackground-color: white; padding: 10px 0px; position: absolute; top: 0px; max-height: 0px; overflow: hidden; -webkit-transform: translate(-100%, -30px); -moz-transform: translate(-100%, -30px); -o-transform: translate(-100%, -30px); -ms-transform: translate(-100%, -30px); transform: translate(-100%, -30px) ; pointer-events: none;\n\t-webkit-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); -moz-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); -o-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); -ms-box-shadow: 0px 2px 8px rgba(0,0,0,0.3); box-shadow: 0px 2px 8px rgba(0,0,0,0.3) ; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; opacity: 0; -webkit-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; -moz-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; -o-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; -ms-transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out; transition: opacity 0.2s ease-out, transform 1s ease-out, max-height 0.2s ease-in-out ; z-index: 99;\n\tdisplay: inline-block; z-index: 999; transform-style: preserve-3d;\n}\n.menu.menu-left { -webkit-transform: translate(0%, -30px); -moz-transform: translate(0%, -30px); -o-transform: translate(0%, -30px); -ms-transform: translate(0%, -30px); transform: translate(0%, -30px) ; }\n.menu.menu-left.visible { -webkit-transform: translate(0%, 0px); -moz-transform: translate(0%, 0px); -o-transform: translate(0%, 0px); -ms-transform: translate(0%, 0px); transform: translate(0%, 0px) ; }\n.menu.visible {\n\topacity: 1; -webkit-transform: translate(-100%, 0px); -moz-transform: translate(-100%, 0px); -o-transform: translate(-100%, 0px); -ms-transform: translate(-100%, 0px); transform: translate(-100%, 0px) ; pointer-events: all;\n\t-webkit-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s cubic-bezier(0.86, 0, 0.07, 1); transition: opacity 0.1s ease-out, transform 0.3s ease-out, max-height 0.3s cubic-bezier(0.86, 0, 0.07, 1) ;\n}\n\n.menu-item {\n\tdisplay: block; text-decoration: none; color: black; padding: 6px 24px; -webkit-transition: all 0.2s; -moz-transition: all 0.2s; -o-transition: all 0.2s; -ms-transition: all 0.2s; transition: all 0.2s ; border-bottom: none; font-weight: normal;\n    max-height: 150px; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: 6; -webkit-box-orient: vertical; display: -webkit-box;\n}\n.menu-item-separator { margin-top: 3px; margin-bottom: 3px; border-top: 1px solid #eee }\n\n.menu-item.noaction { cursor: default }\n.menu-item:hover:not(.noaction) { background-color: #F6F6F6; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; color: inherit; cursor: pointer; color: black }\n.menu-item:active:not(.noaction), .menu-item:focus:not(.noaction) { background-color: #AF3BFF !important; color: white !important; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n.menu-item.selected:before {\n\tcontent: \"L\"; display: inline-block; -webkit-transform: rotateZ(45deg) scaleX(-1); -moz-transform: rotateZ(45deg) scaleX(-1); -o-transform: rotateZ(45deg) scaleX(-1); -ms-transform: rotateZ(45deg) scaleX(-1); transform: rotateZ(45deg) scaleX(-1) ;\n\tfont-weight: bold; position: absolute; margin-left: -14px; font-size: 12px; margin-top: 2px;\n}\n\n.menu-radio { white-space: normal; line-height: 26px }\n.menu-radio a {\n\tbackground-color: #EEE; width: 18.5%;; text-align: center; margin-top: 2px; margin-bottom: 2px; color: #666; font-weight: bold;\n\ttext-decoration: none; font-size: 13px; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; text-transform: uppercase; display: inline-block;\n}\n.menu-radio a:hover, .menu-radio a.selected { -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; background-color: #AF3BFF !important; color: white !important }\n.menu-radio a.long { font-size: 10px; vertical-align: -1px; }\n\n\n/* ---- Selectbar.css ---- */\n\n\n.selectbar.visible { margin-top: 0px; visibility: visible }\n.selectbar {\n    position: fixed; top: 0; left: 0; background-color: white; -webkit-box-shadow: 0px 0px 25px rgba(22, 39, 97, 0.2); -moz-box-shadow: 0px 0px 25px rgba(22, 39, 97, 0.2); -o-box-shadow: 0px 0px 25px rgba(22, 39, 97, 0.2); -ms-box-shadow: 0px 0px 25px rgba(22, 39, 97, 0.2); box-shadow: 0px 0px 25px rgba(22, 39, 97, 0.2) ; margin-top: -75px;\n    -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; visibility: hidden; z-index: 9999; color: black; border-left: 5px solid #ede1f582; width: 100%;\n    padding: 13px; font-size: 13px; font-weight: lighter; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ;\n}\n\n.selectbar .num { margin-left: 15px; min-width: 30px; text-align: right; display: inline-block; }\n.selectbar .size { margin-left: 10px; color: #9f9ba2; min-width: 75px; display: inline-block; }\n.selectbar .actions { display: inline-block; margin-left: 20px; font-size: 13px; text-transform: uppercase; line-height: 20px; }\n.selectbar .action { padding: 5px 20px; border: 1px solid #edd4ff; margin-left: 10px; -webkit-border-radius: 30px; -moz-border-radius: 30px; -o-border-radius: 30px; -ms-border-radius: 30px; border-radius: 30px ; color: #af3bff; text-decoration: none; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.selectbar .action:hover { border-color: #c788f3; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; color: #9700ff }\n.selectbar .delete { color: #AAA; border-color: #DDD; }\n.selectbar .delete:hover { color: #333; border-color: #AAA }\n.selectbar .action:active { background-color: #af3bff; color: white; border-color: #af3bff; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n.selectbar .cancel { margin: 20px; font-size: 10px; text-decoration: none; color: #999; text-transform: uppercase; }\n.selectbar .cancel:hover { color: #333; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n\n/* ---- UiFileManager.css ---- */\n\n\nbody { background-color: #EEEEF5; font-family: \"Segoe UI\", Helvetica, Arial; height: 95000px; overflow: hidden; }\nbody.loaded { height: auto; overflow: auto }\nh1 { font-weight: lighter; }\n\na { color: #333 }\na:hover { text-decoration: none }\ninput::placeholder { color: rgba(255, 255, 255, 0.3) }\n\nh2 { font-weight: lighter; }\n\n.link { background-color: transparent; outline: 5px solid transparent; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.link:active { background-color: #fbf5ff; outline: 5px solid #fbf5ff; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n\n.manager.editing .files { float: left; width: 280px; }\n\n.sidebar-button {\n\tdisplay: inline-block; padding: 25px 19px; text-decoration: none; position: absolute;\n\tborder-right: 1px solid #EEE; line-height: 10px; color: #7801F5; transition: all 0.3s\n}\n.sidebar-button:active { background-color: #f5e7ff; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n/*.sidebar-button:hover { background-color: #fbf5ff; }*/\n.sidebar-button span { -webkit-transition: 1s all; -moz-transition: 1s all; -o-transition: 1s all; -ms-transition: 1s all; transition: 1s all ; transform-origin: 2.5px 7px; display: inline-block; }\n.manager.sidebar_closed .sidebar-button span { -webkit-transform: rotateZ(180deg); -moz-transform: rotateZ(180deg); -o-transform: rotateZ(180deg); -ms-transform: rotateZ(180deg); transform: rotateZ(180deg) ; }\n.manager.sidebar_closed .files { margin-left: -300px; }\n.manager.sidebar_closed .editor { width: 100%; }\n\n.button {\n\tpadding: 5px 10px; margin-left: 10px; background-color: #752ff2; border-bottom: 2px solid #caadff; background-position: -50px center;\n\t-webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; text-decoration: none; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; display: inline-block;\n\tcolor: #333; font-size: 12px; vertical-align: 2px; text-transform: uppercase; color: white; max-width: 100px;\n}\n.button:hover { background-color: #9e71ed; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; }\n.button:active { position: relative; top: 1px }\n.button.loading, .button.disabled { color: rgba(255,255,255,0.7);; pointer-events: none; border-bottom: 2px solid #666; background-color: #999; }\n.button.loading { background: #999 url(../img/loading.gif) no-repeat center center; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; color: rgba(0,0,0,0); }\n.button.done { background-color: #4dc758; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; border-color: #4dc758; pointer-events: none; }\n.button.hidden { max-width: 0px; display: inline-block; padding-left: 0px; padding-right: 0px; margin: 0px; }\n\n/* List */\n\n.files {\n\twidth: 97%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; color: #555; position: relative; z-index: 1; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ;\n\tfont-size: 14px; -webkit-box-shadow: 0px 9px 20px -15px #a5cbec; -moz-box-shadow: 0px 9px 20px -15px #a5cbec; -o-box-shadow: 0px 9px 20px -15px #a5cbec; -ms-box-shadow: 0px 9px 20px -15px #a5cbec; box-shadow: 0px 9px 20px -15px #a5cbec ; max-width: 400px; border: 1px solid #EEEEF5;\n}\n.files .tr { white-space: nowrap }\n.files .td { display: inline-block; width: 60px }\n.files .tbody .td { line-height: 18px; vertical-align: bottom; }\n.files .td.name { min-width: 100px }\n.files .td.size { width: 60px; text-align: right; padding-left: 5px; }\n.files .td.status { text-align: right; }\n.files .td.peer { width: 60px }\n.files .td.uploaded { width: 130px; text-align: right; }\n.files .td.added { width: 90px }\n.files .orderby { color: inherit; text-decoration: none; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; outline: 5px solid transparent; }\n.files .orderby:hover { text-decoration: underline; }\n.files .orderby .icon-arrow-down { opacity: 0; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; -ms-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out ; }\n.files .orderby.selected .icon-arrow-down { opacity: 0.3; }\n.files .orderby:active { background-color: rgba(133, 239, 255, 0.09); outline: 5px solid rgba(133, 239, 255, 0.09); -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; }\n.files .orderby:hover .icon-arrow-down { opacity: 0.5; }\n.files .orderby:not(.desc) .icon-arrow-down { -webkit-transform: rotateZ(180deg); -moz-transform: rotateZ(180deg); -o-transform: rotateZ(180deg); -ms-transform: rotateZ(180deg); transform: rotateZ(180deg) ; }\n.files .tr.editing .td { background-color: #ede1f582; border-top-color: #ece9ef; }\n.files .thead { /*background: -webkit-linear-gradient(358deg, #e7f1f7, #e9f2f72e);background: -moz-linear-gradient(358deg, #e7f1f7, #e9f2f72e);background: -o-linear-gradient(358deg, #e7f1f7, #e9f2f72e);background: -ms-linear-gradient(358deg, #e7f1f7, #e9f2f72e);background: linear-gradient(358deg, #e7f1f7, #e9f2f72e);*/ }\n.files .thead .td {\n\tborder-top: none; color: #8984c2; background-color: #f7f7fc;\n\tfont-size: 12px; /*text-transform: uppercase; background-color: transparent; font-weight: bold;*/\n}\n.files .thead .td a:last-of-type { font-weight: bold; }\n.files .thead .td a { text-decoration: none; }\n.files .thead .td a:hover { text-decoration: underline; }\n.files .tbody { max-height: calc(100vh - 95px); overflow-y: auto; overflow-x: hidden; }\n.files .tr { background-color: white; }\n.files .td { padding: 10px 20px; border-top: 1px solid #EEE; font-size: 13px; white-space: nowrap; }\n.files .td.full { width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;  white-space: pre-line; }\n.files .td.pre { width: 0px; color: transparent; padding-left: 0px; border-left: 2px solid transparent; }\n.files .tbody .td { height: 18px; }\n.files .tbody .td.full { height: auto; }\n.files .td.pre .checkbox-outer { opacity: 0.6; margin-left: -11px; margin-top: -15px; width: 18px; height: 12px; display: inline-block; }\n.files .tr.modified .td.pre { border-left-color: #7801F5 }\n.files .tr.added .td.pre { border-left-color: #00ec93 }\n.files .tr.ignored .td.pre { border-left-color: #999; }\n.files .tr.ignored { opacity: 0.5; }\n.files .tr.optional { background: -webkit-linear-gradient(90deg, #fff6dd, 30%, white, 10%, white);background: -moz-linear-gradient(90deg, #fff6dd, 30%, white, 10%, white);background: -o-linear-gradient(90deg, #fff6dd, 30%, white, 10%, white);background: -ms-linear-gradient(90deg, #fff6dd, 30%, white, 10%, white);background: linear-gradient(90deg, #fff6dd, 30%, white, 10%, white); }\n.files .tr.optional_empty { color: #999; font-style: italic; }\n.files .td.error { background-color: #F44336; color: white; }\n.files .td.site { width: 70px }\n.files .td.site .link { color: inherit; text-decoration: none }\n.files .td.status .percent {\n\t-webkit-transition: all 1s ease-in-out; -moz-transition: all 1s ease-in-out; -o-transition: all 1s ease-in-out; -ms-transition: all 1s ease-in-out; transition: all 1s ease-in-out ; display: inline-block; width: 80px; background-color: #EEE; font-size: 10px;\n\theight: 15px; line-height: 15px; text-align: center; margin-right: 20px;\n}\n.files .td.name { padding-left: 10px; width: calc(100% - 167px); max-height: 18px; padding-right: 10px; }\n.files .tr.nobuttons .td.name { width: calc(100% - 127px); }\n.files .tr.nobuttons .td.buttons { width: 0px; }\n.files .td.name .title { color: inherit; text-decoration: none }\n.files .td.name .link { display: inline-block; overflow: hidden; text-overflow: ellipsis; vertical-align: -4px; max-width: 100%; }\n.files .pinned .td.name .link { max-width: calc(100% - 40px); }\n.files .thead .td.uploaded { text-align: left }\n.files .thead .td.uploaded .title { padding-left: 7px; }\n.files .peer .icon-profile { background: currentColor; color: #47d094; font-size: 10px; top: 1px; margin-right: 13px }\n.files .peer .icon-profile:before { background: currentColor }\n.files .peer .num { color: #969696; }\n.files .uploaded .uploaded-text { display: inline-block; text-align: right; }\n.files .uploaded .dots-container { display: inline-block; width: 0px; padding-right: 65px;; }\n.files .td.buttons { width: 40px; padding-left: 0px; padding-right: 0px; }\n.files .td.buttons .edit {\n\tbackground-color: #2196f336; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; -ms-border-radius: 15px; border-radius: 15px ; padding: 1px 9px; font-size: 80%; text-decoration: none; color: #1976D2;\n}\n.files .checkbox-outer { padding: 15px; padding-left: 20px; padding-right: 0px; }\n.files .checkbox {\n\tdisplay: inline-block; width: 12px; height: 12px; border: 2px solid #00000014;\n\t-webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ; vertical-align: -3px; margin-right: 10px;\n}\n.files .selected .checkbox { border-color: #dedede }\n.files .selected .checkbox:after {\n\tbackground-color: #dedede; content: \"\"; text-decoration: none; display: block; width: 10px; height: 10px; margin-left: 1px; margin-top: 1px;\n}\n.files .tbody .td.size { font-size: 13px }\n.files .tbody .td.added, #PageFiles .files .td.access { font-size: 12px; color: #999 }\n.files .tr.type-dir .name { font-weight: bold; }\n.files .tr.type-parent .name .link { display: inline-block; width: 100%; padding: 5px; margin-top: -5px; }\n.files .foot .td { color: #a4a4a4; background-color: #f7f7fc; }\n.files .foot .create { float: right; text-decoration: none; position: relative; }\n.files .foot .create .link { color: #8c42ed; text-decoration: none; }\n.files .foot .create .link:active { background-color: #8c42ed3b; outline: 5px solid #8c42ed3b; }\n.files .foot .create .menu { top: 40px; }\n\n\n/* Editor */\n\n.editor { background-color: #F7F7FC; float: left; width: calc(100% - 280px); -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ; }\n.editor .CodeMirror { height: calc(100vh - 79px); visibility: hidden; }\n.editor textarea { width: 100%; height: 800px; white-space: pre; }\n.editor .title { margin-left: 20px; }\n.editor .editor-head {\n\tpadding: 15px 20px; padding-left: 45px; font-size: 18px; font-weight: lighter; border: 1px solid #EEEEF5;\n    white-space: nowrap; overflow: hidden;\n}\n.editor.loaded .CodeMirror { visibility: inherit; }\n.editor.error .CodeMirror { display: none; }\n.editor .button.save { min-width: 30px; text-align: center; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; }\n.editor .button.save.done { min-width: 80px; }\n.editor .error-message { text-align: center; padding: 50px; }\n\n.editor .CodeMirror-foldmarker {\n\tline-height: .3; cursor: pointer; background-color: #ffeb3b61; text-shadow: none; font-family: inherit;\n\tcolor: #050505; border: 1px solid #ffdf7f; padding: 0px 5px;\n}\n.editor .CodeMirror-activeline-background { background-color: #F6F6F6 !important; }"
  },
  {
    "path": "plugins/UiFileManager/media/js/Config.coffee",
    "content": "window.BINARY_EXTENSIONS = [\n\t\"3dm\", \"3ds\", \"3g2\", \"3gp\", \"7z\", \"a\", \"aac\", \"adp\", \"ai\", \"aif\", \"aiff\", \"alz\", \"ape\", \"apk\", \"appimage\", \"ar\", \"arj\", \"asc\", \"asf\", \"au\", \"avi\", \"bak\",\n\t\"baml\", \"bh\", \"bin\", \"bk\", \"bmp\", \"btif\", \"bz2\", \"bzip2\", \"cab\", \"caf\", \"cgm\", \"class\", \"cmx\", \"cpio\", \"cr2\", \"cur\", \"dat\", \"dcm\", \"deb\", \"dex\", \"djvu\",\n\t\"dll\", \"dmg\", \"dng\", \"doc\", \"docm\", \"docx\", \"dot\", \"dotm\", \"dra\", \"DS_Store\", \"dsk\", \"dts\", \"dtshd\", \"dvb\", \"dwg\", \"dxf\", \"ecelp4800\", \"ecelp7470\",\n\t\"ecelp9600\", \"egg\", \"eol\", \"eot\", \"epub\", \"exe\", \"f4v\", \"fbs\", \"fh\", \"fla\", \"flac\", \"flatpak\", \"fli\", \"flv\", \"fpx\", \"fst\", \"fvt\", \"g3\", \"gh\", \"gif\",\n\t\"gpg\", \"graffle\", \"gz\", \"gzip\", \"h261\", \"h263\", \"h264\", \"icns\", \"ico\", \"ief\", \"img\", \"ipa\", \"iso\", \"jar\", \"jpeg\", \"jpg\", \"jpgv\", \"jpm\", \"jxr\", \"key\",\n\t\"ktx\", \"lha\", \"lib\", \"lvp\", \"lz\", \"lzh\", \"lzma\", \"lzo\", \"m3u\", \"m4a\", \"m4v\", \"mar\", \"mdi\", \"mht\", \"mid\", \"midi\", \"mj2\", \"mka\", \"mkv\", \"mmr\", \"mng\",\n\t\"mobi\", \"mov\", \"movie\", \"mp3\", \"mp4\", \"mp4a\", \"mpeg\", \"mpg\", \"mpga\", \"msgpack\", \"mxu\", \"nef\", \"npx\", \"numbers\", \"nupkg\", \"o\", \"oga\", \"ogg\", \"ogv\",\n\t\"otf\", \"pages\", \"pbm\", \"pcx\", \"pdb\", \"pdf\", \"pea\", \"pgm\", \"pic\", \"png\", \"pnm\", \"pot\", \"potm\", \"potx\", \"ppa\", \"ppam\", \"ppm\", \"pps\", \"ppsm\", \"ppsx\",\n\t\"ppt\", \"pptm\", \"pptx\", \"psd\", \"pya\", \"pyc\", \"pyo\", \"pyv\", \"qt\", \"rar\", \"ras\", \"raw\", \"resources\", \"rgb\", \"rip\", \"rlc\", \"rmf\", \"rmvb\", \"rpm\", \"rtf\",\n\t\"rz\", \"s3m\", \"s7z\", \"scpt\", \"sgi\", \"shar\", \"sig\", \"sil\", \"sketch\", \"slk\", \"smv\", \"snap\", \"snk\", \"so\", \"stl\", \"sub\", \"suo\", \"swf\", \"tar\", \"tbz2\", \"tbz\",\n\t\"tga\", \"tgz\", \"thmx\", \"tif\", \"tiff\", \"tlz\", \"ttc\", \"ttf\", \"txz\", \"udf\", \"uvh\", \"uvi\", \"uvm\", \"uvp\", \"uvs\", \"uvu\", \"viv\", \"vob\", \"war\", \"wav\", \"wax\",\n\t\"wbmp\", \"wdp\", \"weba\", \"webm\", \"webp\", \"whl\", \"wim\", \"wm\", \"wma\", \"wmv\", \"wmx\", \"woff2\", \"woff\", \"wrm\", \"wvx\", \"xbm\", \"xif\", \"xla\", \"xlam\", \"xls\",\n\t\"xlsb\", \"xlsm\", \"xlsx\", \"xlt\", \"xltm\", \"xltx\", \"xm\", \"xmind\", \"xpi\", \"xpm\", \"xwd\", \"xz\", \"z\", \"zip\", \"zipx\"\n]\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/FileEditor.coffee",
    "content": "class FileEditor extends Class\n\tconstructor: (@inner_path) ->\n\t\t@need_update = true\n\t\t@on_loaded = new Promise()\n\t\t@is_loading = false\n\t\t@content = \"\"\n\t\t@node_cm = null\n\t\t@cm = null\n\t\t@error = null\n\t\t@is_loaded = false\n\t\t@is_modified = false\n\t\t@is_saving = false\n\t\t@mode = \"Loading\"\n\n\tupdate: ->\n\t\tis_required = Page.url_params.get(\"edit_mode\") != \"new\"\n\n\t\tPage.cmd \"fileGet\", {inner_path: @inner_path, required: is_required}, (res) =>\n\t\t\tif res?.error\n\t\t\t\t@error = res.error\n\t\t\t\t@content = res.error\n\t\t\t\t@log \"Error loading: #{@error}\"\n\t\t\telse\n\t\t\t\tif res\n\t\t\t\t\t@content = res\n\t\t\t\telse\n\t\t\t\t\t@content = \"\"\n\t\t\t\t\t@mode = \"Create\"\n\t\t\tif not @content\n\t\t\t\t@cm.getDoc().clearHistory()\n\t\t\t@cm.setValue(@content)\n\t\t\tif not @error\n\t\t\t\t@is_loaded = true\n\t\t\tPage.projector.scheduleRender()\n\n\tisModified: =>\n\t\treturn @content != @cm.getValue()\n\n\tstoreCmNode: (node) =>\n\t\t@node_cm = node\n\n\tgetMode: (inner_path) ->\n\t\text = inner_path.split(\".\").pop()\n\t\ttypes = {\n\t\t\t\"py\": \"python\",\n\t\t\t\"json\": \"application/json\",\n\t\t\t\"js\": \"javascript\",\n\t\t\t\"coffee\": \"coffeescript\",\n\t\t\t\"html\": \"htmlmixed\",\n\t\t\t\"htm\": \"htmlmixed\",\n\t\t\t\"php\": \"htmlmixed\",\n\t\t\t\"rs\": \"rust\",\n\t\t\t\"css\": \"css\",\n\t\t\t\"md\": \"markdown\",\n\t\t\t\"xml\": \"xml\",\n\t\t\t\"svg\": \"xml\"\n\t\t}\n\t\treturn types[ext]\n\n\tfoldJson: (from, to) =>\n\t\t@log \"foldJson\", from, to\n\t\t# Get open / close token\n\t\tstartToken = '{'\n\t\tendToken = '}'\n\t\tprevLine = @cm.getLine(from.line)\n\t\tif prevLine.lastIndexOf('[') > prevLine.lastIndexOf('{')\n\t\t  startToken = '['\n\t\t  endToken = ']'\n\n\t\t# Get json content\n\t\tinternal = @cm.getRange(from, to)\n\t\ttoParse = startToken + internal + endToken\n\n\t\t#Get key count\n\t\ttry\n\t\t\tparsed = JSON.parse(toParse)\n\t\t\tcount = Object.keys(parsed).length\n\t\tcatch e\n\t\t\tnull\n\n\t\treturn if count then \"\\u21A4#{count}\\u21A6\" else \"\\u2194\"\n\n\tcreateCodeMirror: ->\n\t\tmode = @getMode(@inner_path)\n\t\t@log \"Creating CodeMirror\", @inner_path, mode\n\t\toptions = {\n\t\t\tvalue: \"Loading...\",\n\t\t\tmode: mode,\n\t\t\tlineNumbers: true,\n\t\t\tstyleActiveLine: true,\n\t\t\tmatchBrackets: true,\n\t\t\tkeyMap: \"sublime\",\n\t\t\ttheme: \"mdn-like\",\n\t\t\textraKeys: {\"Ctrl-Space\": \"autocomplete\"},\n\t\t\tfoldGutter: true,\n\t\t\tgutters: [\"CodeMirror-linenumbers\", \"CodeMirror-foldgutter\"]\n\n\t\t}\n\t\tif mode == \"application/json\"\n\t\t\toptions.gutters.unshift(\"CodeMirror-lint-markers\")\n\t\t\toptions.lint = true\n\t\t\toptions.foldOptions = { widget: @foldJson }\n\n\t\t@cm = CodeMirror(@node_cm, options)\n\t\t@cm.on \"changes\", (changes) =>\n\t\t\tif @is_loaded and not @is_modified\n\t\t\t\t@is_modified = true\n\t\t\t\tPage.projector.scheduleRender()\n\n\n\tloadEditor: ->\n\t\tif not @is_loading\n\t\t\tdocument.getElementsByTagName(\"head\")[0].insertAdjacentHTML(\n\t\t\t\t\"beforeend\",\n\t\t\t\t\"\"\"<link rel=\"stylesheet\" href=\"codemirror/all.css\" />\"\"\"\n\t\t\t)\n\t\t\tscript = document.createElement('script')\n\t\t\tscript.src = \"codemirror/all.js\"\n\t\t\tscript.onload = =>\n\t\t\t\t@createCodeMirror()\n\t\t\t\t@on_loaded.resolve()\n\t\t\tdocument.head.appendChild(script)\n\t\treturn @on_loaded\n\n\thandleSidebarButtonClick: =>\n\t\tPage.is_sidebar_closed = not Page.is_sidebar_closed\n\t\treturn false\n\n\thandleSaveClick: =>\n\t\tnum_errors = (mark for mark in Page.file_editor.cm.getAllMarks() when mark.className == \"CodeMirror-lint-mark-error\").length\n\t\tif num_errors > 0\n\t\t\tPage.cmd \"wrapperConfirm\", [\"<b>Warning:</b> The file looks invalid.\", \"Save anyway\"], @save\n\t\telse\n\t\t\t@save()\n\t\treturn false\n\n\tsave: =>\n\t\tPage.projector.scheduleRender()\n\t\t@is_saving = true\n\t\tPage.cmd \"fileWrite\", [@inner_path, Text.fileEncode(@cm.getValue())], (res) =>\n\t\t\t@is_saving = false\n\t\t\tif res.error\n\t\t\t\tPage.cmd \"wrapperNotification\", [\"error\", \"Error saving #{res.error}\"]\n\t\t\telse\n\t\t\t\t@is_save_done = true\n\t\t\t\tsetTimeout (() =>\n\t\t\t\t\t@is_save_done = false\n\t\t\t\t\tPage.projector.scheduleRender()\n\t\t\t\t), 2000\n\t\t\t\t@content = @cm.getValue()\n\t\t\t\t@is_modified = false\n\t\t\t\tif @mode == \"Create\"\n\t\t\t\t\t@mode = \"Edit\"\n\t\t\t\tPage.file_list.need_update = true\n\t\t\tPage.projector.scheduleRender()\n\n\trender: ->\n\t\tif @need_update\n\t\t\t@loadEditor().then =>\n\t\t\t\t@update()\n\t\t\t@need_update = false\n\t\th(\"div.editor\", {afterCreate: @storeCmNode, classes: {error: @error, loaded: @is_loaded}}, [\n\t\t\th(\"a.sidebar-button\", {href: \"#Sidebar\", onclick: @handleSidebarButtonClick}, h(\"span\", \"\\u2039\")),\n\t\t\th(\"div.editor-head\", [\n\t\t\t\tif @mode in [\"Edit\", \"Create\"]\n\t\t\t\t\th(\"a.save.button\",\n\t\t\t\t\t\t{href: \"#Save\", classes: {loading: @is_saving, done: @is_save_done, disabled: not @is_modified}, onclick: @handleSaveClick},\n\t\t\t\t\t\tif @is_save_done then \"Save: done!\" else \"Save\"\n\t\t\t\t\t)\n\t\t\t\th(\"span.title\", @mode, \": \", @inner_path)\n\t\t\t]),\n\t\t\tif @error\n\t\t\t\th(\"div.error-message\",\n\t\t\t\t\th(\"h2\", \"Unable to load the file: #{@error}\")\n\t\t\t\t\th(\"a\", {href: Page.file_list.getHref(@inner_path)}, \"View in browser\")\n\t\t\t\t)\n\t\t])\n\nwindow.FileEditor = FileEditor"
  },
  {
    "path": "plugins/UiFileManager/media/js/FileItemList.coffee",
    "content": "class FileItemList extends Class\n\tconstructor: (@inner_path) ->\n\t\t@items = []\n\t\t@updating = false\n\t\t@files_modified = {}\n\t\t@dirs_modified = {}\n\t\t@files_added = {}\n\t\t@dirs_added = {}\n\t\t@files_optional = {}\n\t\t@items_by_name = {}\n\n\t# Update item list\n\tupdate: (cb) ->\n\t\t@updating = true\n\t\t@logStart(\"Updating dirlist\")\n\t\tPage.cmd \"dirList\", {inner_path: @inner_path, stats: true}, (res) =>\n\t\t\tif res.error\n\t\t\t\t@error = res.error\n\t\t\telse\n\t\t\t\t@error = null\n\t\t\t\tpattern_ignore = RegExp(\"^\" + Page.site_info.content?.ignore)\n\n\t\t\t\t@items.splice(0, @items.length)  # Remove all items\n\n\t\t\t\t@items_by_name = {}\n\t\t\t\tfor row in res\n\t\t\t\t\trow.type = @getFileType(row)\n\t\t\t\t\trow.inner_path = @inner_path + row.name\n\t\t\t\t\tif Page.site_info.content?.ignore and row.inner_path.match(pattern_ignore)\n\t\t\t\t\t\trow.ignored = true\n\t\t\t\t\t@items.push(row)\n\t\t\t\t\t@items_by_name[row.name] = row\n\n\t\t\t\t@sort()\n\n\t\t\tif Page.site_info?.settings?.own\n\t\t\t\t@updateAddedFiles()\n\n\t\t\t@updateOptionalFiles =>\n\t\t\t\t@updating = false\n\t\t\t\tcb?()\n\t\t\t\t@logEnd(\"Updating dirlist\", @inner_path)\n\t\t\t\tPage.projector.scheduleRender()\n\n\t\t\t\t@updateModifiedFiles =>\n\t\t\t\t\tPage.projector.scheduleRender()\n\n\n\tupdateModifiedFiles: (cb) =>\n\t\t# Add modified attribute to changed files\n\t\tPage.cmd \"siteListModifiedFiles\", [], (res) =>\n\t\t\t@files_modified = {}\n\t\t\t@dirs_modified = {}\n\t\t\tfor inner_path in res.modified_files\n\t\t\t\t@files_modified[inner_path] = true\n\t\t\t\tdir_inner_path = \"\"\n\t\t\t\tdir_parts = inner_path.split(\"/\")\n\t\t\t\tfor dir_part in dir_parts[..-2]\n\t\t\t\t\tif dir_inner_path\n\t\t\t\t\t\tdir_inner_path += \"/#{dir_part}\"\n\t\t\t\t\telse\n\t\t\t\t\t\tdir_inner_path = dir_part\n\t\t\t\t\t@dirs_modified[dir_inner_path] = true\n\n\t\t\tcb?()\n\n\t# Update newly added items list since last sign\n\tupdateAddedFiles: =>\n\t\tPage.cmd \"fileGet\", \"content.json\", (res) =>\n\t\t\tif not res\n\t\t\t\treturn false\n\n\t\t\tcontent = JSON.parse(res)\n\n\t\t\t# Check new files\n\t\t\tif not content.files?\n\t\t\t\treturn false\n\n\t\t\t@files_added = {}\n\n\t\t\tfor file in @items\n\t\t\t\tif file.name == \"content.json\" or file.is_dir\n\t\t\t\t\tcontinue\n\t\t\t\tif not content.files[@inner_path + file.name]\n\t\t\t\t\t@files_added[@inner_path + file.name] = true\n\n\t\t\t# Check new dirs\n\t\t\t@dirs_added = {}\n\n\t\t\tdirs_content = {}\n\t\t\tfor file_name of Object.assign({}, content.files, content.files_optional)\n\t\t\t\tif not file_name.startsWith(@inner_path)\n\t\t\t\t\tcontinue\n\n\t\t\t\tpattern = new RegExp(\"#{@inner_path}(.*?)/\")\n\t\t\t\tmatch = file_name.match(pattern)\n\n\t\t\t\tif not match\n\t\t\t\t\tcontinue\n\n\t\t\t\tdirs_content[match[1]] = true\n\n\t\t\tfor file in @items\n\t\t\t\tif not file.is_dir\n\t\t\t\t\tcontinue\n\t\t\t\tif not dirs_content[file.name]\n\t\t\t\t\t@dirs_added[@inner_path + file.name] = true\n\n\t# Update optional files list\n\tupdateOptionalFiles: (cb) =>\n\t\tPage.cmd \"optionalFileList\", {filter: \"\"}, (res) =>\n\t\t\t@files_optional = {}\n\t\t\tfor optional_file in res\n\t\t\t\t@files_optional[optional_file.inner_path] = optional_file\n\n\t\t\t@addOptionalFilesToItems()\n\n\t\t\tcb?()\n\n\t# Add optional files to item list\n\taddOptionalFilesToItems: =>\n\t\tis_added = false\n\t\tfor inner_path, optional_file of @files_optional\n\t\t\tif optional_file.inner_path.startsWith(@inner_path)\n\t\t\t\tif @getDirectory(optional_file.inner_path) == @inner_path\n\t\t\t\t\t# Add optional file to list\n\t\t\t\t\tfile_name = @getFileName(optional_file.inner_path)\n\t\t\t\t\tif not @items_by_name[file_name]\n\t\t\t\t\t\trow = {\n\t\t\t\t\t\t\t\"name\": file_name, \"type\": \"file\", \"optional_empty\": true,\n\t\t\t\t\t\t\t\"size\": optional_file.size, \"is_dir\": false, \"inner_path\": optional_file.inner_path\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@items.push(row)\n\t\t\t\t\t\t@items_by_name[file_name] = row\n\t\t\t\t\t\tis_added = true\n\t\t\t\telse\n\t\t\t\t\t# Add optional dir to list\n\t\t\t\t\tdir_name = optional_file.inner_path.replace(@inner_path, \"\").match(/(.*?)\\//, \"\")?[1]\n\t\t\t\t\tif dir_name and not @items_by_name[dir_name]\n\t\t\t\t\t\trow = {\n\t\t\t\t\t\t\t\"name\": dir_name, \"type\": \"dir\", \"optional_empty\": true,\n\t\t\t\t\t\t\t\"size\": 0, \"is_dir\": true, \"inner_path\": optional_file.inner_path\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@items.push(row)\n\t\t\t\t\t\t@items_by_name[dir_name] = row\n\t\t\t\t\t\tis_added = true\n\n\t\tif is_added\n\t\t\t@sort()\n\n\tgetFileType: (file) =>\n\t\tif file.is_dir\n\t\t\treturn \"dir\"\n\t\telse\n\t\t\treturn \"unknown\"\n\n\tgetDirectory: (inner_path) ->\n\t\tif inner_path.indexOf(\"/\") != -1\n\t\t\treturn inner_path.replace(/^(.*\\/)(.*?)$/, \"$1\")\n\t\telse\n\t\t\treturn \"\"\n\n\tgetFileName: (inner_path) ->\n\t\treturn inner_path.replace(/^(.*\\/)(.*?)$/, \"$2\")\n\n\n\tisModified: (inner_path) =>\n\t\treturn @files_modified[inner_path] or @dirs_modified[inner_path]\n\n\tisAdded: (inner_path) =>\n\t\treturn @files_added[inner_path] or @dirs_added[inner_path]\n\n\thasPermissionDelete: (file) =>\n\t\tif file.type in [\"dir\", \"parent\"]\n\t\t\treturn false\n\n\t\tif file.inner_path == \"content.json\"\n\t\t\treturn false\n\n\t\toptional_info = @getOptionalInfo(file.inner_path)\n\t\tif optional_info and optional_info.downloaded_percent > 0\n\t\t\treturn true\n\t\telse\n\t\t\treturn Page.site_info?.settings?.own\n\n\tgetOptionalInfo: (inner_path) =>\n\t\treturn @files_optional[inner_path]\n\n\tsort: =>\n\t\t@items.sort (a, b) ->\n\t\t\treturn (b.is_dir - a.is_dir) || a.name.localeCompare(b.name)\n\n\nwindow.FileItemList = FileItemList"
  },
  {
    "path": "plugins/UiFileManager/media/js/FileList.coffee",
    "content": "class FileList extends Class\n\tconstructor: (@site, @inner_path, @is_owner=false) ->\n\t\t@need_update = true\n\t\t@error = null\n\t\t@url_root = \"/list/\" + @site + \"/\"\n\t\tif @inner_path\n\t\t\t@inner_path += \"/\"\n\t\t\t@url_root += @inner_path\n\t\t@log(\"inited\", @url_root)\n\t\t@item_list = new FileItemList(@inner_path)\n\t\t@item_list.items = @item_list.items\n\t\t@menu_create = new Menu()\n\n\t\t@select_action = null\n\t\t@selected = {}\n\t\t@selected_items_num = 0\n\t\t@selected_items_size = 0\n\t\t@selected_optional_empty_num = 0\n\n\tisSelectedAll: ->\n\t\tfalse\n\n\tupdate: =>\n\t\t@item_list.update =>\n\t\t\tdocument.body.classList.add(\"loaded\")\n\n\tgetHref: (inner_path) =>\n\t\treturn \"/\" + @site + \"/\" + inner_path\n\n\tgetListHref: (inner_path) =>\n\t\treturn \"/list/\" + @site + \"/\" + inner_path\n\n\tgetEditHref: (inner_path, mode=null) =>\n\t\thref = @url_root + \"?file=\" + inner_path\n\t\tif mode\n\t\t\thref += \"&edit_mode=#{mode}\"\n\t\treturn href\n\n\tcheckSelectedItems: =>\n\t\t@selected_items_num = 0\n\t\t@selected_items_size = 0\n\t\t@selected_optional_empty_num = 0\n\t\tfor item in @item_list.items\n\t\t\tif @selected[item.inner_path]\n\t\t\t\t@selected_items_num += 1\n\t\t\t\t@selected_items_size += item.size\n\t\t\t\toptional_info = @item_list.getOptionalInfo(item.inner_path)\n\t\t\t\tif optional_info and not optional_info.downloaded_percent > 0\n\t\t\t\t\t@selected_optional_empty_num += 1\n\n\thandleMenuCreateClick: =>\n\t\t@menu_create.items = []\n\t\t@menu_create.items.push [\"File\", @handleNewFileClick]\n\t\t@menu_create.items.push [\"Directory\", @handleNewDirectoryClick]\n\t\t@menu_create.toggle()\n\t\treturn false\n\n\thandleNewFileClick: =>\n\t\tPage.cmd \"wrapperPrompt\", \"New file name:\", (file_name) =>\n\t\t\twindow.top.location.href = @getEditHref(@inner_path + file_name, \"new\")\n\t\treturn false\n\n\thandleNewDirectoryClick: =>\n\t\tPage.cmd \"wrapperPrompt\", \"New directory name:\", (res) =>\n\t\t\talert(\"directory name #{res}\")\n\t\treturn false\n\n\thandleSelectClick: (e) =>\n\t\treturn false\n\n\thandleSelectEnd: (e) =>\n\t\tdocument.body.removeEventListener('mouseup', @handleSelectEnd)\n\t\t@select_action = null\n\n\thandleSelectMousedown: (e) =>\n\t\tinner_path = e.currentTarget.attributes.inner_path.value\n\t\tif @selected[inner_path]\n\t\t\tdelete @selected[inner_path]\n\t\t\t@select_action = \"deselect\"\n\t\telse\n\t\t\t@selected[inner_path] = true\n\t\t\t@select_action = \"select\"\n\t\t@checkSelectedItems()\n\t\tdocument.body.addEventListener('mouseup', @handleSelectEnd)\n\t\te.stopPropagation()\n\t\tPage.projector.scheduleRender()\n\t\treturn false\n\n\thandleRowMouseenter: (e) =>\n\t\tif e.buttons and @select_action\n\t\t\tinner_path = e.target.attributes.inner_path.value\n\t\t\tif @select_action == \"select\"\n\t\t\t\t@selected[inner_path] = true\n\t\t\telse\n\t\t\t\tdelete @selected[inner_path]\n\t\t\t@checkSelectedItems()\n\t\t\tPage.projector.scheduleRender()\n\t\treturn false\n\n\thandleSelectbarCancel: =>\n\t\t@selected = {}\n\t\t@checkSelectedItems()\n\t\tPage.projector.scheduleRender()\n\t\treturn false\n\n\thandleSelectbarDelete: (e, remove_optional=false) =>\n\t\tfor inner_path of @selected\n\t\t\toptional_info = @item_list.getOptionalInfo(inner_path)\n\t\t\tdelete @selected[inner_path]\n\t\t\tif optional_info and not remove_optional\n\t\t\t\tPage.cmd \"optionalFileDelete\", inner_path\n\t\t\telse\n\t\t\t\tPage.cmd \"fileDelete\", inner_path\n\t\t@need_update = true\n\t\tPage.projector.scheduleRender()\n\t\t@checkSelectedItems()\n\t\treturn false\n\n\thandleSelectbarRemoveOptional: (e) =>\n\t\treturn @handleSelectbarDelete(e, true)\n\n\trenderSelectbar: =>\n\t\th(\"div.selectbar\", {classes: {visible: @selected_items_num > 0}}, [\n\t\t\t\"Selected:\",\n\t\t\th(\"span.info\", [\n\t\t\t\th(\"span.num\", \"#{@selected_items_num} files\"),\n\t\t\t\th(\"span.size\", \"(#{Text.formatSize(@selected_items_size)})\"),\n\t\t\t])\n\t\t\th(\"div.actions\", [\n\t\t\t\tif @selected_optional_empty_num > 0\n\t\t\t\t\th(\"a.action.delete.remove_optional\", {href: \"#\", onclick: @handleSelectbarRemoveOptional}, \"Delete and remove optional\")\n\t\t\t\telse\n\t\t\t\t\th(\"a.action.delete\", {href: \"#\", onclick: @handleSelectbarDelete}, \"Delete\")\n\t\t\t])\n\t\t\th(\"a.cancel.link\", {href: \"#\", onclick: @handleSelectbarCancel}, \"Cancel\")\n\t\t])\n\n\trenderHead: =>\n\t\tparent_links = []\n\t\tinner_path_parent = \"\"\n\t\tfor parent_dir in @inner_path.split(\"/\")\n\t\t\tif not parent_dir\n\t\t\t\tcontinue\n\t\t\tif inner_path_parent\n\t\t\t\tinner_path_parent += \"/\"\n\t\t\tinner_path_parent += \"#{parent_dir}\"\n\t\t\tparent_links.push(\n\t\t\t\t[\" / \", h(\"a\", {href: @getListHref(inner_path_parent)}, parent_dir)]\n\t\t\t)\n\t\treturn h(\"div.tr.thead\", h(\"div.td.full\",\n\t\t\th(\"a\", {href: @getListHref(\"\")}, \"root\"),\n\t\t\tparent_links\n\t\t))\n\n\trenderItemCheckbox: (item) =>\n\t\tif not @item_list.hasPermissionDelete(item)\n\t\t\treturn [\" \"]\n\n\t\treturn h(\"a.checkbox-outer\", {\n\t\t\thref: \"#Select\",\n\t\t\tonmousedown: @handleSelectMousedown,\n\t\t\tonclick: @handleSelectClick,\n\t\t\tinner_path: item.inner_path\n\t\t}, h(\"span.checkbox\"))\n\n\trenderItem: (item) =>\n\t\tif item.type == \"parent\"\n\t\t\thref = @url_root.replace(/^(.*)\\/.{2,255}?$/, \"$1/\")\n\t\telse if item.type == \"dir\"\n\t\t\thref = @url_root + item.name\n\t\telse\n\t\t\thref = @url_root.replace(/^\\/list\\//, \"/\") + item.name\n\n\t\tinner_path = @inner_path + item.name\n\t\thref_edit = @getEditHref(inner_path)\n\t\tis_dir = item.type in [\"dir\", \"parent\"]\n\t\text = item.name.split(\".\").pop()\n\n\t\tis_editing = inner_path == Page.file_editor?.inner_path\n\t\tis_editable = not is_dir and item.size < 1024 * 1024 and ext not in window.BINARY_EXTENSIONS\n\t\tis_modified = @item_list.isModified(inner_path)\n\t\tis_added = @item_list.isAdded(inner_path)\n\t\toptional_info = @item_list.getOptionalInfo(inner_path)\n\n\t\tstyle = \"\"\n\t\ttitle = \"\"\n\n\t\tif optional_info\n\t\t\tdownloaded_percent = optional_info.downloaded_percent\n\t\t\tif not downloaded_percent\n\t\t\t\tdownloaded_percent = 0\n\t\t\tstyle += \"background: linear-gradient(90deg, #fff6dd, #{downloaded_percent}%, white, #{downloaded_percent}%, white);\"\n\t\t\tis_added = false\n\n\t\tif item.ignored\n\t\t\tis_added = false\n\n\t\tif is_modified then title += \" (modified)\"\n\t\tif is_added then title += \" (new)\"\n\t\tif optional_info or item.optional_empty then title += \" (optional)\"\n\t\tif item.ignored then title += \" (ignored from content.json)\"\n\n\t\tclasses = {\n\t\t\t\"type-#{item.type}\": true, editing: is_editing, nobuttons: not is_editable, selected: @selected[inner_path],\n\t\t\tmodified: is_modified, added: is_added, ignored: item.ignored, optional: optional_info, optional_empty: item.optional_empty\n\t\t}\n\n\t\th(\"div.tr\", {key: item.name, classes: classes, style: style, onmouseenter: @handleRowMouseenter, inner_path: inner_path}, [\n\t\t\th(\"div.td.pre\", {title: title},\n\t\t\t\t@renderItemCheckbox(item)\n\t\t\t),\n\t\t\th(\"div.td.name\", h(\"a.link\", {href: href}, item.name))\n\t\t\th(\"div.td.buttons\", if is_editable then h(\"a.edit\", {href: href_edit}, if Page.site_info.settings.own then \"Edit\" else \"View\"))\n\t\t\th(\"div.td.size\", if is_dir then \"[DIR]\" else Text.formatSize(item.size))\n\t\t])\n\n\n\trenderItems: =>\n\t\treturn [\n\t\t\tif @item_list.error and not @item_list.items.length and not @item_list.updating then [\n\t\t\t\t\th(\"div.tr\", {key: \"error\"}, h(\"div.td.full.error\", @item_list.error))\n\t\t\t\t],\n\t\t\tif @inner_path then @renderItem({\"name\": \"..\", type: \"parent\", size: 0})\n\t\t\t@item_list.items.map @renderItem\n\t\t]\n\n\trenderFoot: =>\n\t\tfiles = (item for item in @item_list.items when item.type not in [\"parent\", \"dir\"])\n\t\tdirs = (item for item in @item_list.items when item.type == \"dir\")\n\t\tif files.length\n\t\t\ttotal_size = (item.size for file in files).reduce (a, b) -> a + b\n\t\telse\n\t\t\ttotal_size = 0\n\n\t\tfoot_text = \"Total: \"\n\t\tfoot_text += \"#{dirs.length} dir, #{files.length} file in #{Text.formatSize(total_size)}\"\n\n\t\treturn [\n\t\t\tif dirs.length or files.length or Page.site_info?.settings?.own\n\t\t\t\th(\"div.tr.foot-info.foot\", h(\"div.td.full\", [\n\t\t\t\t\tif @item_list.updating\n\t\t\t\t\t\t\"Updating file list...\"\n\t\t\t\t\telse\n\t\t\t\t\t\tif dirs.length or files.length then foot_text\n\t\t\t\t\tif Page.site_info?.settings?.own\n\t\t\t\t\t\th(\"div.create\", [\n\t\t\t\t\t\t\th(\"a.link\", {href: \"#Create+new+file\", onclick: @handleNewFileClick}, \"+ New\")\n\t\t\t\t\t\t\t@menu_create.render()\n\t\t\t\t\t\t])\n\t\t\t\t]))\n\t\t]\n\n\trender: =>\n\t\tif @need_update\n\t\t\t@update()\n\t\t\t@need_update = false\n\n\t\t\tif not @item_list.items\n\t\t\t\treturn []\n\n\t\treturn h(\"div.files\", [\n\t\t\t@renderSelectbar(),\n\t\t\t@renderHead(),\n\t\t\th(\"div.tbody\", @renderItems()),\n\t\t\t@renderFoot()\n\t\t])\n\nwindow.FileList = FileList\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/UiFileManager.coffee",
    "content": "window.h = maquette.h\n\nclass UiFileManager extends ZeroFrame\n\tinit: ->\n\t\t@url_params = new URLSearchParams(window.location.search)\n\t\t@list_site = @url_params.get(\"site\")\n\t\t@list_address = @url_params.get(\"address\")\n\t\t@list_inner_path = @url_params.get(\"inner_path\")\n\t\t@editor_inner_path = @url_params.get(\"file\")\n\t\t@file_list = new FileList(@list_site, @list_inner_path)\n\n\t\t@site_info = null\n\t\t@server_info = null\n\n\t\t@is_sidebar_closed = false\n\n\t\tif @editor_inner_path\n\t\t\t@file_editor = new FileEditor(@editor_inner_path)\n\n\t\twindow.onbeforeunload = =>\n\t\t\tif @file_editor?.isModified()\n\t\t\t\treturn true\n\t\t\telse\n\t\t\t\treturn null\n\n\t\twindow.onresize = =>\n\t\t\t@checkBodyWidth()\n\n\t\t@checkBodyWidth()\n\n\t\t@cmd(\"wrapperSetViewport\", \"width=device-width, initial-scale=0.8\")\n\n\t\t@cmd \"serverInfo\", {}, (server_info) =>\n\t\t\t@server_info = server_info\n\t\t@cmd \"siteInfo\", {}, (site_info) =>\n\t\t\t@cmd(\"wrapperSetTitle\", \"List: /#{@list_inner_path} - #{site_info.content.title} - ZeroNet\")\n\t\t\t@site_info = site_info\n\t\t\tif @file_editor then @file_editor.on_loaded.then =>\n\t\t\t\t@file_editor.cm.setOption(\"readOnly\", not site_info.settings.own)\n\t\t\t\t@file_editor.mode = if site_info.settings.own then \"Edit\" else \"View\"\n\t\t\t@projector.scheduleRender()\n\n\tcheckBodyWidth: =>\n\t\tif not @file_editor\n\t\t\treturn false\n\n\t\tif document.body.offsetWidth < 960 and not @is_sidebar_closed\n\t\t\t@is_sidebar_closed = true\n\t\t\t@projector?.scheduleRender()\n\t\telse if document.body.offsetWidth > 960 and @is_sidebar_closed\n\t\t\t@is_sidebar_closed = false\n\t\t\t@projector?.scheduleRender()\n\n\tonRequest: (cmd, message) =>\n\t\tif cmd == \"setSiteInfo\"\n\t\t\t@site_info = message\n\t\t\tRateLimitCb 1000, (cb_done) =>\n\t\t\t\t@file_list.update(cb_done)\n\t\t\t@projector.scheduleRender()\n\t\telse if cmd == \"setServerInfo\"\n\t\t\t@server_info = message\n\t\t\t@projector.scheduleRender()\n\t\telse\n\t\t\t@log \"Unknown incoming message:\", cmd\n\n\tcreateProjector: =>\n\t\t@projector = maquette.createProjector()\n\t\t@projector.replace($(\"#content\"), @render)\n\n\trender: =>\n\t\treturn h(\"div.content#content\", [\n\t\t\th(\"div.manager\", {classes: {editing: @file_editor, sidebar_closed: @is_sidebar_closed}}, [\n\t\t\t\t@file_list.render(),\n\t\t\t\tif @file_editor then @file_editor.render()\n\t\t\t])\n\t\t])\n\nwindow.Page = new UiFileManager()\nwindow.Page.createProjector()\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/all.js",
    "content": "\n/* ---- lib/Animation.coffee ---- */\n\n\n(function() {\n  var Animation;\n\n  Animation = (function() {\n    function Animation() {}\n\n    Animation.prototype.slideDown = function(elem, props) {\n      var cstyle, h, margin_bottom, margin_top, padding_bottom, padding_top, transition;\n      if (elem.offsetTop > 2000) {\n        return;\n      }\n      h = elem.offsetHeight;\n      cstyle = window.getComputedStyle(elem);\n      margin_top = cstyle.marginTop;\n      margin_bottom = cstyle.marginBottom;\n      padding_top = cstyle.paddingTop;\n      padding_bottom = cstyle.paddingBottom;\n      transition = cstyle.transition;\n      elem.style.boxSizing = \"border-box\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(0.6)\";\n      elem.style.opacity = \"0\";\n      elem.style.height = \"0px\";\n      elem.style.marginTop = \"0px\";\n      elem.style.marginBottom = \"0px\";\n      elem.style.paddingTop = \"0px\";\n      elem.style.paddingBottom = \"0px\";\n      elem.style.transition = \"none\";\n      setTimeout((function() {\n        elem.className += \" animate-inout\";\n        elem.style.height = h + \"px\";\n        elem.style.transform = \"scale(1)\";\n        elem.style.opacity = \"1\";\n        elem.style.marginTop = margin_top;\n        elem.style.marginBottom = margin_bottom;\n        elem.style.paddingTop = padding_top;\n        return elem.style.paddingBottom = padding_bottom;\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate-inout\");\n        elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null;\n        elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null;\n        elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null;\n        return elem.removeEventListener(\"transitionend\", arguments.callee, false);\n      });\n    };\n\n    Animation.prototype.slideUp = function(elem, remove_func, props) {\n      if (elem.offsetTop > 1000) {\n        return remove_func();\n      }\n      elem.className += \" animate-back\";\n      elem.style.boxSizing = \"border-box\";\n      elem.style.height = elem.offsetHeight + \"px\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(1)\";\n      elem.style.opacity = \"1\";\n      elem.style.pointerEvents = \"none\";\n      setTimeout((function() {\n        elem.style.height = \"0px\";\n        elem.style.marginTop = \"0px\";\n        elem.style.marginBottom = \"0px\";\n        elem.style.paddingTop = \"0px\";\n        elem.style.paddingBottom = \"0px\";\n        elem.style.transform = \"scale(0.8)\";\n        elem.style.borderTopWidth = \"0px\";\n        elem.style.borderBottomWidth = \"0px\";\n        return elem.style.opacity = \"0\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\" || e.elapsedTime >= 0.6) {\n          elem.removeEventListener(\"transitionend\", arguments.callee, false);\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.slideUpInout = function(elem, remove_func, props) {\n      elem.className += \" animate-inout\";\n      elem.style.boxSizing = \"border-box\";\n      elem.style.height = elem.offsetHeight + \"px\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(1)\";\n      elem.style.opacity = \"1\";\n      elem.style.pointerEvents = \"none\";\n      setTimeout((function() {\n        elem.style.height = \"0px\";\n        elem.style.marginTop = \"0px\";\n        elem.style.marginBottom = \"0px\";\n        elem.style.paddingTop = \"0px\";\n        elem.style.paddingBottom = \"0px\";\n        elem.style.transform = \"scale(0.8)\";\n        elem.style.borderTopWidth = \"0px\";\n        elem.style.borderBottomWidth = \"0px\";\n        return elem.style.opacity = \"0\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\" || e.elapsedTime >= 0.6) {\n          elem.removeEventListener(\"transitionend\", arguments.callee, false);\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.showRight = function(elem, props) {\n      elem.className += \" animate\";\n      elem.style.opacity = 0;\n      elem.style.transform = \"TranslateX(-20px) Scale(1.01)\";\n      setTimeout((function() {\n        elem.style.opacity = 1;\n        return elem.style.transform = \"TranslateX(0px) Scale(1)\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate\");\n        return elem.style.transform = elem.style.opacity = null;\n      });\n    };\n\n    Animation.prototype.show = function(elem, props) {\n      var delay, ref;\n      delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;\n      elem.style.opacity = 0;\n      setTimeout((function() {\n        return elem.className += \" animate\";\n      }), 1);\n      setTimeout((function() {\n        return elem.style.opacity = 1;\n      }), delay);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate\");\n        elem.style.opacity = null;\n        return elem.removeEventListener(\"transitionend\", arguments.callee, false);\n      });\n    };\n\n    Animation.prototype.hide = function(elem, remove_func, props) {\n      var delay, ref;\n      delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;\n      elem.className += \" animate\";\n      setTimeout((function() {\n        return elem.style.opacity = 0;\n      }), delay);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\") {\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.addVisibleClass = function(elem, props) {\n      return setTimeout(function() {\n        return elem.classList.add(\"visible\");\n      });\n    };\n\n    return Animation;\n\n  })();\n\n  window.Animation = new Animation();\n\n}).call(this);\n\n/* ---- lib/Class.coffee ---- */\n\n\n(function() {\n  var Class,\n    slice = [].slice;\n\n  Class = (function() {\n    function Class() {}\n\n    Class.prototype.trace = true;\n\n    Class.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      if (!this.trace) {\n        return;\n      }\n      if (typeof console === 'undefined') {\n        return;\n      }\n      args.unshift(\"[\" + this.constructor.name + \"]\");\n      console.log.apply(console, args);\n      return this;\n    };\n\n    Class.prototype.logStart = function() {\n      var args, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      if (!this.trace) {\n        return;\n      }\n      this.logtimers || (this.logtimers = {});\n      this.logtimers[name] = +(new Date);\n      if (args.length > 0) {\n        this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(started)\"]));\n      }\n      return this;\n    };\n\n    Class.prototype.logEnd = function() {\n      var args, ms, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      ms = +(new Date) - this.logtimers[name];\n      this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(Done in \" + ms + \"ms)\"]));\n      return this;\n    };\n\n    return Class;\n\n  })();\n\n  window.Class = Class;\n\n}).call(this);\n\n/* ---- lib/Dollar.coffee ---- */\n\n\n(function() {\n  window.$ = function(selector) {\n    if (selector.startsWith(\"#\")) {\n      return document.getElementById(selector.replace(\"#\", \"\"));\n    }\n  };\n\n}).call(this);\n\n/* ---- lib/ItemList.coffee ---- */\n\n\n(function() {\n  var ItemList;\n\n  ItemList = (function() {\n    function ItemList(item_class1, key1) {\n      this.item_class = item_class1;\n      this.key = key1;\n      this.items = [];\n      this.items_bykey = {};\n    }\n\n    ItemList.prototype.sync = function(rows, item_class, key) {\n      var current_obj, i, item, len, results, row;\n      this.items.splice(0, this.items.length);\n      results = [];\n      for (i = 0, len = rows.length; i < len; i++) {\n        row = rows[i];\n        current_obj = this.items_bykey[row[this.key]];\n        if (current_obj) {\n          current_obj.row = row;\n          results.push(this.items.push(current_obj));\n        } else {\n          item = new this.item_class(row, this);\n          this.items_bykey[row[this.key]] = item;\n          results.push(this.items.push(item));\n        }\n      }\n      return results;\n    };\n\n    ItemList.prototype.deleteItem = function(item) {\n      var index;\n      index = this.items.indexOf(item);\n      if (index > -1) {\n        this.items.splice(index, 1);\n      } else {\n        console.log(\"Can't delete item\", item);\n      }\n      return delete this.items_bykey[item.row[this.key]];\n    };\n\n    return ItemList;\n\n  })();\n\n  window.ItemList = ItemList;\n\n}).call(this);\n\n/* ---- lib/Menu.coffee ---- */\n\n\n(function() {\n  var Menu,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n  Menu = (function() {\n    function Menu() {\n      this.render = bind(this.render, this);\n      this.getStyle = bind(this.getStyle, this);\n      this.renderItem = bind(this.renderItem, this);\n      this.handleClick = bind(this.handleClick, this);\n      this.getDirection = bind(this.getDirection, this);\n      this.storeNode = bind(this.storeNode, this);\n      this.toggle = bind(this.toggle, this);\n      this.hide = bind(this.hide, this);\n      this.show = bind(this.show, this);\n      this.visible = false;\n      this.items = [];\n      this.node = null;\n      this.height = 0;\n      this.direction = \"bottom\";\n    }\n\n    Menu.prototype.show = function() {\n      var ref;\n      if ((ref = window.visible_menu) != null) {\n        ref.hide();\n      }\n      this.visible = true;\n      window.visible_menu = this;\n      return this.direction = this.getDirection();\n    };\n\n    Menu.prototype.hide = function() {\n      return this.visible = false;\n    };\n\n    Menu.prototype.toggle = function() {\n      if (this.visible) {\n        this.hide();\n      } else {\n        this.show();\n      }\n      return Page.projector.scheduleRender();\n    };\n\n    Menu.prototype.addItem = function(title, cb, selected) {\n      if (selected == null) {\n        selected = false;\n      }\n      return this.items.push([title, cb, selected]);\n    };\n\n    Menu.prototype.storeNode = function(node) {\n      this.node = node;\n      if (this.visible) {\n        node.className = node.className.replace(\"visible\", \"\");\n        setTimeout(((function(_this) {\n          return function() {\n            node.className += \" visible\";\n            return node.attributes.style.value = _this.getStyle();\n          };\n        })(this)), 20);\n        node.style.maxHeight = \"none\";\n        this.height = node.offsetHeight;\n        node.style.maxHeight = \"0px\";\n        return this.direction = this.getDirection();\n      }\n    };\n\n    Menu.prototype.getDirection = function() {\n      if (this.node && this.node.parentNode.getBoundingClientRect().top + this.height + 60 > document.body.clientHeight && this.node.parentNode.getBoundingClientRect().top - this.height > 0) {\n        return \"top\";\n      } else {\n        return \"bottom\";\n      }\n    };\n\n    Menu.prototype.handleClick = function(e) {\n      var cb, i, item, keep_menu, len, ref, selected, title;\n      keep_menu = false;\n      ref = this.items;\n      for (i = 0, len = ref.length; i < len; i++) {\n        item = ref[i];\n        title = item[0], cb = item[1], selected = item[2];\n        if (title === e.currentTarget.textContent || e.currentTarget[\"data-title\"] === title) {\n          keep_menu = typeof cb === \"function\" ? cb(item) : void 0;\n          break;\n        }\n      }\n      if (keep_menu !== true && cb !== null) {\n        this.hide();\n      }\n      return false;\n    };\n\n    Menu.prototype.renderItem = function(item) {\n      var cb, classes, href, onclick, selected, title;\n      title = item[0], cb = item[1], selected = item[2];\n      if (typeof selected === \"function\") {\n        selected = selected();\n      }\n      if (title === \"---\") {\n        return h(\"div.menu-item-separator\", {\n          key: Time.timestamp()\n        });\n      } else {\n        if (cb === null) {\n          href = void 0;\n          onclick = this.handleClick;\n        } else if (typeof cb === \"string\") {\n          href = cb;\n          onclick = true;\n        } else {\n          href = \"#\" + title;\n          onclick = this.handleClick;\n        }\n        classes = {\n          \"selected\": selected,\n          \"noaction\": cb === null\n        };\n        return h(\"a.menu-item\", {\n          href: href,\n          onclick: onclick,\n          \"data-title\": title,\n          key: title,\n          classes: classes\n        }, title);\n      }\n    };\n\n    Menu.prototype.getStyle = function() {\n      var max_height, style;\n      if (this.visible) {\n        max_height = this.height;\n      } else {\n        max_height = 0;\n      }\n      style = \"max-height: \" + max_height + \"px\";\n      if (this.direction === \"top\") {\n        style += \";margin-top: \" + (0 - this.height - 50) + \"px\";\n      } else {\n        style += \";margin-top: 0px\";\n      }\n      return style;\n    };\n\n    Menu.prototype.render = function(class_name) {\n      if (class_name == null) {\n        class_name = \"\";\n      }\n      if (this.visible || this.node) {\n        return h(\"div.menu\" + class_name, {\n          classes: {\n            \"visible\": this.visible\n          },\n          style: this.getStyle(),\n          afterCreate: this.storeNode\n        }, this.items.map(this.renderItem));\n      }\n    };\n\n    return Menu;\n\n  })();\n\n  window.Menu = Menu;\n\n  document.body.addEventListener(\"mouseup\", function(e) {\n    var menu_node, menu_parents, ref, ref1;\n    if (!window.visible_menu || !window.visible_menu.node) {\n      return false;\n    }\n    menu_node = window.visible_menu.node;\n    menu_parents = [menu_node, menu_node.parentNode];\n    if ((ref = e.target.parentNode, indexOf.call(menu_parents, ref) < 0) && (ref1 = e.target.parentNode.parentNode, indexOf.call(menu_parents, ref1) < 0)) {\n      window.visible_menu.hide();\n      return Page.projector.scheduleRender();\n    }\n  });\n\n}).call(this);\n\n/* ---- lib/Promise.coffee ---- */\n\n\n(function() {\n  var Promise,\n    slice = [].slice;\n\n  Promise = (function() {\n    Promise.when = function() {\n      var args, fn, i, len, num_uncompleted, promise, task, task_id, tasks;\n      tasks = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      num_uncompleted = tasks.length;\n      args = new Array(num_uncompleted);\n      promise = new Promise();\n      fn = function(task_id) {\n        return task.then(function() {\n          args[task_id] = Array.prototype.slice.call(arguments);\n          num_uncompleted--;\n          if (num_uncompleted === 0) {\n            return promise.complete.apply(promise, args);\n          }\n        });\n      };\n      for (task_id = i = 0, len = tasks.length; i < len; task_id = ++i) {\n        task = tasks[task_id];\n        fn(task_id);\n      }\n      return promise;\n    };\n\n    function Promise() {\n      this.resolved = false;\n      this.end_promise = null;\n      this.result = null;\n      this.callbacks = [];\n    }\n\n    Promise.prototype.resolve = function() {\n      var back, callback, i, len, ref;\n      if (this.resolved) {\n        return false;\n      }\n      this.resolved = true;\n      this.data = arguments;\n      if (!arguments.length) {\n        this.data = [true];\n      }\n      this.result = this.data[0];\n      ref = this.callbacks;\n      for (i = 0, len = ref.length; i < len; i++) {\n        callback = ref[i];\n        back = callback.apply(callback, this.data);\n      }\n      if (this.end_promise) {\n        return this.end_promise.resolve(back);\n      }\n    };\n\n    Promise.prototype.fail = function() {\n      return this.resolve(false);\n    };\n\n    Promise.prototype.then = function(callback) {\n      if (this.resolved === true) {\n        callback.apply(callback, this.data);\n        return;\n      }\n      this.callbacks.push(callback);\n      return this.end_promise = new Promise();\n    };\n\n    return Promise;\n\n  })();\n\n  window.Promise = Promise;\n\n\n  /*\n  s = Date.now()\n  log = (text) ->\n  \tconsole.log Date.now()-s, Array.prototype.slice.call(arguments).join(\", \")\n  \n  log \"Started\"\n  \n  cmd = (query) ->\n  \tp = new Promise()\n  \tsetTimeout ( ->\n  \t\tp.resolve query+\" Result\"\n  \t), 100\n  \treturn p\n  \n  back = cmd(\"SELECT * FROM message\").then (res) ->\n  \tlog res\n  \treturn \"Return from query\"\n  .then (res) ->\n  \tlog \"Back then\", res\n  \n  log \"Query started\", back\n   */\n\n}).call(this);\n\n/* ---- lib/Prototypes.coffee ---- */\n\n\n(function() {\n  String.prototype.startsWith = function(s) {\n    return this.slice(0, s.length) === s;\n  };\n\n  String.prototype.endsWith = function(s) {\n    return s === '' || this.slice(-s.length) === s;\n  };\n\n  String.prototype.repeat = function(count) {\n    return new Array(count + 1).join(this);\n  };\n\n  window.isEmpty = function(obj) {\n    var key;\n    for (key in obj) {\n      return false;\n    }\n    return true;\n  };\n\n}).call(this);\n\n/* ---- lib/RateLimitCb.coffee ---- */\n\n\n(function() {\n  var call_after_interval, calling, calling_iterval, last_time,\n    slice = [].slice;\n\n  last_time = {};\n\n  calling = {};\n\n  calling_iterval = {};\n\n  call_after_interval = {};\n\n  window.RateLimitCb = function(interval, fn, args) {\n    var cb;\n    if (args == null) {\n      args = [];\n    }\n    cb = function() {\n      var left;\n      left = interval - (Date.now() - last_time[fn]);\n      if (left <= 0) {\n        delete last_time[fn];\n        if (calling[fn]) {\n          RateLimitCb(interval, fn, calling[fn]);\n        }\n        return delete calling[fn];\n      } else {\n        return setTimeout((function() {\n          delete last_time[fn];\n          if (calling[fn]) {\n            RateLimitCb(interval, fn, calling[fn]);\n          }\n          return delete calling[fn];\n        }), left);\n      }\n    };\n    if (last_time[fn]) {\n      return calling[fn] = args;\n    } else {\n      last_time[fn] = Date.now();\n      return fn.apply(this, [cb].concat(slice.call(args)));\n    }\n  };\n\n  window.RateLimit = function(interval, fn) {\n    if (calling_iterval[fn] > interval) {\n      clearInterval(calling[fn]);\n      delete calling[fn];\n    }\n    if (!calling[fn]) {\n      call_after_interval[fn] = false;\n      fn();\n      calling_iterval[fn] = interval;\n      return calling[fn] = setTimeout((function() {\n        if (call_after_interval[fn]) {\n          fn();\n        }\n        delete calling[fn];\n        return delete call_after_interval[fn];\n      }), interval);\n    } else {\n      return call_after_interval[fn] = true;\n    }\n  };\n\n\n  /*\n  window.s = Date.now()\n  window.load = (done, num) ->\n    console.log \"Loading #{num}...\", Date.now()-window.s\n    setTimeout (-> done()), 1000\n  \n  RateLimit 500, window.load, [0] # Called instantly\n  RateLimit 500, window.load, [1]\n  setTimeout (-> RateLimit 500, window.load, [300]), 300\n  setTimeout (-> RateLimit 500, window.load, [600]), 600 # Called after 1000ms\n  setTimeout (-> RateLimit 500, window.load, [1000]), 1000\n  setTimeout (-> RateLimit 500, window.load, [1200]), 1200  # Called after 2000ms\n  setTimeout (-> RateLimit 500, window.load, [3000]), 3000  # Called after 3000ms\n   */\n\n}).call(this);\n\n/* ---- lib/Text.coffee ---- */\n\n\n(function() {\n  var Text,\n    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n  Text = (function() {\n    function Text() {}\n\n    Text.prototype.toColor = function(text, saturation, lightness) {\n      var hash, i, j, ref;\n      if (saturation == null) {\n        saturation = 30;\n      }\n      if (lightness == null) {\n        lightness = 50;\n      }\n      hash = 0;\n      for (i = j = 0, ref = text.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {\n        hash += text.charCodeAt(i) * i;\n        hash = hash % 1777;\n      }\n      return \"hsl(\" + (hash % 360) + (\",\" + saturation + \"%,\" + lightness + \"%)\");\n    };\n\n    Text.prototype.renderMarked = function(text, options) {\n      if (options == null) {\n        options = {};\n      }\n      options[\"gfm\"] = true;\n      options[\"breaks\"] = true;\n      options[\"sanitize\"] = true;\n      options[\"renderer\"] = marked_renderer;\n      text = marked(text, options);\n      return this.fixHtmlLinks(text);\n    };\n\n    Text.prototype.emailLinks = function(text) {\n      return text.replace(/([a-zA-Z0-9]+)@zeroid.bit/g, \"<a href='?to=$1' onclick='return Page.message_create.show(\\\"$1\\\")'>$1@zeroid.bit</a>\");\n    };\n\n    Text.prototype.fixHtmlLinks = function(text) {\n      if (window.is_proxy) {\n        return text.replace(/href=\"http:\\/\\/(127.0.0.1|localhost):43110/g, 'href=\"http://zero');\n      } else {\n        return text.replace(/href=\"http:\\/\\/(127.0.0.1|localhost):43110/g, 'href=\"');\n      }\n    };\n\n    Text.prototype.fixLink = function(link) {\n      var back;\n      if (window.is_proxy) {\n        back = link.replace(/http:\\/\\/(127.0.0.1|localhost):43110/, 'http://zero');\n        return back.replace(/http:\\/\\/zero\\/([^\\/]+\\.bit)/, \"http://$1\");\n      } else {\n        return link.replace(/http:\\/\\/(127.0.0.1|localhost):43110/, '');\n      }\n    };\n\n    Text.prototype.toUrl = function(text) {\n      return text.replace(/[^A-Za-z0-9]/g, \"+\").replace(/[+]+/g, \"+\").replace(/[+]+$/, \"\");\n    };\n\n    Text.prototype.getSiteUrl = function(address) {\n      if (window.is_proxy) {\n        if (indexOf.call(address, \".\") >= 0) {\n          return \"http://\" + address + \"/\";\n        } else {\n          return \"http://zero/\" + address + \"/\";\n        }\n      } else {\n        return \"/\" + address + \"/\";\n      }\n    };\n\n    Text.prototype.fixReply = function(text) {\n      return text.replace(/(>.*\\n)([^\\n>])/gm, \"$1\\n$2\");\n    };\n\n    Text.prototype.toBitcoinAddress = function(text) {\n      return text.replace(/[^A-Za-z0-9]/g, \"\");\n    };\n\n    Text.prototype.jsonEncode = function(obj) {\n      return unescape(encodeURIComponent(JSON.stringify(obj)));\n    };\n\n    Text.prototype.jsonDecode = function(obj) {\n      return JSON.parse(decodeURIComponent(escape(obj)));\n    };\n\n    Text.prototype.fileEncode = function(obj) {\n      if (typeof obj === \"string\") {\n        return btoa(unescape(encodeURIComponent(obj)));\n      } else {\n        return btoa(unescape(encodeURIComponent(JSON.stringify(obj, void 0, '\\t'))));\n      }\n    };\n\n    Text.prototype.utf8Encode = function(s) {\n      return unescape(encodeURIComponent(s));\n    };\n\n    Text.prototype.utf8Decode = function(s) {\n      return decodeURIComponent(escape(s));\n    };\n\n    Text.prototype.distance = function(s1, s2) {\n      var char, extra_parts, j, key, len, match, next_find, next_find_i, val;\n      s1 = s1.toLocaleLowerCase();\n      s2 = s2.toLocaleLowerCase();\n      next_find_i = 0;\n      next_find = s2[0];\n      match = true;\n      extra_parts = {};\n      for (j = 0, len = s1.length; j < len; j++) {\n        char = s1[j];\n        if (char !== next_find) {\n          if (extra_parts[next_find_i]) {\n            extra_parts[next_find_i] += char;\n          } else {\n            extra_parts[next_find_i] = char;\n          }\n        } else {\n          next_find_i++;\n          next_find = s2[next_find_i];\n        }\n      }\n      if (extra_parts[next_find_i]) {\n        extra_parts[next_find_i] = \"\";\n      }\n      extra_parts = (function() {\n        var results;\n        results = [];\n        for (key in extra_parts) {\n          val = extra_parts[key];\n          results.push(val);\n        }\n        return results;\n      })();\n      if (next_find_i >= s2.length) {\n        return extra_parts.length + extra_parts.join(\"\").length;\n      } else {\n        return false;\n      }\n    };\n\n    Text.prototype.parseQuery = function(query) {\n      var j, key, len, params, part, parts, ref, val;\n      params = {};\n      parts = query.split('&');\n      for (j = 0, len = parts.length; j < len; j++) {\n        part = parts[j];\n        ref = part.split(\"=\"), key = ref[0], val = ref[1];\n        if (val) {\n          params[decodeURIComponent(key)] = decodeURIComponent(val);\n        } else {\n          params[\"url\"] = decodeURIComponent(key);\n        }\n      }\n      return params;\n    };\n\n    Text.prototype.encodeQuery = function(params) {\n      var back, key, val;\n      back = [];\n      if (params.url) {\n        back.push(params.url);\n      }\n      for (key in params) {\n        val = params[key];\n        if (!val || key === \"url\") {\n          continue;\n        }\n        back.push((encodeURIComponent(key)) + \"=\" + (encodeURIComponent(val)));\n      }\n      return back.join(\"&\");\n    };\n\n    Text.prototype.highlight = function(text, search) {\n      var back, i, j, len, part, parts;\n      if (!text) {\n        return [\"\"];\n      }\n      parts = text.split(RegExp(search, \"i\"));\n      back = [];\n      for (i = j = 0, len = parts.length; j < len; i = ++j) {\n        part = parts[i];\n        back.push(part);\n        if (i < parts.length - 1) {\n          back.push(h(\"span.highlight\", {\n            key: i\n          }, search));\n        }\n      }\n      return back;\n    };\n\n    Text.prototype.formatSize = function(size) {\n      var size_mb;\n      if (isNaN(parseInt(size))) {\n        return \"\";\n      }\n      size_mb = size / 1024 / 1024;\n      if (size_mb >= 1000) {\n        return (size_mb / 1024).toFixed(1) + \" GB\";\n      } else if (size_mb >= 100) {\n        return size_mb.toFixed(0) + \" MB\";\n      } else if (size / 1024 >= 1000) {\n        return size_mb.toFixed(2) + \" MB\";\n      } else {\n        return (parseInt(size) / 1024).toFixed(2) + \" KB\";\n      }\n    };\n\n    return Text;\n\n  })();\n\n  window.is_proxy = document.location.host === \"zero\" || window.location.pathname === \"/\";\n\n  window.Text = new Text();\n\n}).call(this);\n\n/* ---- lib/Time.coffee ---- */\n\n\n(function() {\n  var Time;\n\n  Time = (function() {\n    function Time() {}\n\n    Time.prototype.since = function(timestamp) {\n      var back, minutes, now, secs;\n      now = +(new Date) / 1000;\n      if (timestamp > 1000000000000) {\n        timestamp = timestamp / 1000;\n      }\n      secs = now - timestamp;\n      if (secs < 60) {\n        back = \"Just now\";\n      } else if (secs < 60 * 60) {\n        minutes = Math.round(secs / 60);\n        back = \"\" + minutes + \" minutes ago\";\n      } else if (secs < 60 * 60 * 24) {\n        back = (Math.round(secs / 60 / 60)) + \" hours ago\";\n      } else if (secs < 60 * 60 * 24 * 3) {\n        back = (Math.round(secs / 60 / 60 / 24)) + \" days ago\";\n      } else {\n        back = \"on \" + this.date(timestamp);\n      }\n      back = back.replace(/^1 ([a-z]+)s/, \"1 $1\");\n      return back;\n    };\n\n    Time.prototype.dateIso = function(timestamp) {\n      var tzoffset;\n      if (timestamp == null) {\n        timestamp = null;\n      }\n      if (!timestamp) {\n        timestamp = window.Time.timestamp();\n      }\n      if (timestamp > 1000000000000) {\n        timestamp = timestamp / 1000;\n      }\n      tzoffset = (new Date()).getTimezoneOffset() * 60;\n      return (new Date((timestamp - tzoffset) * 1000)).toISOString().split(\"T\")[0];\n    };\n\n    Time.prototype.date = function(timestamp, format) {\n      var display, parts;\n      if (timestamp == null) {\n        timestamp = null;\n      }\n      if (format == null) {\n        format = \"short\";\n      }\n      if (!timestamp) {\n        timestamp = window.Time.timestamp();\n      }\n      if (timestamp > 1000000000000) {\n        timestamp = timestamp / 1000;\n      }\n      parts = (new Date(timestamp * 1000)).toString().split(\" \");\n      if (format === \"short\") {\n        display = parts.slice(1, 4);\n      } else if (format === \"day\") {\n        display = parts.slice(1, 3);\n      } else if (format === \"month\") {\n        display = [parts[1], parts[3]];\n      } else if (format === \"long\") {\n        display = parts.slice(1, 5);\n      }\n      return display.join(\" \").replace(/( [0-9]{4})/, \",$1\");\n    };\n\n    Time.prototype.weekDay = function(timestamp) {\n      if (timestamp > 1000000000000) {\n        timestamp = timestamp / 1000;\n      }\n      return [\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"][(new Date(timestamp * 1000)).getDay()];\n    };\n\n    Time.prototype.timestamp = function(date) {\n      if (date == null) {\n        date = \"\";\n      }\n      if (date === \"now\" || date === \"\") {\n        return parseInt(+(new Date) / 1000);\n      } else {\n        return parseInt(Date.parse(date) / 1000);\n      }\n    };\n\n    return Time;\n\n  })();\n\n  window.Time = new Time;\n\n}).call(this);\n\n/* ---- lib/ZeroFrame.coffee ---- */\n\n\n(function() {\n  var ZeroFrame,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  ZeroFrame = (function(superClass) {\n    extend(ZeroFrame, superClass);\n\n    function ZeroFrame(url) {\n      this.onCloseWebsocket = bind(this.onCloseWebsocket, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      this.onRequest = bind(this.onRequest, this);\n      this.onMessage = bind(this.onMessage, this);\n      this.url = url;\n      this.waiting_cb = {};\n      this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\");\n      this.connect();\n      this.next_message_id = 1;\n      this.history_state = {};\n      this.init();\n    }\n\n    ZeroFrame.prototype.init = function() {\n      return this;\n    };\n\n    ZeroFrame.prototype.connect = function() {\n      this.target = window.parent;\n      window.addEventListener(\"message\", this.onMessage, false);\n      this.cmd(\"innerReady\");\n      window.addEventListener(\"beforeunload\", (function(_this) {\n        return function(e) {\n          _this.log(\"save scrollTop\", window.pageYOffset);\n          _this.history_state[\"scrollTop\"] = window.pageYOffset;\n          return _this.cmd(\"wrapperReplaceState\", [_this.history_state, null]);\n        };\n      })(this));\n      return this.cmd(\"wrapperGetState\", [], (function(_this) {\n        return function(state) {\n          if (state != null) {\n            _this.history_state = state;\n          }\n          _this.log(\"restore scrollTop\", state, window.pageYOffset);\n          if (window.pageYOffset === 0 && state) {\n            return window.scroll(window.pageXOffset, state.scrollTop);\n          }\n        };\n      })(this));\n    };\n\n    ZeroFrame.prototype.onMessage = function(e) {\n      var cmd, message;\n      message = e.data;\n      cmd = message.cmd;\n      if (cmd === \"response\") {\n        if (this.waiting_cb[message.to] != null) {\n          return this.waiting_cb[message.to](message.result);\n        } else {\n          return this.log(\"Websocket callback not found:\", message);\n        }\n      } else if (cmd === \"wrapperReady\") {\n        return this.cmd(\"innerReady\");\n      } else if (cmd === \"ping\") {\n        return this.response(message.id, \"pong\");\n      } else if (cmd === \"wrapperOpenedWebsocket\") {\n        return this.onOpenWebsocket();\n      } else if (cmd === \"wrapperClosedWebsocket\") {\n        return this.onCloseWebsocket();\n      } else {\n        return this.onRequest(cmd, message.params);\n      }\n    };\n\n    ZeroFrame.prototype.onRequest = function(cmd, message) {\n      return this.log(\"Unknown request\", message);\n    };\n\n    ZeroFrame.prototype.response = function(to, result) {\n      return this.send({\n        \"cmd\": \"response\",\n        \"to\": to,\n        \"result\": result\n      });\n    };\n\n    ZeroFrame.prototype.cmd = function(cmd, params, cb) {\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      return this.send({\n        \"cmd\": cmd,\n        \"params\": params\n      }, cb);\n    };\n\n    ZeroFrame.prototype.send = function(message, cb) {\n      if (cb == null) {\n        cb = null;\n      }\n      message.wrapper_nonce = this.wrapper_nonce;\n      message.id = this.next_message_id;\n      this.next_message_id += 1;\n      this.target.postMessage(message, \"*\");\n      if (cb) {\n        return this.waiting_cb[message.id] = cb;\n      }\n    };\n\n    ZeroFrame.prototype.onOpenWebsocket = function() {\n      return this.log(\"Websocket open\");\n    };\n\n    ZeroFrame.prototype.onCloseWebsocket = function() {\n      return this.log(\"Websocket close\");\n    };\n\n    return ZeroFrame;\n\n  })(Class);\n\n  window.ZeroFrame = ZeroFrame;\n\n}).call(this);\n\n/* ---- lib/maquette.js ---- */\n\n\n(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['exports'], factory);\n    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {\n        // CommonJS\n        factory(exports);\n    } else {\n        // Browser globals\n        factory(root.maquette = {});\n    }\n}(this, function (exports) {\n    'use strict';\n    ;\n    ;\n    ;\n    ;\n    var NAMESPACE_W3 = 'http://www.w3.org/';\n    var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';\n    var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';\n    // Utilities\n    var emptyArray = [];\n    var extend = function (base, overrides) {\n        var result = {};\n        Object.keys(base).forEach(function (key) {\n            result[key] = base[key];\n        });\n        if (overrides) {\n            Object.keys(overrides).forEach(function (key) {\n                result[key] = overrides[key];\n            });\n        }\n        return result;\n    };\n    // Hyperscript helper functions\n    var same = function (vnode1, vnode2) {\n        if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {\n            return false;\n        }\n        if (vnode1.properties && vnode2.properties) {\n            if (vnode1.properties.key !== vnode2.properties.key) {\n                return false;\n            }\n            return vnode1.properties.bind === vnode2.properties.bind;\n        }\n        return !vnode1.properties && !vnode2.properties;\n    };\n    var toTextVNode = function (data) {\n        return {\n            vnodeSelector: '',\n            properties: undefined,\n            children: undefined,\n            text: data.toString(),\n            domNode: null\n        };\n    };\n    var appendChildren = function (parentSelector, insertions, main) {\n        for (var i = 0; i < insertions.length; i++) {\n            var item = insertions[i];\n            if (Array.isArray(item)) {\n                appendChildren(parentSelector, item, main);\n            } else {\n                if (item !== null && item !== undefined) {\n                    if (!item.hasOwnProperty('vnodeSelector')) {\n                        item = toTextVNode(item);\n                    }\n                    main.push(item);\n                }\n            }\n        }\n    };\n    // Render helper functions\n    var missingTransition = function () {\n        throw new Error('Provide a transitions object to the projectionOptions to do animations');\n    };\n    var DEFAULT_PROJECTION_OPTIONS = {\n        namespace: undefined,\n        eventHandlerInterceptor: undefined,\n        styleApplyer: function (domNode, styleName, value) {\n            // Provides a hook to add vendor prefixes for browsers that still need it.\n            domNode.style[styleName] = value;\n        },\n        transitions: {\n            enter: missingTransition,\n            exit: missingTransition\n        }\n    };\n    var applyDefaultProjectionOptions = function (projectorOptions) {\n        return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);\n    };\n    var checkStyleValue = function (styleValue) {\n        if (typeof styleValue !== 'string') {\n            throw new Error('Style values must be strings');\n        }\n    };\n    var setProperties = function (domNode, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            /* tslint:disable:no-var-keyword: edge case */\n            var propValue = properties[propName];\n            /* tslint:enable:no-var-keyword */\n            if (propName === 'className') {\n                throw new Error('Property \"className\" is not supported, use \"class\".');\n            } else if (propName === 'class') {\n                if (domNode.className) {\n                    // May happen if classes is specified before class\n                    domNode.className += ' ' + propValue;\n                } else {\n                    domNode.className = propValue;\n                }\n            } else if (propName === 'classes') {\n                // object with string keys and boolean values\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    if (propValue[className]) {\n                        domNode.classList.add(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                // object with string keys and string (!) values\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var styleValue = propValue[styleName];\n                    if (styleValue) {\n                        checkStyleValue(styleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, styleValue);\n                    }\n                }\n            } else if (propName === 'key') {\n                continue;\n            } else if (propValue === null || propValue === undefined) {\n                continue;\n            } else {\n                var type = typeof propValue;\n                if (type === 'function') {\n                    if (propName.lastIndexOf('on', 0) === 0) {\n                        if (eventHandlerInterceptor) {\n                            propValue = eventHandlerInterceptor(propName, propValue, domNode, properties);    // intercept eventhandlers\n                        }\n                        if (propName === 'oninput') {\n                            (function () {\n                                // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput\n                                var oldPropValue = propValue;\n                                propValue = function (evt) {\n                                    evt.target['oninput-value'] = evt.target.value;\n                                    // may be HTMLTextAreaElement as well\n                                    oldPropValue.apply(this, [evt]);\n                                };\n                            }());\n                        }\n                        domNode[propName] = propValue;\n                    }\n                } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {\n                    if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                        domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                    } else {\n                        domNode.setAttribute(propName, propValue);\n                    }\n                } else {\n                    domNode[propName] = propValue;\n                }\n            }\n        }\n    };\n    var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var propertiesUpdated = false;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            // assuming that properties will be nullified instead of missing is by design\n            var propValue = properties[propName];\n            var previousValue = previousProperties[propName];\n            if (propName === 'class') {\n                if (previousValue !== propValue) {\n                    throw new Error('\"class\" property may not be updated. Use the \"classes\" property for conditional css classes.');\n                }\n            } else if (propName === 'classes') {\n                var classList = domNode.classList;\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    var on = !!propValue[className];\n                    var previousOn = !!previousValue[className];\n                    if (on === previousOn) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (on) {\n                        classList.add(className);\n                    } else {\n                        classList.remove(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var newStyleValue = propValue[styleName];\n                    var oldStyleValue = previousValue[styleName];\n                    if (newStyleValue === oldStyleValue) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (newStyleValue) {\n                        checkStyleValue(newStyleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, newStyleValue);\n                    } else {\n                        projectionOptions.styleApplyer(domNode, styleName, '');\n                    }\n                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };\n    var findIndexOfChild = function (children, sameAs, start) {\n        if (sameAs.vnodeSelector !== '') {\n            // Never scan for text-nodes\n            for (var i = start; i < children.length; i++) {\n                if (same(children[i], sameAs)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    var nodeAdded = function (vNode, transitions) {\n        if (vNode.properties) {\n            var enterAnimation = vNode.properties.enterAnimation;\n            if (enterAnimation) {\n                if (typeof enterAnimation === 'function') {\n                    enterAnimation(vNode.domNode, vNode.properties);\n                } else {\n                    transitions.enter(vNode.domNode, vNode.properties, enterAnimation);\n                }\n            }\n        }\n    };\n    var nodeToRemove = function (vNode, transitions) {\n        var domNode = vNode.domNode;\n        if (vNode.properties) {\n            var exitAnimation = vNode.properties.exitAnimation;\n            if (exitAnimation) {\n                domNode.style.pointerEvents = 'none';\n                var removeDomNode = function () {\n                    if (domNode.parentNode) {\n                        domNode.parentNode.removeChild(domNode);\n                    }\n                };\n                if (typeof exitAnimation === 'function') {\n                    exitAnimation(domNode, removeDomNode, vNode.properties);\n                    return;\n                } else {\n                    transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);\n                    return;\n                }\n            }\n        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;\n        var transitions = projectionOptions.transitions;\n        var oldIndex = 0;\n        var newIndex = 0;\n        var i;\n        var textUpdated = false;\n        while (newIndex < newChildrenLength) {\n            var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;\n            var newChild = newChildren[newIndex];\n            if (oldChild !== undefined && same(oldChild, newChild)) {\n                textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;\n                oldIndex++;\n            } else {\n                var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);\n                if (findOldIndex >= 0) {\n                    // Remove preceding missing children\n                    for (i = oldIndex; i < findOldIndex; i++) {\n                        nodeToRemove(oldChildren[i], transitions);\n                        checkDistinguishable(oldChildren, i, vnode, 'removed');\n                    }\n                    textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;\n                    oldIndex = findOldIndex + 1;\n                } else {\n                    // New child\n                    createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);\n                    nodeAdded(newChild, transitions);\n                    checkDistinguishable(newChildren, newIndex, vnode, 'added');\n                }\n            }\n            newIndex++;\n        }\n        if (oldChildrenLength > oldIndex) {\n            // Remove child fragments\n            for (i = oldIndex; i < oldChildrenLength; i++) {\n                nodeToRemove(oldChildren[i], transitions);\n                checkDistinguishable(oldChildren, i, vnode, 'removed');\n            }\n        }\n        return textUpdated;\n    };\n    var addChildren = function (domNode, children, projectionOptions) {\n        if (!children) {\n            return;\n        }\n        for (var i = 0; i < children.length; i++) {\n            createDom(children[i], domNode, undefined, projectionOptions);\n        }\n    };\n    var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {\n        addChildren(domNode, vnode.children, projectionOptions);\n        // children before properties, needed for value property of <select>.\n        if (vnode.text) {\n            domNode.textContent = vnode.text;\n        }\n        setProperties(domNode, vnode.properties, projectionOptions);\n        if (vnode.properties && vnode.properties.afterCreate) {\n            vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n        }\n    };\n    createDom = function (vnode, parentNode, insertBefore, projectionOptions) {\n        var domNode, i, c, start = 0, type, found;\n        var vnodeSelector = vnode.vnodeSelector;\n        if (vnodeSelector === '') {\n            domNode = vnode.domNode = document.createTextNode(vnode.text);\n            if (insertBefore !== undefined) {\n                parentNode.insertBefore(domNode, insertBefore);\n            } else {\n                parentNode.appendChild(domNode);\n            }\n        } else {\n            for (i = 0; i <= vnodeSelector.length; ++i) {\n                c = vnodeSelector.charAt(i);\n                if (i === vnodeSelector.length || c === '.' || c === '#') {\n                    type = vnodeSelector.charAt(start - 1);\n                    found = vnodeSelector.slice(start, i);\n                    if (type === '.') {\n                        domNode.classList.add(found);\n                    } else if (type === '#') {\n                        domNode.id = found;\n                    } else {\n                        if (found === 'svg') {\n                            projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n                        }\n                        if (projectionOptions.namespace !== undefined) {\n                            domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);\n                        } else {\n                            domNode = vnode.domNode = document.createElement(found);\n                        }\n                        if (insertBefore !== undefined) {\n                            parentNode.insertBefore(domNode, insertBefore);\n                        } else {\n                            parentNode.appendChild(domNode);\n                        }\n                    }\n                    start = i + 1;\n                }\n            }\n            initPropertiesAndChildren(domNode, vnode, projectionOptions);\n        }\n    };\n    updateDom = function (previous, vnode, projectionOptions) {\n        var domNode = previous.domNode;\n        var textUpdated = false;\n        if (previous === vnode) {\n            return false;    // By contract, VNode objects may not be modified anymore after passing them to maquette\n        }\n        var updated = false;\n        if (vnode.vnodeSelector === '') {\n            if (vnode.text !== previous.text) {\n                var newVNode = document.createTextNode(vnode.text);\n                domNode.parentNode.replaceChild(newVNode, domNode);\n                vnode.domNode = newVNode;\n                textUpdated = true;\n                return textUpdated;\n            }\n        } else {\n            if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {\n                projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n            }\n            if (previous.text !== vnode.text) {\n                updated = true;\n                if (vnode.text === undefined) {\n                    domNode.removeChild(domNode.firstChild);    // the only textnode presumably\n                } else {\n                    domNode.textContent = vnode.text;\n                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {\n            // Optional properties argument was omitted\n            properties = undefined;\n        }\n        var text = undefined;\n        var children = undefined;\n        var argsLength = arguments.length;\n        // Recognize a common special case where there is only a single text node\n        if (argsLength === childIndex + 1) {\n            var onlyChild = arguments[childIndex];\n            if (typeof onlyChild === 'string') {\n                text = onlyChild;\n            } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {\n                text = onlyChild[0];\n            }\n        }\n        if (text === undefined) {\n            children = [];\n            for (; childIndex < arguments.length; childIndex++) {\n                var child = arguments[childIndex];\n                if (child === null || child === undefined) {\n                    continue;\n                } else if (Array.isArray(child)) {\n                    appendChildren(selector, child, children);\n                } else if (child.hasOwnProperty('vnodeSelector')) {\n                    children.push(child);\n                } else {\n                    children.push(toTextVNode(child));\n                }\n            }\n        }\n        return {\n            vnodeSelector: selector,\n            properties: properties,\n            children: children,\n            text: text === '' ? undefined : text,\n            domNode: null\n        };\n    };\n    /**\n * Contains simple low-level utility functions to manipulate the real DOM.\n */\n    exports.dom = {\n        /**\n     * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in\n     * its [[Projection.domNode|domNode]] property.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection.\n     * @returns The [[Projection]] which also contains the DOM Node that was created.\n     */\n        create: function (vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, document.createElement('div'), undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Appends a new childnode to the DOM which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param parentNode - The parent node for the new childNode.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the [[Projection]].\n     * @returns The [[Projection]] that was created.\n     */\n        append: function (parentNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, parentNode, undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Inserts a new DOM node which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param beforeNode - The node that the DOM Node is inserted before.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.\n     * NOTE: [[VNode]] objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        insertBefore: function (beforeNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.\n     * This means that the virtual DOM and the real DOM will have one overlapping element.\n     * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects\n     * may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        merge: function (element, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            vnode.domNode = element;\n            initPropertiesAndChildren(element, vnode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        }\n    };\n    /**\n * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.\n * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.\n * For more information, see [[CalculationCache]].\n *\n * @param <Result> The type of the value that is cached.\n */\n    exports.createCache = function () {\n        var cachedInputs = undefined;\n        var cachedOutcome = undefined;\n        var result = {\n            invalidate: function () {\n                cachedOutcome = undefined;\n                cachedInputs = undefined;\n            },\n            result: function (inputs, calculation) {\n                if (cachedInputs) {\n                    for (var i = 0; i < inputs.length; i++) {\n                        if (cachedInputs[i] !== inputs[i]) {\n                            cachedOutcome = undefined;\n                        }\n                    }\n                }\n                if (!cachedOutcome) {\n                    cachedOutcome = calculation();\n                    cachedInputs = inputs;\n                }\n                return cachedOutcome;\n            }\n        };\n        return result;\n    };\n    /**\n * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.\n * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.\n *\n * @param <Source>       The type of source items. A database-record for instance.\n * @param <Target>       The type of target items. A [[Component]] for instance.\n * @param getSourceKey   `function(source)` that must return a key to identify each source object. The result must either be a string or a number.\n * @param createResult   `function(source, index)` that must create a new result object from a given source. This function is identical\n *                       to the `callback` argument in `Array.map(callback)`.\n * @param updateResult   `function(source, target, index)` that updates a result to an updated source.\n */\n    exports.createMapping = function (getSourceKey, createResult, updateResult) {\n        var keys = [];\n        var results = [];\n        return {\n            results: results,\n            map: function (newSources) {\n                var newKeys = newSources.map(getSourceKey);\n                var oldTargets = results.slice();\n                var oldIndex = 0;\n                for (var i = 0; i < newSources.length; i++) {\n                    var source = newSources[i];\n                    var sourceKey = newKeys[i];\n                    if (sourceKey === keys[oldIndex]) {\n                        results[i] = oldTargets[oldIndex];\n                        updateResult(source, oldTargets[oldIndex], i);\n                        oldIndex++;\n                    } else {\n                        var found = false;\n                        for (var j = 1; j < keys.length; j++) {\n                            var searchIndex = (oldIndex + j) % keys.length;\n                            if (keys[searchIndex] === sourceKey) {\n                                results[i] = oldTargets[searchIndex];\n                                updateResult(newSources[i], oldTargets[searchIndex], i);\n                                oldIndex = searchIndex + 1;\n                                found = true;\n                                break;\n                            }\n                        }\n                        if (!found) {\n                            results[i] = createResult(source, i);\n                        }\n                    }\n                }\n                results.length = newSources.length;\n                keys = newKeys;\n            }\n        };\n    };\n    /**\n * Creates a [[Projector]] instance using the provided projectionOptions.\n *\n * For more information, see [[Projector]].\n *\n * @param projectionOptions   Options that influence how the DOM is rendered and updated.\n */\n    exports.createProjector = function (projectorOptions) {\n        var projector;\n        var projectionOptions = applyDefaultProjectionOptions(projectorOptions);\n        projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {\n            return function () {\n                // intercept function calls (event handlers) to do a render afterwards.\n                projector.scheduleRender();\n                return eventHandler.apply(properties.bind || this, arguments);\n            };\n        };\n        var renderCompleted = true;\n        var scheduled;\n        var stopped = false;\n        var projections = [];\n        var renderFunctions = [];\n        // matches the projections array\n        var doRender = function () {\n            scheduled = undefined;\n            if (!renderCompleted) {\n                return;    // The last render threw an error, it should be logged in the browser console.\n            }\n            renderCompleted = false;\n            for (var i = 0; i < projections.length; i++) {\n                var updatedVnode = renderFunctions[i]();\n                projections[i].update(updatedVnode);\n            }\n            renderCompleted = true;\n        };\n        projector = {\n            scheduleRender: function () {\n                if (!scheduled && !stopped) {\n                    scheduled = requestAnimationFrame(doRender);\n                }\n            },\n            stop: function () {\n                if (scheduled) {\n                    cancelAnimationFrame(scheduled);\n                    scheduled = undefined;\n                }\n                stopped = true;\n            },\n            resume: function () {\n                stopped = false;\n                renderCompleted = true;\n                projector.scheduleRender();\n            },\n            append: function (parentNode, renderMaquetteFunction) {\n                projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            insertBefore: function (beforeNode, renderMaquetteFunction) {\n                projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            merge: function (domNode, renderMaquetteFunction) {\n                projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            replace: function (domNode, renderMaquetteFunction) {\n                var vnode = renderMaquetteFunction();\n                createDom(vnode, domNode.parentNode, domNode, projectionOptions);\n                domNode.parentNode.removeChild(domNode);\n                projections.push(createProjection(vnode, projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            detach: function (renderMaquetteFunction) {\n                for (var i = 0; i < renderFunctions.length; i++) {\n                    if (renderFunctions[i] === renderMaquetteFunction) {\n                        renderFunctions.splice(i, 1);\n                        return projections.splice(i, 1)[0];\n                    }\n                }\n                throw new Error('renderMaquetteFunction was not found');\n            }\n        };\n        return projector;\n    };\n}));\n\n\n/* ---- Config.coffee ---- */\n\n\n(function() {\n  window.BINARY_EXTENSIONS = [\"3dm\", \"3ds\", \"3g2\", \"3gp\", \"7z\", \"a\", \"aac\", \"adp\", \"ai\", \"aif\", \"aiff\", \"alz\", \"ape\", \"apk\", \"appimage\", \"ar\", \"arj\", \"asc\", \"asf\", \"au\", \"avi\", \"bak\", \"baml\", \"bh\", \"bin\", \"bk\", \"bmp\", \"btif\", \"bz2\", \"bzip2\", \"cab\", \"caf\", \"cgm\", \"class\", \"cmx\", \"cpio\", \"cr2\", \"cur\", \"dat\", \"dcm\", \"deb\", \"dex\", \"djvu\", \"dll\", \"dmg\", \"dng\", \"doc\", \"docm\", \"docx\", \"dot\", \"dotm\", \"dra\", \"DS_Store\", \"dsk\", \"dts\", \"dtshd\", \"dvb\", \"dwg\", \"dxf\", \"ecelp4800\", \"ecelp7470\", \"ecelp9600\", \"egg\", \"eol\", \"eot\", \"epub\", \"exe\", \"f4v\", \"fbs\", \"fh\", \"fla\", \"flac\", \"flatpak\", \"fli\", \"flv\", \"fpx\", \"fst\", \"fvt\", \"g3\", \"gh\", \"gif\", \"gpg\", \"graffle\", \"gz\", \"gzip\", \"h261\", \"h263\", \"h264\", \"icns\", \"ico\", \"ief\", \"img\", \"ipa\", \"iso\", \"jar\", \"jpeg\", \"jpg\", \"jpgv\", \"jpm\", \"jxr\", \"key\", \"ktx\", \"lha\", \"lib\", \"lvp\", \"lz\", \"lzh\", \"lzma\", \"lzo\", \"m3u\", \"m4a\", \"m4v\", \"mar\", \"mdi\", \"mht\", \"mid\", \"midi\", \"mj2\", \"mka\", \"mkv\", \"mmr\", \"mng\", \"mobi\", \"mov\", \"movie\", \"mp3\", \"mp4\", \"mp4a\", \"mpeg\", \"mpg\", \"mpga\", \"msgpack\", \"mxu\", \"nef\", \"npx\", \"numbers\", \"nupkg\", \"o\", \"oga\", \"ogg\", \"ogv\", \"otf\", \"pages\", \"pbm\", \"pcx\", \"pdb\", \"pdf\", \"pea\", \"pgm\", \"pic\", \"png\", \"pnm\", \"pot\", \"potm\", \"potx\", \"ppa\", \"ppam\", \"ppm\", \"pps\", \"ppsm\", \"ppsx\", \"ppt\", \"pptm\", \"pptx\", \"psd\", \"pya\", \"pyc\", \"pyo\", \"pyv\", \"qt\", \"rar\", \"ras\", \"raw\", \"resources\", \"rgb\", \"rip\", \"rlc\", \"rmf\", \"rmvb\", \"rpm\", \"rtf\", \"rz\", \"s3m\", \"s7z\", \"scpt\", \"sgi\", \"shar\", \"sig\", \"sil\", \"sketch\", \"slk\", \"smv\", \"snap\", \"snk\", \"so\", \"stl\", \"sub\", \"suo\", \"swf\", \"tar\", \"tbz2\", \"tbz\", \"tga\", \"tgz\", \"thmx\", \"tif\", \"tiff\", \"tlz\", \"ttc\", \"ttf\", \"txz\", \"udf\", \"uvh\", \"uvi\", \"uvm\", \"uvp\", \"uvs\", \"uvu\", \"viv\", \"vob\", \"war\", \"wav\", \"wax\", \"wbmp\", \"wdp\", \"weba\", \"webm\", \"webp\", \"whl\", \"wim\", \"wm\", \"wma\", \"wmv\", \"wmx\", \"woff2\", \"woff\", \"wrm\", \"wvx\", \"xbm\", \"xif\", \"xla\", \"xlam\", \"xls\", \"xlsb\", \"xlsm\", \"xlsx\", \"xlt\", \"xltm\", \"xltx\", \"xm\", \"xmind\", \"xpi\", \"xpm\", \"xwd\", \"xz\", \"z\", \"zip\", \"zipx\"];\n\n}).call(this);\n\n\n/* ---- FileEditor.coffee ---- */\n\n\n(function() {\n  var FileEditor,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  FileEditor = (function(superClass) {\n    extend(FileEditor, superClass);\n\n    function FileEditor(inner_path1) {\n      this.inner_path = inner_path1;\n      this.save = bind(this.save, this);\n      this.handleSaveClick = bind(this.handleSaveClick, this);\n      this.handleSidebarButtonClick = bind(this.handleSidebarButtonClick, this);\n      this.foldJson = bind(this.foldJson, this);\n      this.storeCmNode = bind(this.storeCmNode, this);\n      this.isModified = bind(this.isModified, this);\n      this.need_update = true;\n      this.on_loaded = new Promise();\n      this.is_loading = false;\n      this.content = \"\";\n      this.node_cm = null;\n      this.cm = null;\n      this.error = null;\n      this.is_loaded = false;\n      this.is_modified = false;\n      this.is_saving = false;\n      this.mode = \"Loading\";\n    }\n\n    FileEditor.prototype.update = function() {\n      var is_required;\n      is_required = Page.url_params.get(\"edit_mode\") !== \"new\";\n      return Page.cmd(\"fileGet\", {\n        inner_path: this.inner_path,\n        required: is_required\n      }, (function(_this) {\n        return function(res) {\n          if (res != null ? res.error : void 0) {\n            _this.error = res.error;\n            _this.content = res.error;\n            _this.log(\"Error loading: \" + _this.error);\n          } else {\n            if (res) {\n              _this.content = res;\n            } else {\n              _this.content = \"\";\n              _this.mode = \"Create\";\n            }\n          }\n          if (!_this.content) {\n            _this.cm.getDoc().clearHistory();\n          }\n          _this.cm.setValue(_this.content);\n          if (!_this.error) {\n            _this.is_loaded = true;\n          }\n          return Page.projector.scheduleRender();\n        };\n      })(this));\n    };\n\n    FileEditor.prototype.isModified = function() {\n      return this.content !== this.cm.getValue();\n    };\n\n    FileEditor.prototype.storeCmNode = function(node) {\n      return this.node_cm = node;\n    };\n\n    FileEditor.prototype.getMode = function(inner_path) {\n      var ext, types;\n      ext = inner_path.split(\".\").pop();\n      types = {\n        \"py\": \"python\",\n        \"json\": \"application/json\",\n        \"js\": \"javascript\",\n        \"coffee\": \"coffeescript\",\n        \"html\": \"htmlmixed\",\n        \"htm\": \"htmlmixed\",\n        \"php\": \"htmlmixed\",\n        \"rs\": \"rust\",\n        \"css\": \"css\",\n        \"md\": \"markdown\",\n        \"xml\": \"xml\",\n        \"svg\": \"xml\"\n      };\n      return types[ext];\n    };\n\n    FileEditor.prototype.foldJson = function(from, to) {\n      var count, e, endToken, internal, parsed, prevLine, startToken, toParse;\n      this.log(\"foldJson\", from, to);\n      startToken = '{';\n      endToken = '}';\n      prevLine = this.cm.getLine(from.line);\n      if (prevLine.lastIndexOf('[') > prevLine.lastIndexOf('{')) {\n        startToken = '[';\n        endToken = ']';\n      }\n      internal = this.cm.getRange(from, to);\n      toParse = startToken + internal + endToken;\n      try {\n        parsed = JSON.parse(toParse);\n        count = Object.keys(parsed).length;\n      } catch (error) {\n        e = error;\n        null;\n      }\n      if (count) {\n        return \"\\u21A4\" + count + \"\\u21A6\";\n      } else {\n        return \"\\u2194\";\n      }\n    };\n\n    FileEditor.prototype.createCodeMirror = function() {\n      var mode, options;\n      mode = this.getMode(this.inner_path);\n      this.log(\"Creating CodeMirror\", this.inner_path, mode);\n      options = {\n        value: \"Loading...\",\n        mode: mode,\n        lineNumbers: true,\n        styleActiveLine: true,\n        matchBrackets: true,\n        keyMap: \"sublime\",\n        theme: \"mdn-like\",\n        extraKeys: {\n          \"Ctrl-Space\": \"autocomplete\"\n        },\n        foldGutter: true,\n        gutters: [\"CodeMirror-linenumbers\", \"CodeMirror-foldgutter\"]\n      };\n      if (mode === \"application/json\") {\n        options.gutters.unshift(\"CodeMirror-lint-markers\");\n        options.lint = true;\n        options.foldOptions = {\n          widget: this.foldJson\n        };\n      }\n      this.cm = CodeMirror(this.node_cm, options);\n      return this.cm.on(\"changes\", (function(_this) {\n        return function(changes) {\n          if (_this.is_loaded && !_this.is_modified) {\n            _this.is_modified = true;\n            return Page.projector.scheduleRender();\n          }\n        };\n      })(this));\n    };\n\n    FileEditor.prototype.loadEditor = function() {\n      var script;\n      if (!this.is_loading) {\n        document.getElementsByTagName(\"head\")[0].insertAdjacentHTML(\"beforeend\", \"<link rel=\\\"stylesheet\\\" href=\\\"codemirror/all.css\\\" />\");\n        script = document.createElement('script');\n        script.src = \"codemirror/all.js\";\n        script.onload = (function(_this) {\n          return function() {\n            _this.createCodeMirror();\n            return _this.on_loaded.resolve();\n          };\n        })(this);\n        document.head.appendChild(script);\n      }\n      return this.on_loaded;\n    };\n\n    FileEditor.prototype.handleSidebarButtonClick = function() {\n      Page.is_sidebar_closed = !Page.is_sidebar_closed;\n      return false;\n    };\n\n    FileEditor.prototype.handleSaveClick = function() {\n      var mark, num_errors;\n      num_errors = ((function() {\n        var i, len, ref, results;\n        ref = Page.file_editor.cm.getAllMarks();\n        results = [];\n        for (i = 0, len = ref.length; i < len; i++) {\n          mark = ref[i];\n          if (mark.className === \"CodeMirror-lint-mark-error\") {\n            results.push(mark);\n          }\n        }\n        return results;\n      })()).length;\n      if (num_errors > 0) {\n        Page.cmd(\"wrapperConfirm\", [\"<b>Warning:</b> The file looks invalid.\", \"Save anyway\"], this.save);\n      } else {\n        this.save();\n      }\n      return false;\n    };\n\n    FileEditor.prototype.save = function() {\n      Page.projector.scheduleRender();\n      this.is_saving = true;\n      return Page.cmd(\"fileWrite\", [this.inner_path, Text.fileEncode(this.cm.getValue())], (function(_this) {\n        return function(res) {\n          _this.is_saving = false;\n          if (res.error) {\n            Page.cmd(\"wrapperNotification\", [\"error\", \"Error saving \" + res.error]);\n          } else {\n            _this.is_save_done = true;\n            setTimeout((function() {\n              _this.is_save_done = false;\n              return Page.projector.scheduleRender();\n            }), 2000);\n            _this.content = _this.cm.getValue();\n            _this.is_modified = false;\n            if (_this.mode === \"Create\") {\n              _this.mode = \"Edit\";\n            }\n            Page.file_list.need_update = true;\n          }\n          return Page.projector.scheduleRender();\n        };\n      })(this));\n    };\n\n    FileEditor.prototype.render = function() {\n      var ref;\n      if (this.need_update) {\n        this.loadEditor().then((function(_this) {\n          return function() {\n            return _this.update();\n          };\n        })(this));\n        this.need_update = false;\n      }\n      return h(\"div.editor\", {\n        afterCreate: this.storeCmNode,\n        classes: {\n          error: this.error,\n          loaded: this.is_loaded\n        }\n      }, [\n        h(\"a.sidebar-button\", {\n          href: \"#Sidebar\",\n          onclick: this.handleSidebarButtonClick\n        }, h(\"span\", \"\\u2039\")), h(\"div.editor-head\", [\n          (ref = this.mode) === \"Edit\" || ref === \"Create\" ? h(\"a.save.button\", {\n            href: \"#Save\",\n            classes: {\n              loading: this.is_saving,\n              done: this.is_save_done,\n              disabled: !this.is_modified\n            },\n            onclick: this.handleSaveClick\n          }, this.is_save_done ? \"Save: done!\" : \"Save\") : void 0, h(\"span.title\", this.mode, \": \", this.inner_path)\n        ]), this.error ? h(\"div.error-message\", h(\"h2\", \"Unable to load the file: \" + this.error), h(\"a\", {\n          href: Page.file_list.getHref(this.inner_path)\n        }, \"View in browser\")) : void 0\n      ]);\n    };\n\n    return FileEditor;\n\n  })(Class);\n\n  window.FileEditor = FileEditor;\n\n}).call(this);\n\n/* ---- FileItemList.coffee ---- */\n\n\n(function() {\n  var FileItemList,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  FileItemList = (function(superClass) {\n    extend(FileItemList, superClass);\n\n    function FileItemList(inner_path1) {\n      this.inner_path = inner_path1;\n      this.sort = bind(this.sort, this);\n      this.getOptionalInfo = bind(this.getOptionalInfo, this);\n      this.hasPermissionDelete = bind(this.hasPermissionDelete, this);\n      this.isAdded = bind(this.isAdded, this);\n      this.isModified = bind(this.isModified, this);\n      this.getFileType = bind(this.getFileType, this);\n      this.addOptionalFilesToItems = bind(this.addOptionalFilesToItems, this);\n      this.updateOptionalFiles = bind(this.updateOptionalFiles, this);\n      this.updateAddedFiles = bind(this.updateAddedFiles, this);\n      this.updateModifiedFiles = bind(this.updateModifiedFiles, this);\n      this.items = [];\n      this.updating = false;\n      this.files_modified = {};\n      this.dirs_modified = {};\n      this.files_added = {};\n      this.dirs_added = {};\n      this.files_optional = {};\n      this.items_by_name = {};\n    }\n\n    FileItemList.prototype.update = function(cb) {\n      this.updating = true;\n      this.logStart(\"Updating dirlist\");\n      return Page.cmd(\"dirList\", {\n        inner_path: this.inner_path,\n        stats: true\n      }, (function(_this) {\n        return function(res) {\n          var i, len, pattern_ignore, ref, ref1, ref2, ref3, row;\n          if (res.error) {\n            _this.error = res.error;\n          } else {\n            _this.error = null;\n            pattern_ignore = RegExp(\"^\" + ((ref = Page.site_info.content) != null ? ref.ignore : void 0));\n            _this.items.splice(0, _this.items.length);\n            _this.items_by_name = {};\n            for (i = 0, len = res.length; i < len; i++) {\n              row = res[i];\n              row.type = _this.getFileType(row);\n              row.inner_path = _this.inner_path + row.name;\n              if (((ref1 = Page.site_info.content) != null ? ref1.ignore : void 0) && row.inner_path.match(pattern_ignore)) {\n                row.ignored = true;\n              }\n              _this.items.push(row);\n              _this.items_by_name[row.name] = row;\n            }\n            _this.sort();\n          }\n          if ((ref2 = Page.site_info) != null ? (ref3 = ref2.settings) != null ? ref3.own : void 0 : void 0) {\n            _this.updateAddedFiles();\n          }\n          return _this.updateOptionalFiles(function() {\n            _this.updating = false;\n            if (typeof cb === \"function\") {\n              cb();\n            }\n            _this.logEnd(\"Updating dirlist\", _this.inner_path);\n            Page.projector.scheduleRender();\n            return _this.updateModifiedFiles(function() {\n              return Page.projector.scheduleRender();\n            });\n          });\n        };\n      })(this));\n    };\n\n    FileItemList.prototype.updateModifiedFiles = function(cb) {\n      return Page.cmd(\"siteListModifiedFiles\", [], (function(_this) {\n        return function(res) {\n          var dir_inner_path, dir_part, dir_parts, i, inner_path, j, len, len1, ref, ref1;\n          _this.files_modified = {};\n          _this.dirs_modified = {};\n          ref = res.modified_files;\n          for (i = 0, len = ref.length; i < len; i++) {\n            inner_path = ref[i];\n            _this.files_modified[inner_path] = true;\n            dir_inner_path = \"\";\n            dir_parts = inner_path.split(\"/\");\n            ref1 = dir_parts.slice(0, -1);\n            for (j = 0, len1 = ref1.length; j < len1; j++) {\n              dir_part = ref1[j];\n              if (dir_inner_path) {\n                dir_inner_path += \"/\" + dir_part;\n              } else {\n                dir_inner_path = dir_part;\n              }\n              _this.dirs_modified[dir_inner_path] = true;\n            }\n          }\n          return typeof cb === \"function\" ? cb() : void 0;\n        };\n      })(this));\n    };\n\n    FileItemList.prototype.updateAddedFiles = function() {\n      return Page.cmd(\"fileGet\", \"content.json\", (function(_this) {\n        return function(res) {\n          var content, dirs_content, file, file_name, i, j, len, len1, match, pattern, ref, ref1, results;\n          if (!res) {\n            return false;\n          }\n          content = JSON.parse(res);\n          if (content.files == null) {\n            return false;\n          }\n          _this.files_added = {};\n          ref = _this.items;\n          for (i = 0, len = ref.length; i < len; i++) {\n            file = ref[i];\n            if (file.name === \"content.json\" || file.is_dir) {\n              continue;\n            }\n            if (!content.files[_this.inner_path + file.name]) {\n              _this.files_added[_this.inner_path + file.name] = true;\n            }\n          }\n          _this.dirs_added = {};\n          dirs_content = {};\n          for (file_name in Object.assign({}, content.files, content.files_optional)) {\n            if (!file_name.startsWith(_this.inner_path)) {\n              continue;\n            }\n            pattern = new RegExp(_this.inner_path + \"(.*?)/\");\n            match = file_name.match(pattern);\n            if (!match) {\n              continue;\n            }\n            dirs_content[match[1]] = true;\n          }\n          ref1 = _this.items;\n          results = [];\n          for (j = 0, len1 = ref1.length; j < len1; j++) {\n            file = ref1[j];\n            if (!file.is_dir) {\n              continue;\n            }\n            if (!dirs_content[file.name]) {\n              results.push(_this.dirs_added[_this.inner_path + file.name] = true);\n            } else {\n              results.push(void 0);\n            }\n          }\n          return results;\n        };\n      })(this));\n    };\n\n    FileItemList.prototype.updateOptionalFiles = function(cb) {\n      return Page.cmd(\"optionalFileList\", {\n        filter: \"\"\n      }, (function(_this) {\n        return function(res) {\n          var i, len, optional_file;\n          _this.files_optional = {};\n          for (i = 0, len = res.length; i < len; i++) {\n            optional_file = res[i];\n            _this.files_optional[optional_file.inner_path] = optional_file;\n          }\n          _this.addOptionalFilesToItems();\n          return typeof cb === \"function\" ? cb() : void 0;\n        };\n      })(this));\n    };\n\n    FileItemList.prototype.addOptionalFilesToItems = function() {\n      var dir_name, file_name, inner_path, is_added, optional_file, ref, ref1, row;\n      is_added = false;\n      ref = this.files_optional;\n      for (inner_path in ref) {\n        optional_file = ref[inner_path];\n        if (optional_file.inner_path.startsWith(this.inner_path)) {\n          if (this.getDirectory(optional_file.inner_path) === this.inner_path) {\n            file_name = this.getFileName(optional_file.inner_path);\n            if (!this.items_by_name[file_name]) {\n              row = {\n                \"name\": file_name,\n                \"type\": \"file\",\n                \"optional_empty\": true,\n                \"size\": optional_file.size,\n                \"is_dir\": false,\n                \"inner_path\": optional_file.inner_path\n              };\n              this.items.push(row);\n              this.items_by_name[file_name] = row;\n              is_added = true;\n            }\n          } else {\n            dir_name = (ref1 = optional_file.inner_path.replace(this.inner_path, \"\").match(/(.*?)\\//, \"\")) != null ? ref1[1] : void 0;\n            if (dir_name && !this.items_by_name[dir_name]) {\n              row = {\n                \"name\": dir_name,\n                \"type\": \"dir\",\n                \"optional_empty\": true,\n                \"size\": 0,\n                \"is_dir\": true,\n                \"inner_path\": optional_file.inner_path\n              };\n              this.items.push(row);\n              this.items_by_name[dir_name] = row;\n              is_added = true;\n            }\n          }\n        }\n      }\n      if (is_added) {\n        return this.sort();\n      }\n    };\n\n    FileItemList.prototype.getFileType = function(file) {\n      if (file.is_dir) {\n        return \"dir\";\n      } else {\n        return \"unknown\";\n      }\n    };\n\n    FileItemList.prototype.getDirectory = function(inner_path) {\n      if (inner_path.indexOf(\"/\") !== -1) {\n        return inner_path.replace(/^(.*\\/)(.*?)$/, \"$1\");\n      } else {\n        return \"\";\n      }\n    };\n\n    FileItemList.prototype.getFileName = function(inner_path) {\n      return inner_path.replace(/^(.*\\/)(.*?)$/, \"$2\");\n    };\n\n    FileItemList.prototype.isModified = function(inner_path) {\n      return this.files_modified[inner_path] || this.dirs_modified[inner_path];\n    };\n\n    FileItemList.prototype.isAdded = function(inner_path) {\n      return this.files_added[inner_path] || this.dirs_added[inner_path];\n    };\n\n    FileItemList.prototype.hasPermissionDelete = function(file) {\n      var optional_info, ref, ref1, ref2;\n      if ((ref = file.type) === \"dir\" || ref === \"parent\") {\n        return false;\n      }\n      if (file.inner_path === \"content.json\") {\n        return false;\n      }\n      optional_info = this.getOptionalInfo(file.inner_path);\n      if (optional_info && optional_info.downloaded_percent > 0) {\n        return true;\n      } else {\n        return (ref1 = Page.site_info) != null ? (ref2 = ref1.settings) != null ? ref2.own : void 0 : void 0;\n      }\n    };\n\n    FileItemList.prototype.getOptionalInfo = function(inner_path) {\n      return this.files_optional[inner_path];\n    };\n\n    FileItemList.prototype.sort = function() {\n      return this.items.sort(function(a, b) {\n        return (b.is_dir - a.is_dir) || a.name.localeCompare(b.name);\n      });\n    };\n\n    return FileItemList;\n\n  })(Class);\n\n  window.FileItemList = FileItemList;\n\n}).call(this);\n\n/* ---- FileList.coffee ---- */\n\n\n(function() {\n  var FileList,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty,\n    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n  FileList = (function(superClass) {\n    extend(FileList, superClass);\n\n    function FileList(site, inner_path1, is_owner) {\n      this.site = site;\n      this.inner_path = inner_path1;\n      this.is_owner = is_owner != null ? is_owner : false;\n      this.render = bind(this.render, this);\n      this.renderFoot = bind(this.renderFoot, this);\n      this.renderItems = bind(this.renderItems, this);\n      this.renderItem = bind(this.renderItem, this);\n      this.renderItemCheckbox = bind(this.renderItemCheckbox, this);\n      this.renderHead = bind(this.renderHead, this);\n      this.renderSelectbar = bind(this.renderSelectbar, this);\n      this.handleSelectbarRemoveOptional = bind(this.handleSelectbarRemoveOptional, this);\n      this.handleSelectbarDelete = bind(this.handleSelectbarDelete, this);\n      this.handleSelectbarCancel = bind(this.handleSelectbarCancel, this);\n      this.handleRowMouseenter = bind(this.handleRowMouseenter, this);\n      this.handleSelectMousedown = bind(this.handleSelectMousedown, this);\n      this.handleSelectEnd = bind(this.handleSelectEnd, this);\n      this.handleSelectClick = bind(this.handleSelectClick, this);\n      this.handleNewDirectoryClick = bind(this.handleNewDirectoryClick, this);\n      this.handleNewFileClick = bind(this.handleNewFileClick, this);\n      this.handleMenuCreateClick = bind(this.handleMenuCreateClick, this);\n      this.checkSelectedItems = bind(this.checkSelectedItems, this);\n      this.getEditHref = bind(this.getEditHref, this);\n      this.getListHref = bind(this.getListHref, this);\n      this.getHref = bind(this.getHref, this);\n      this.update = bind(this.update, this);\n      this.need_update = true;\n      this.error = null;\n      this.url_root = \"/list/\" + this.site + \"/\";\n      if (this.inner_path) {\n        this.inner_path += \"/\";\n        this.url_root += this.inner_path;\n      }\n      this.log(\"inited\", this.url_root);\n      this.item_list = new FileItemList(this.inner_path);\n      this.item_list.items = this.item_list.items;\n      this.menu_create = new Menu();\n      this.select_action = null;\n      this.selected = {};\n      this.selected_items_num = 0;\n      this.selected_items_size = 0;\n      this.selected_optional_empty_num = 0;\n    }\n\n    FileList.prototype.isSelectedAll = function() {\n      return false;\n    };\n\n    FileList.prototype.update = function() {\n      return this.item_list.update((function(_this) {\n        return function() {\n          return document.body.classList.add(\"loaded\");\n        };\n      })(this));\n    };\n\n    FileList.prototype.getHref = function(inner_path) {\n      return \"/\" + this.site + \"/\" + inner_path;\n    };\n\n    FileList.prototype.getListHref = function(inner_path) {\n      return \"/list/\" + this.site + \"/\" + inner_path;\n    };\n\n    FileList.prototype.getEditHref = function(inner_path, mode) {\n      var href;\n      if (mode == null) {\n        mode = null;\n      }\n      href = this.url_root + \"?file=\" + inner_path;\n      if (mode) {\n        href += \"&edit_mode=\" + mode;\n      }\n      return href;\n    };\n\n    FileList.prototype.checkSelectedItems = function() {\n      var i, item, len, optional_info, ref, results;\n      this.selected_items_num = 0;\n      this.selected_items_size = 0;\n      this.selected_optional_empty_num = 0;\n      ref = this.item_list.items;\n      results = [];\n      for (i = 0, len = ref.length; i < len; i++) {\n        item = ref[i];\n        if (this.selected[item.inner_path]) {\n          this.selected_items_num += 1;\n          this.selected_items_size += item.size;\n          optional_info = this.item_list.getOptionalInfo(item.inner_path);\n          if (optional_info && !optional_info.downloaded_percent > 0) {\n            results.push(this.selected_optional_empty_num += 1);\n          } else {\n            results.push(void 0);\n          }\n        } else {\n          results.push(void 0);\n        }\n      }\n      return results;\n    };\n\n    FileList.prototype.handleMenuCreateClick = function() {\n      this.menu_create.items = [];\n      this.menu_create.items.push([\"File\", this.handleNewFileClick]);\n      this.menu_create.items.push([\"Directory\", this.handleNewDirectoryClick]);\n      this.menu_create.toggle();\n      return false;\n    };\n\n    FileList.prototype.handleNewFileClick = function() {\n      Page.cmd(\"wrapperPrompt\", \"New file name:\", (function(_this) {\n        return function(file_name) {\n          return window.top.location.href = _this.getEditHref(_this.inner_path + file_name, \"new\");\n        };\n      })(this));\n      return false;\n    };\n\n    FileList.prototype.handleNewDirectoryClick = function() {\n      Page.cmd(\"wrapperPrompt\", \"New directory name:\", (function(_this) {\n        return function(res) {\n          return alert(\"directory name \" + res);\n        };\n      })(this));\n      return false;\n    };\n\n    FileList.prototype.handleSelectClick = function(e) {\n      return false;\n    };\n\n    FileList.prototype.handleSelectEnd = function(e) {\n      document.body.removeEventListener('mouseup', this.handleSelectEnd);\n      return this.select_action = null;\n    };\n\n    FileList.prototype.handleSelectMousedown = function(e) {\n      var inner_path;\n      inner_path = e.currentTarget.attributes.inner_path.value;\n      if (this.selected[inner_path]) {\n        delete this.selected[inner_path];\n        this.select_action = \"deselect\";\n      } else {\n        this.selected[inner_path] = true;\n        this.select_action = \"select\";\n      }\n      this.checkSelectedItems();\n      document.body.addEventListener('mouseup', this.handleSelectEnd);\n      e.stopPropagation();\n      Page.projector.scheduleRender();\n      return false;\n    };\n\n    FileList.prototype.handleRowMouseenter = function(e) {\n      var inner_path;\n      if (e.buttons && this.select_action) {\n        inner_path = e.target.attributes.inner_path.value;\n        if (this.select_action === \"select\") {\n          this.selected[inner_path] = true;\n        } else {\n          delete this.selected[inner_path];\n        }\n        this.checkSelectedItems();\n        Page.projector.scheduleRender();\n      }\n      return false;\n    };\n\n    FileList.prototype.handleSelectbarCancel = function() {\n      this.selected = {};\n      this.checkSelectedItems();\n      Page.projector.scheduleRender();\n      return false;\n    };\n\n    FileList.prototype.handleSelectbarDelete = function(e, remove_optional) {\n      var inner_path, optional_info;\n      if (remove_optional == null) {\n        remove_optional = false;\n      }\n      for (inner_path in this.selected) {\n        optional_info = this.item_list.getOptionalInfo(inner_path);\n        delete this.selected[inner_path];\n        if (optional_info && !remove_optional) {\n          Page.cmd(\"optionalFileDelete\", inner_path);\n        } else {\n          Page.cmd(\"fileDelete\", inner_path);\n        }\n      }\n      this.need_update = true;\n      Page.projector.scheduleRender();\n      this.checkSelectedItems();\n      return false;\n    };\n\n    FileList.prototype.handleSelectbarRemoveOptional = function(e) {\n      return this.handleSelectbarDelete(e, true);\n    };\n\n    FileList.prototype.renderSelectbar = function() {\n      return h(\"div.selectbar\", {\n        classes: {\n          visible: this.selected_items_num > 0\n        }\n      }, [\n        \"Selected:\", h(\"span.info\", [h(\"span.num\", this.selected_items_num + \" files\"), h(\"span.size\", \"(\" + (Text.formatSize(this.selected_items_size)) + \")\")]), h(\"div.actions\", [\n          this.selected_optional_empty_num > 0 ? h(\"a.action.delete.remove_optional\", {\n            href: \"#\",\n            onclick: this.handleSelectbarRemoveOptional\n          }, \"Delete and remove optional\") : h(\"a.action.delete\", {\n            href: \"#\",\n            onclick: this.handleSelectbarDelete\n          }, \"Delete\")\n        ]), h(\"a.cancel.link\", {\n          href: \"#\",\n          onclick: this.handleSelectbarCancel\n        }, \"Cancel\")\n      ]);\n    };\n\n    FileList.prototype.renderHead = function() {\n      var i, inner_path_parent, len, parent_dir, parent_links, ref;\n      parent_links = [];\n      inner_path_parent = \"\";\n      ref = this.inner_path.split(\"/\");\n      for (i = 0, len = ref.length; i < len; i++) {\n        parent_dir = ref[i];\n        if (!parent_dir) {\n          continue;\n        }\n        if (inner_path_parent) {\n          inner_path_parent += \"/\";\n        }\n        inner_path_parent += \"\" + parent_dir;\n        parent_links.push([\n          \" / \", h(\"a\", {\n            href: this.getListHref(inner_path_parent)\n          }, parent_dir)\n        ]);\n      }\n      return h(\"div.tr.thead\", h(\"div.td.full\", h(\"a\", {\n        href: this.getListHref(\"\")\n      }, \"root\"), parent_links));\n    };\n\n    FileList.prototype.renderItemCheckbox = function(item) {\n      if (!this.item_list.hasPermissionDelete(item)) {\n        return [\" \"];\n      }\n      return h(\"a.checkbox-outer\", {\n        href: \"#Select\",\n        onmousedown: this.handleSelectMousedown,\n        onclick: this.handleSelectClick,\n        inner_path: item.inner_path\n      }, h(\"span.checkbox\"));\n    };\n\n    FileList.prototype.renderItem = function(item) {\n      var classes, downloaded_percent, ext, href, href_edit, inner_path, is_added, is_dir, is_editable, is_editing, is_modified, obj, optional_info, ref, ref1, style, title;\n      if (item.type === \"parent\") {\n        href = this.url_root.replace(/^(.*)\\/.{2,255}?$/, \"$1/\");\n      } else if (item.type === \"dir\") {\n        href = this.url_root + item.name;\n      } else {\n        href = this.url_root.replace(/^\\/list\\//, \"/\") + item.name;\n      }\n      inner_path = this.inner_path + item.name;\n      href_edit = this.getEditHref(inner_path);\n      is_dir = (ref = item.type) === \"dir\" || ref === \"parent\";\n      ext = item.name.split(\".\").pop();\n      is_editing = inner_path === ((ref1 = Page.file_editor) != null ? ref1.inner_path : void 0);\n      is_editable = !is_dir && item.size < 1024 * 1024 && indexOf.call(window.BINARY_EXTENSIONS, ext) < 0;\n      is_modified = this.item_list.isModified(inner_path);\n      is_added = this.item_list.isAdded(inner_path);\n      optional_info = this.item_list.getOptionalInfo(inner_path);\n      style = \"\";\n      title = \"\";\n      if (optional_info) {\n        downloaded_percent = optional_info.downloaded_percent;\n        if (!downloaded_percent) {\n          downloaded_percent = 0;\n        }\n        style += \"background: linear-gradient(90deg, #fff6dd, \" + downloaded_percent + \"%, white, \" + downloaded_percent + \"%, white);\";\n        is_added = false;\n      }\n      if (item.ignored) {\n        is_added = false;\n      }\n      if (is_modified) {\n        title += \" (modified)\";\n      }\n      if (is_added) {\n        title += \" (new)\";\n      }\n      if (optional_info || item.optional_empty) {\n        title += \" (optional)\";\n      }\n      if (item.ignored) {\n        title += \" (ignored from content.json)\";\n      }\n      classes = (\n        obj = {},\n        obj[\"type-\" + item.type] = true,\n        obj.editing = is_editing,\n        obj.nobuttons = !is_editable,\n        obj.selected = this.selected[inner_path],\n        obj.modified = is_modified,\n        obj.added = is_added,\n        obj.ignored = item.ignored,\n        obj.optional = optional_info,\n        obj.optional_empty = item.optional_empty,\n        obj\n      );\n      return h(\"div.tr\", {\n        key: item.name,\n        classes: classes,\n        style: style,\n        onmouseenter: this.handleRowMouseenter,\n        inner_path: inner_path\n      }, [\n        h(\"div.td.pre\", {\n          title: title\n        }, this.renderItemCheckbox(item)), h(\"div.td.name\", h(\"a.link\", {\n          href: href\n        }, item.name)), h(\"div.td.buttons\", is_editable ? h(\"a.edit\", {\n          href: href_edit\n        }, Page.site_info.settings.own ? \"Edit\" : \"View\") : void 0), h(\"div.td.size\", is_dir ? \"[DIR]\" : Text.formatSize(item.size))\n      ]);\n    };\n\n    FileList.prototype.renderItems = function() {\n      return [\n        this.item_list.error && !this.item_list.items.length && !this.item_list.updating ? [\n          h(\"div.tr\", {\n            key: \"error\"\n          }, h(\"div.td.full.error\", this.item_list.error))\n        ] : void 0, this.inner_path ? this.renderItem({\n          \"name\": \"..\",\n          type: \"parent\",\n          size: 0\n        }) : void 0, this.item_list.items.map(this.renderItem)\n      ];\n    };\n\n    FileList.prototype.renderFoot = function() {\n      var dirs, file, files, foot_text, item, ref, ref1, ref2, ref3, total_size;\n      files = (function() {\n        var i, len, ref, ref1, results;\n        ref = this.item_list.items;\n        results = [];\n        for (i = 0, len = ref.length; i < len; i++) {\n          item = ref[i];\n          if ((ref1 = item.type) !== \"parent\" && ref1 !== \"dir\") {\n            results.push(item);\n          }\n        }\n        return results;\n      }).call(this);\n      dirs = (function() {\n        var i, len, ref, results;\n        ref = this.item_list.items;\n        results = [];\n        for (i = 0, len = ref.length; i < len; i++) {\n          item = ref[i];\n          if (item.type === \"dir\") {\n            results.push(item);\n          }\n        }\n        return results;\n      }).call(this);\n      if (files.length) {\n        total_size = ((function() {\n          var i, len, results;\n          results = [];\n          for (i = 0, len = files.length; i < len; i++) {\n            file = files[i];\n            results.push(item.size);\n          }\n          return results;\n        })()).reduce(function(a, b) {\n          return a + b;\n        });\n      } else {\n        total_size = 0;\n      }\n      foot_text = \"Total: \";\n      foot_text += dirs.length + \" dir, \" + files.length + \" file in \" + (Text.formatSize(total_size));\n      return [\n        dirs.length || files.length || ((ref = Page.site_info) != null ? (ref1 = ref.settings) != null ? ref1.own : void 0 : void 0) ? h(\"div.tr.foot-info.foot\", h(\"div.td.full\", [\n          this.item_list.updating ? \"Updating file list...\" : dirs.length || files.length ? foot_text : void 0, ((ref2 = Page.site_info) != null ? (ref3 = ref2.settings) != null ? ref3.own : void 0 : void 0) ? h(\"div.create\", [\n            h(\"a.link\", {\n              href: \"#Create+new+file\",\n              onclick: this.handleNewFileClick\n            }, \"+ New\"), this.menu_create.render()\n          ]) : void 0\n        ])) : void 0\n      ];\n    };\n\n    FileList.prototype.render = function() {\n      if (this.need_update) {\n        this.update();\n        this.need_update = false;\n        if (!this.item_list.items) {\n          return [];\n        }\n      }\n      return h(\"div.files\", [this.renderSelectbar(), this.renderHead(), h(\"div.tbody\", this.renderItems()), this.renderFoot()]);\n    };\n\n    return FileList;\n\n  })(Class);\n\n  window.FileList = FileList;\n\n}).call(this);\n\n/* ---- UiFileManager.coffee ---- */\n\n\n(function() {\n  var UiFileManager,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  window.h = maquette.h;\n\n  UiFileManager = (function(superClass) {\n    extend(UiFileManager, superClass);\n\n    function UiFileManager() {\n      this.render = bind(this.render, this);\n      this.createProjector = bind(this.createProjector, this);\n      this.onRequest = bind(this.onRequest, this);\n      this.checkBodyWidth = bind(this.checkBodyWidth, this);\n      return UiFileManager.__super__.constructor.apply(this, arguments);\n    }\n\n    UiFileManager.prototype.init = function() {\n      this.url_params = new URLSearchParams(window.location.search);\n      this.list_site = this.url_params.get(\"site\");\n      this.list_address = this.url_params.get(\"address\");\n      this.list_inner_path = this.url_params.get(\"inner_path\");\n      this.editor_inner_path = this.url_params.get(\"file\");\n      this.file_list = new FileList(this.list_site, this.list_inner_path);\n      this.site_info = null;\n      this.server_info = null;\n      this.is_sidebar_closed = false;\n      if (this.editor_inner_path) {\n        this.file_editor = new FileEditor(this.editor_inner_path);\n      }\n      window.onbeforeunload = (function(_this) {\n        return function() {\n          var ref;\n          if ((ref = _this.file_editor) != null ? ref.isModified() : void 0) {\n            return true;\n          } else {\n            return null;\n          }\n        };\n      })(this);\n      window.onresize = (function(_this) {\n        return function() {\n          return _this.checkBodyWidth();\n        };\n      })(this);\n      this.checkBodyWidth();\n      this.cmd(\"wrapperSetViewport\", \"width=device-width, initial-scale=0.8\");\n      this.cmd(\"serverInfo\", {}, (function(_this) {\n        return function(server_info) {\n          return _this.server_info = server_info;\n        };\n      })(this));\n      return this.cmd(\"siteInfo\", {}, (function(_this) {\n        return function(site_info) {\n          _this.cmd(\"wrapperSetTitle\", \"List: /\" + _this.list_inner_path + \" - \" + site_info.content.title + \" - ZeroNet\");\n          _this.site_info = site_info;\n          if (_this.file_editor) {\n            _this.file_editor.on_loaded.then(function() {\n              _this.file_editor.cm.setOption(\"readOnly\", !site_info.settings.own);\n              return _this.file_editor.mode = site_info.settings.own ? \"Edit\" : \"View\";\n            });\n          }\n          return _this.projector.scheduleRender();\n        };\n      })(this));\n    };\n\n    UiFileManager.prototype.checkBodyWidth = function() {\n      var ref, ref1;\n      if (!this.file_editor) {\n        return false;\n      }\n      if (document.body.offsetWidth < 960 && !this.is_sidebar_closed) {\n        this.is_sidebar_closed = true;\n        return (ref = this.projector) != null ? ref.scheduleRender() : void 0;\n      } else if (document.body.offsetWidth > 960 && this.is_sidebar_closed) {\n        this.is_sidebar_closed = false;\n        return (ref1 = this.projector) != null ? ref1.scheduleRender() : void 0;\n      }\n    };\n\n    UiFileManager.prototype.onRequest = function(cmd, message) {\n      if (cmd === \"setSiteInfo\") {\n        this.site_info = message;\n        RateLimitCb(1000, (function(_this) {\n          return function(cb_done) {\n            return _this.file_list.update(cb_done);\n          };\n        })(this));\n        return this.projector.scheduleRender();\n      } else if (cmd === \"setServerInfo\") {\n        this.server_info = message;\n        return this.projector.scheduleRender();\n      } else {\n        return this.log(\"Unknown incoming message:\", cmd);\n      }\n    };\n\n    UiFileManager.prototype.createProjector = function() {\n      this.projector = maquette.createProjector();\n      return this.projector.replace($(\"#content\"), this.render);\n    };\n\n    UiFileManager.prototype.render = function() {\n      return h(\"div.content#content\", [\n        h(\"div.manager\", {\n          classes: {\n            editing: this.file_editor,\n            sidebar_closed: this.is_sidebar_closed\n          }\n        }, [this.file_list.render(), this.file_editor ? this.file_editor.render() : void 0])\n      ]);\n    };\n\n    return UiFileManager;\n\n  })(ZeroFrame);\n\n  window.Page = new UiFileManager();\n\n  window.Page.createProjector();\n\n}).call(this);"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Animation.coffee",
    "content": "class Animation\n\tslideDown: (elem, props) ->\n\t\tif elem.offsetTop > 2000\n\t\t\treturn\n\n\t\th = elem.offsetHeight\n\t\tcstyle = window.getComputedStyle(elem)\n\t\tmargin_top = cstyle.marginTop\n\t\tmargin_bottom = cstyle.marginBottom\n\t\tpadding_top = cstyle.paddingTop\n\t\tpadding_bottom = cstyle.paddingBottom\n\t\ttransition = cstyle.transition\n\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(0.6)\"\n\t\telem.style.opacity = \"0\"\n\t\telem.style.height = \"0px\"\n\t\telem.style.marginTop = \"0px\"\n\t\telem.style.marginBottom = \"0px\"\n\t\telem.style.paddingTop = \"0px\"\n\t\telem.style.paddingBottom = \"0px\"\n\t\telem.style.transition = \"none\"\n\n\t\tsetTimeout (->\n\t\t\telem.className += \" animate-inout\"\n\t\t\telem.style.height = h+\"px\"\n\t\t\telem.style.transform = \"scale(1)\"\n\t\t\telem.style.opacity = \"1\"\n\t\t\telem.style.marginTop = margin_top\n\t\t\telem.style.marginBottom = margin_bottom\n\t\t\telem.style.paddingTop = padding_top\n\t\t\telem.style.paddingBottom = padding_bottom\n\t\t), 1\n\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate-inout\")\n\t\t\telem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null\n\t\t\telem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null\n\t\t\telem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null\n\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\n\n\tslideUp: (elem, remove_func, props) ->\n\t\tif elem.offsetTop > 1000\n\t\t\treturn remove_func()\n\n\t\telem.className += \" animate-back\"\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.height = elem.offsetHeight+\"px\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(1)\"\n\t\telem.style.opacity = \"1\"\n\t\telem.style.pointerEvents = \"none\"\n\t\tsetTimeout (->\n\t\t\telem.style.height = \"0px\"\n\t\t\telem.style.marginTop = \"0px\"\n\t\t\telem.style.marginBottom = \"0px\"\n\t\t\telem.style.paddingTop = \"0px\"\n\t\t\telem.style.paddingBottom = \"0px\"\n\t\t\telem.style.transform = \"scale(0.8)\"\n\t\t\telem.style.borderTopWidth = \"0px\"\n\t\t\telem.style.borderBottomWidth = \"0px\"\n\t\t\telem.style.opacity = \"0\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\" or e.elapsedTime >= 0.6\n\t\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\t\t\t\tremove_func()\n\n\n\tslideUpInout: (elem, remove_func, props) ->\n\t\telem.className += \" animate-inout\"\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.height = elem.offsetHeight+\"px\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(1)\"\n\t\telem.style.opacity = \"1\"\n\t\telem.style.pointerEvents = \"none\"\n\t\tsetTimeout (->\n\t\t\telem.style.height = \"0px\"\n\t\t\telem.style.marginTop = \"0px\"\n\t\t\telem.style.marginBottom = \"0px\"\n\t\t\telem.style.paddingTop = \"0px\"\n\t\t\telem.style.paddingBottom = \"0px\"\n\t\t\telem.style.transform = \"scale(0.8)\"\n\t\t\telem.style.borderTopWidth = \"0px\"\n\t\t\telem.style.borderBottomWidth = \"0px\"\n\t\t\telem.style.opacity = \"0\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\" or e.elapsedTime >= 0.6\n\t\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\t\t\t\tremove_func()\n\n\n\tshowRight: (elem, props) ->\n\t\telem.className += \" animate\"\n\t\telem.style.opacity = 0\n\t\telem.style.transform = \"TranslateX(-20px) Scale(1.01)\"\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 1\n\t\t\telem.style.transform = \"TranslateX(0px) Scale(1)\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate\")\n\t\t\telem.style.transform = elem.style.opacity = null\n\n\n\tshow: (elem, props) ->\n\t\tdelay = arguments[arguments.length-2]?.delay*1000 or 1\n\t\telem.style.opacity = 0\n\t\tsetTimeout (->\n\t\t\telem.className += \" animate\"\n\t\t), 1\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 1\n\t\t), delay\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate\")\n\t\t\telem.style.opacity = null\n\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\n\thide: (elem, remove_func, props) ->\n\t\tdelay = arguments[arguments.length-2]?.delay*1000 or 1\n\t\telem.className += \" animate\"\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 0\n\t\t), delay\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\"\n\t\t\t\tremove_func()\n\n\taddVisibleClass: (elem, props) ->\n\t\tsetTimeout ->\n\t\t\telem.classList.add(\"visible\")\n\nwindow.Animation = new Animation()"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Class.coffee",
    "content": "class Class\n\ttrace: true\n\n\tlog: (args...) ->\n\t\treturn unless @trace\n\t\treturn if typeof console is 'undefined'\n\t\targs.unshift(\"[#{@.constructor.name}]\")\n\t\tconsole.log(args...)\n\t\t@\n\t\t\n\tlogStart: (name, args...) ->\n\t\treturn unless @trace\n\t\t@logtimers or= {}\n\t\t@logtimers[name] = +(new Date)\n\t\t@log \"#{name}\", args..., \"(started)\" if args.length > 0\n\t\t@\n\t\t\n\tlogEnd: (name, args...) ->\n\t\tms = +(new Date)-@logtimers[name]\n\t\t@log \"#{name}\", args..., \"(Done in #{ms}ms)\"\n\t\t@ \n\nwindow.Class = Class"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Dollar.coffee",
    "content": "window.$ = (selector) ->\n\tif selector.startsWith(\"#\")\n\t\treturn document.getElementById(selector.replace(\"#\", \"\"))\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/ItemList.coffee",
    "content": "class ItemList\n\tconstructor: (@item_class, @key) ->\n\t\t@items = []\n\t\t@items_bykey = {}\n\n\tsync: (rows, item_class, key) ->\n\t\t@items.splice(0, @items.length)  # Empty items\n\t\tfor row in rows\n\t\t\tcurrent_obj = @items_bykey[row[@key]]\n\t\t\tif current_obj\n\t\t\t\tcurrent_obj.row = row\n\t\t\t\t@items.push current_obj\n\t\t\telse\n\t\t\t\titem = new @item_class(row, @)\n\t\t\t\t@items_bykey[row[@key]] = item\n\t\t\t\t@items.push item\n\n\tdeleteItem: (item) ->\n\t\tindex = @items.indexOf(item)\n\t\tif index > -1\n\t\t\t@items.splice(index, 1)\n\t\telse\n\t\t\tconsole.log \"Can't delete item\", item\n\t\tdelete @items_bykey[item.row[@key]]\n\nwindow.ItemList = ItemList"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Menu.coffee",
    "content": "class Menu\n\tconstructor: ->\n\t\t@visible = false\n\t\t@items = []\n\t\t@node = null\n\t\t@height = 0\n\t\t@direction = \"bottom\"\n\n\tshow: =>\n\t\twindow.visible_menu?.hide()\n\t\t@visible = true\n\t\twindow.visible_menu = @\n\t\t@direction = @getDirection()\n\n\thide: =>\n\t\t@visible = false\n\n\ttoggle: =>\n\t\tif @visible\n\t\t\t@hide()\n\t\telse\n\t\t\t@show()\n\t\tPage.projector.scheduleRender()\n\n\n\taddItem: (title, cb, selected=false) ->\n\t\t@items.push([title, cb, selected])\n\n\n\tstoreNode: (node) =>\n\t\t@node = node\n\t\t# Animate visible\n\t\tif @visible\n\t\t\tnode.className = node.className.replace(\"visible\", \"\")\n\t\t\tsetTimeout (=>\n\t\t\t\tnode.className += \" visible\"\n\t\t\t\tnode.attributes.style.value = @getStyle()\n\t\t\t), 20\n\t\t\tnode.style.maxHeight = \"none\"\n\t\t\t@height = node.offsetHeight\n\t\t\tnode.style.maxHeight = \"0px\"\n\t\t\t@direction = @getDirection()\n\n\tgetDirection: =>\n\t\tif @node and @node.parentNode.getBoundingClientRect().top + @height + 60 > document.body.clientHeight and @node.parentNode.getBoundingClientRect().top - @height > 0\n\t\t\treturn \"top\"\n\t\telse\n\t\t\treturn \"bottom\"\n\n\thandleClick: (e) =>\n\t\tkeep_menu = false\n\t\tfor item in @items\n\t\t\t[title, cb, selected] = item\n\t\t\tif title == e.currentTarget.textContent or e.currentTarget[\"data-title\"] == title\n\t\t\t\tkeep_menu = cb?(item)\n\t\t\t\tbreak\n\t\tif keep_menu != true and cb != null\n\t\t\t@hide()\n\t\treturn false\n\n\trenderItem: (item) =>\n\t\t[title, cb, selected] = item\n\t\tif typeof(selected) == \"function\"\n\t\t\tselected = selected()\n\n\t\tif title == \"---\"\n\t\t\treturn h(\"div.menu-item-separator\", {key: Time.timestamp()})\n\t\telse\n\t\t\tif cb == null\n\t\t\t\thref = undefined\n\t\t\t\tonclick = @handleClick\n\t\t\telse if typeof(cb) == \"string\"  # Url\n\t\t\t\thref = cb\n\t\t\t\tonclick = true\n\t\t\telse  # Callback\n\t\t\t\thref = \"#\"+title\n\t\t\t\tonclick = @handleClick\n\t\t\tclasses = {\n\t\t\t\t\"selected\": selected,\n\t\t\t\t\"noaction\": (cb == null)\n\t\t\t}\n\t\t\treturn h(\"a.menu-item\", {href: href, onclick: onclick, \"data-title\": title, key: title, classes: classes}, title)\n\n\tgetStyle: =>\n\t\tif @visible\n\t\t\tmax_height = @height\n\t\telse\n\t\t\tmax_height = 0\n\t\tstyle = \"max-height: #{max_height}px\"\n\t\tif @direction == \"top\"\n\t\t\tstyle += \";margin-top: #{0 - @height - 50}px\"\n\t\telse\n\t\t\tstyle += \";margin-top: 0px\"\n\t\treturn style\n\n\trender: (class_name=\"\") =>\n\t\tif @visible or @node\n\t\t\th(\"div.menu#{class_name}\", {classes: {\"visible\": @visible}, style: @getStyle(), afterCreate: @storeNode}, @items.map(@renderItem))\n\nwindow.Menu = Menu\n\n# Hide menu on outside click\ndocument.body.addEventListener \"mouseup\", (e) ->\n\tif not window.visible_menu or not window.visible_menu.node\n\t\treturn false\n\tmenu_node = window.visible_menu.node\n\tmenu_parents = [menu_node, menu_node.parentNode]\n\tif e.target.parentNode not in menu_parents and e.target.parentNode.parentNode not in menu_parents\n\t\twindow.visible_menu.hide()\n\t\tPage.projector.scheduleRender()\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Promise.coffee",
    "content": "# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html\n\nclass Promise\n\t@when: (tasks...) ->\n\t\tnum_uncompleted = tasks.length\n\t\targs = new Array(num_uncompleted)\n\t\tpromise = new Promise()\n\n\t\tfor task, task_id in tasks\n\t\t\t((task_id) ->\n\t\t\t\ttask.then(() ->\n\t\t\t\t\targs[task_id] = Array.prototype.slice.call(arguments)\n\t\t\t\t\tnum_uncompleted--\n\t\t\t\t\tpromise.complete.apply(promise, args) if num_uncompleted == 0\n\t\t\t\t)\n\t\t\t)(task_id)\n\n\t\treturn promise\n\n\tconstructor: ->\n\t\t@resolved = false\n\t\t@end_promise = null\n\t\t@result = null\n\t\t@callbacks = []\n\n\tresolve: ->\n\t\tif @resolved\n\t\t\treturn false\n\t\t@resolved = true\n\t\t@data = arguments\n\t\tif not arguments.length\n\t\t\t@data = [true]\n\t\t@result = @data[0]\n\t\tfor callback in @callbacks\n\t\t\tback = callback.apply callback, @data\n\t\tif @end_promise\n\t\t\t@end_promise.resolve(back)\n\n\tfail: ->\n\t\t@resolve(false)\n\n\tthen: (callback) ->\n\t\tif @resolved == true\n\t\t\tcallback.apply callback, @data\n\t\t\treturn\n\n\t\t@callbacks.push callback\n\n\t\t@end_promise = new Promise()\n\nwindow.Promise = Promise\n\n###\ns = Date.now()\nlog = (text) ->\n\tconsole.log Date.now()-s, Array.prototype.slice.call(arguments).join(\", \")\n\nlog \"Started\"\n\ncmd = (query) ->\n\tp = new Promise()\n\tsetTimeout ( ->\n\t\tp.resolve query+\" Result\"\n\t), 100\n\treturn p\n\nback = cmd(\"SELECT * FROM message\").then (res) ->\n\tlog res\n\treturn \"Return from query\"\n.then (res) ->\n\tlog \"Back then\", res\n\nlog \"Query started\", back\n###"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Prototypes.coffee",
    "content": "String::startsWith = (s) -> @[...s.length] is s\nString::endsWith = (s) -> s is '' or @[-s.length..] is s\nString::repeat = (count) -> new Array( count + 1 ).join(@)\n\nwindow.isEmpty = (obj) ->\n\tfor key of obj\n\t\treturn false\n\treturn true\n\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/RateLimitCb.coffee",
    "content": "last_time = {}\ncalling = {}\ncalling_iterval = {}\ncall_after_interval = {}\n\n# Rate limit function call and don't allow to run in parallel (until callback is called)\nwindow.RateLimitCb = (interval, fn, args=[]) ->\n    cb = ->  # Callback when function finished\n        left = interval - (Date.now() - last_time[fn])  # Time life until next call\n        # console.log \"CB, left\", left, \"Calling:\", calling[fn]\n        if left <= 0  # No time left from rate limit interval\n            delete last_time[fn]\n            if calling[fn]  # Function called within interval\n                RateLimitCb(interval, fn, calling[fn])\n            delete calling[fn]\n        else  # Time left from rate limit interval\n            setTimeout (->\n                delete last_time[fn]\n                if calling[fn]  # Function called within interval\n                    RateLimitCb(interval, fn, calling[fn])\n                delete calling[fn]\n            ), left\n    if last_time[fn]  # Function called within interval\n        calling[fn] = args  # Schedule call and update arguments\n    else  # Not called within interval, call instantly\n        last_time[fn] = Date.now()\n        fn.apply(this, [cb, args...])\n\n\nwindow.RateLimit = (interval, fn) ->\n    if calling_iterval[fn] > interval\n        clearInterval calling[fn]\n        delete calling[fn]\n\n    if not calling[fn]\n        call_after_interval[fn] = false\n        fn() # First call is not delayed\n        calling_iterval[fn] = interval\n        calling[fn] = setTimeout (->\n            if call_after_interval[fn]\n                fn()\n            delete calling[fn]\n            delete call_after_interval[fn]\n        ), interval\n    else # Called within iterval, delay the call\n        call_after_interval[fn] = true\n\n\n###\nwindow.s = Date.now()\nwindow.load = (done, num) ->\n  console.log \"Loading #{num}...\", Date.now()-window.s\n  setTimeout (-> done()), 1000\n\nRateLimit 500, window.load, [0] # Called instantly\nRateLimit 500, window.load, [1]\nsetTimeout (-> RateLimit 500, window.load, [300]), 300\nsetTimeout (-> RateLimit 500, window.load, [600]), 600 # Called after 1000ms\nsetTimeout (-> RateLimit 500, window.load, [1000]), 1000\nsetTimeout (-> RateLimit 500, window.load, [1200]), 1200  # Called after 2000ms\nsetTimeout (-> RateLimit 500, window.load, [3000]), 3000  # Called after 3000ms\n###"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Text.coffee",
    "content": "class Text\n\ttoColor: (text, saturation=30, lightness=50) ->\n\t\thash = 0\n\t\tfor i in [0..text.length-1]\n\t\t\thash += text.charCodeAt(i)*i\n\t\t\thash = hash % 1777\n\t\treturn \"hsl(\" + (hash % 360) + \",#{saturation}%,#{lightness}%)\";\n\n\n\trenderMarked: (text, options={}) ->\n\t\toptions[\"gfm\"] = true\n\t\toptions[\"breaks\"] = true\n\t\toptions[\"sanitize\"] = true\n\t\toptions[\"renderer\"] = marked_renderer\n\t\ttext = marked(text, options)\n\t\treturn @fixHtmlLinks text\n\n\temailLinks: (text) ->\n\t\treturn text.replace(/([a-zA-Z0-9]+)@zeroid.bit/g, \"<a href='?to=$1' onclick='return Page.message_create.show(\\\"$1\\\")'>$1@zeroid.bit</a>\")\n\n\t# Convert zeronet html links to relaitve\n\tfixHtmlLinks: (text) ->\n\t\tif window.is_proxy\n\t\t\treturn text.replace(/href=\"http:\\/\\/(127.0.0.1|localhost):43110/g, 'href=\"http://zero')\n\t\telse\n\t\t\treturn text.replace(/href=\"http:\\/\\/(127.0.0.1|localhost):43110/g, 'href=\"')\n\n\t# Convert a single link to relative\n\tfixLink: (link) ->\n\t\tif window.is_proxy\n\t\t\tback = link.replace(/http:\\/\\/(127.0.0.1|localhost):43110/, 'http://zero')\n\t\t\treturn back.replace(/http:\\/\\/zero\\/([^\\/]+\\.bit)/, \"http://$1\")  # Domain links\n\t\telse\n\t\t\treturn link.replace(/http:\\/\\/(127.0.0.1|localhost):43110/, '')\n\n\ttoUrl: (text) ->\n\t\treturn text.replace(/[^A-Za-z0-9]/g, \"+\").replace(/[+]+/g, \"+\").replace(/[+]+$/, \"\")\n\n\tgetSiteUrl: (address) ->\n\t\tif window.is_proxy\n\t\t\tif \".\" in address # Domain\n\t\t\t\treturn \"http://\"+address+\"/\"\n\t\t\telse\n\t\t\t\treturn \"http://zero/\"+address+\"/\"\n\t\telse\n\t\t\treturn \"/\"+address+\"/\"\n\n\n\tfixReply: (text) ->\n\t\treturn text.replace(/(>.*\\n)([^\\n>])/gm, \"$1\\n$2\")\n\n\ttoBitcoinAddress: (text) ->\n\t\treturn text.replace(/[^A-Za-z0-9]/g, \"\")\n\n\n\tjsonEncode: (obj) ->\n\t\treturn unescape(encodeURIComponent(JSON.stringify(obj)))\n\n\tjsonDecode: (obj) ->\n\t\treturn JSON.parse(decodeURIComponent(escape(obj)))\n\n\tfileEncode: (obj) ->\n\t\tif typeof(obj) == \"string\"\n\t\t\treturn btoa(unescape(encodeURIComponent(obj)))\n\t\telse\n\t\t\treturn btoa(unescape(encodeURIComponent(JSON.stringify(obj, undefined, '\\t'))))\n\n\tutf8Encode: (s) ->\n\t\treturn unescape(encodeURIComponent(s))\n\n\tutf8Decode: (s) ->\n\t\treturn decodeURIComponent(escape(s))\n\n\n\tdistance: (s1, s2) ->\n\t\ts1 = s1.toLocaleLowerCase()\n\t\ts2 = s2.toLocaleLowerCase()\n\t\tnext_find_i = 0\n\t\tnext_find = s2[0]\n\t\tmatch = true\n\t\textra_parts = {}\n\t\tfor char in s1\n\t\t\tif char != next_find\n\t\t\t\tif extra_parts[next_find_i]\n\t\t\t\t\textra_parts[next_find_i] += char\n\t\t\t\telse\n\t\t\t\t\textra_parts[next_find_i] = char\n\t\t\telse\n\t\t\t\tnext_find_i++\n\t\t\t\tnext_find = s2[next_find_i]\n\n\t\tif extra_parts[next_find_i]\n\t\t\textra_parts[next_find_i] = \"\"  # Extra chars on the end doesnt matter\n\t\textra_parts = (val for key, val of extra_parts)\n\t\tif next_find_i >= s2.length\n\t\t\treturn extra_parts.length + extra_parts.join(\"\").length\n\t\telse\n\t\t\treturn false\n\n\n\tparseQuery: (query) ->\n\t\tparams = {}\n\t\tparts = query.split('&')\n\t\tfor part in parts\n\t\t\t[key, val] = part.split(\"=\")\n\t\t\tif val\n\t\t\t\tparams[decodeURIComponent(key)] = decodeURIComponent(val)\n\t\t\telse\n\t\t\t\tparams[\"url\"] = decodeURIComponent(key)\n\t\treturn params\n\n\tencodeQuery: (params) ->\n\t\tback = []\n\t\tif params.url\n\t\t\tback.push(params.url)\n\t\tfor key, val of params\n\t\t\tif not val or key == \"url\"\n\t\t\t\tcontinue\n\t\t\tback.push(\"#{encodeURIComponent(key)}=#{encodeURIComponent(val)}\")\n\t\treturn back.join(\"&\")\n\n\thighlight: (text, search) ->\n\t\tif not text\n\t\t\treturn [\"\"]\n\t\tparts = text.split(RegExp(search, \"i\"))\n\t\tback = []\n\t\tfor part, i in parts\n\t\t\tback.push(part)\n\t\t\tif i < parts.length-1\n\t\t\t\tback.push(h(\"span.highlight\", {key: i}, search))\n\t\treturn back\n\n\tformatSize: (size) ->\n\t\tif isNaN(parseInt(size))\n\t\t\treturn \"\"\n\t\tsize_mb = size/1024/1024\n\t\tif size_mb >= 1000\n\t\t\treturn (size_mb/1024).toFixed(1)+\" GB\"\n\t\telse if size_mb >= 100\n\t\t\treturn size_mb.toFixed(0)+\" MB\"\n\t\telse if size/1024 >= 1000\n\t\t\treturn size_mb.toFixed(2)+\" MB\"\n\t\telse\n\t\t\treturn (parseInt(size)/1024).toFixed(2)+\" KB\"\n\nwindow.is_proxy = (document.location.host == \"zero\" or window.location.pathname == \"/\")\nwindow.Text = new Text()\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/Time.coffee",
    "content": "class Time\n\tsince: (timestamp) ->\n\t\tnow = +(new Date)/1000\n\t\tif timestamp > 1000000000000  # In ms\n\t\t\ttimestamp = timestamp/1000\n\t\tsecs = now - timestamp\n\t\tif secs < 60\n\t\t\tback = \"Just now\"\n\t\telse if secs < 60*60\n\t\t\tminutes = Math.round(secs/60)\n\t\t\tback = \"\" + minutes + \" minutes ago\"\n\t\telse if secs < 60*60*24\n\t\t\tback = \"#{Math.round(secs/60/60)} hours ago\"\n\t\telse if secs < 60*60*24*3\n\t\t\tback = \"#{Math.round(secs/60/60/24)} days ago\"\n\t\telse\n\t\t\tback = \"on \"+@date(timestamp)\n\t\tback = back.replace(/^1 ([a-z]+)s/, \"1 $1\") # 1 days ago fix\n\t\treturn back\n\n\tdateIso: (timestamp=null) ->\n\t\tif not timestamp\n\t\t\ttimestamp = window.Time.timestamp()\n\n\t\tif timestamp > 1000000000000  # In ms\n\t\t\ttimestamp = timestamp/1000\n\t\ttzoffset = (new Date()).getTimezoneOffset() * 60\n\t\treturn (new Date((timestamp - tzoffset) * 1000)).toISOString().split(\"T\")[0]\n\n\tdate: (timestamp=null, format=\"short\") ->\n\t\tif not timestamp\n\t\t\ttimestamp = window.Time.timestamp()\n\n\t\tif timestamp > 1000000000000  # In ms\n\t\t\ttimestamp = timestamp/1000\n\t\tparts = (new Date(timestamp * 1000)).toString().split(\" \")\n\t\tif format == \"short\"\n\t\t\tdisplay = parts.slice(1, 4)\n\t\telse if format == \"day\"\n\t\t\tdisplay = parts.slice(1, 3)\n\t\telse if format == \"month\"\n\t\t\tdisplay = [parts[1], parts[3]]\n\t\telse if format == \"long\"\n\t\t\tdisplay = parts.slice(1, 5)\n\t\treturn display.join(\" \").replace(/( [0-9]{4})/, \",$1\")\n\n\tweekDay: (timestamp) ->\n\t\tif timestamp > 1000000000000  # In ms\n\t\t\ttimestamp = timestamp/1000\n\t\treturn [\"Sunday\",\"Monday\",\"Tuesday\",\"Wednesday\",\"Thursday\",\"Friday\",\"Saturday\"][ (new Date(timestamp * 1000)).getDay() ]\n\n\ttimestamp: (date=\"\") ->\n\t\tif date == \"now\" or date == \"\"\n\t\t\treturn parseInt(+(new Date)/1000)\n\t\telse\n\t\t\treturn parseInt(Date.parse(date)/1000)\n\n\nwindow.Time = new Time"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/ZeroFrame.coffee",
    "content": "class ZeroFrame extends Class\n\tconstructor: (url) ->\n\t\t@url = url\n\t\t@waiting_cb = {}\n\t\t@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\")\n\t\t@connect()\n\t\t@next_message_id = 1\n\t\t@history_state = {}\n\t\t@init()\n\n\n\tinit: ->\n\t\t@\n\n\n\tconnect: ->\n\t\t@target = window.parent\n\t\twindow.addEventListener(\"message\", @onMessage, false)\n\t\t@cmd(\"innerReady\")\n\n\t\t# Save scrollTop\n\t\twindow.addEventListener \"beforeunload\", (e) =>\n\t\t\t@log \"save scrollTop\", window.pageYOffset\n\t\t\t@history_state[\"scrollTop\"] = window.pageYOffset\n\t\t\t@cmd \"wrapperReplaceState\", [@history_state, null]\n\n\t\t# Restore scrollTop\n\t\t@cmd \"wrapperGetState\", [], (state) =>\n\t\t\t@history_state = state if state?\n\t\t\t@log \"restore scrollTop\", state, window.pageYOffset\n\t\t\tif window.pageYOffset == 0 and state\n\t\t\t\twindow.scroll(window.pageXOffset, state.scrollTop)\n\n\n\tonMessage: (e) =>\n\t\tmessage = e.data\n\t\tcmd = message.cmd\n\t\tif cmd == \"response\"\n\t\t\tif @waiting_cb[message.to]?\n\t\t\t\t@waiting_cb[message.to](message.result)\n\t\t\telse\n\t\t\t\t@log \"Websocket callback not found:\", message\n\t\telse if cmd == \"wrapperReady\" # Wrapper inited later\n\t\t\t@cmd(\"innerReady\")\n\t\telse if cmd == \"ping\"\n\t\t\t@response message.id, \"pong\"\n\t\telse if cmd == \"wrapperOpenedWebsocket\"\n\t\t\t@onOpenWebsocket()\n\t\telse if cmd == \"wrapperClosedWebsocket\"\n\t\t\t@onCloseWebsocket()\n\t\telse\n\t\t\t@onRequest cmd, message.params\n\n\n\tonRequest: (cmd, message) =>\n\t\t@log \"Unknown request\", message\n\n\n\tresponse: (to, result) ->\n\t\t@send {\"cmd\": \"response\", \"to\": to, \"result\": result}\n\n\n\tcmd: (cmd, params={}, cb=null) ->\n\t\t@send {\"cmd\": cmd, \"params\": params}, cb\n\n\n\tsend: (message, cb=null) ->\n\t\tmessage.wrapper_nonce = @wrapper_nonce\n\t\tmessage.id = @next_message_id\n\t\t@next_message_id += 1\n\t\t@target.postMessage(message, \"*\")\n\t\tif cb\n\t\t\t@waiting_cb[message.id] = cb\n\n\n\tonOpenWebsocket: =>\n\t\t@log \"Websocket open\"\n\n\n\tonCloseWebsocket: =>\n\t\t@log \"Websocket close\"\n\n\n\nwindow.ZeroFrame = ZeroFrame\n"
  },
  {
    "path": "plugins/UiFileManager/media/js/lib/maquette.js",
    "content": "(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['exports'], factory);\n    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {\n        // CommonJS\n        factory(exports);\n    } else {\n        // Browser globals\n        factory(root.maquette = {});\n    }\n}(this, function (exports) {\n    'use strict';\n    ;\n    ;\n    ;\n    ;\n    var NAMESPACE_W3 = 'http://www.w3.org/';\n    var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';\n    var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';\n    // Utilities\n    var emptyArray = [];\n    var extend = function (base, overrides) {\n        var result = {};\n        Object.keys(base).forEach(function (key) {\n            result[key] = base[key];\n        });\n        if (overrides) {\n            Object.keys(overrides).forEach(function (key) {\n                result[key] = overrides[key];\n            });\n        }\n        return result;\n    };\n    // Hyperscript helper functions\n    var same = function (vnode1, vnode2) {\n        if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {\n            return false;\n        }\n        if (vnode1.properties && vnode2.properties) {\n            if (vnode1.properties.key !== vnode2.properties.key) {\n                return false;\n            }\n            return vnode1.properties.bind === vnode2.properties.bind;\n        }\n        return !vnode1.properties && !vnode2.properties;\n    };\n    var toTextVNode = function (data) {\n        return {\n            vnodeSelector: '',\n            properties: undefined,\n            children: undefined,\n            text: data.toString(),\n            domNode: null\n        };\n    };\n    var appendChildren = function (parentSelector, insertions, main) {\n        for (var i = 0; i < insertions.length; i++) {\n            var item = insertions[i];\n            if (Array.isArray(item)) {\n                appendChildren(parentSelector, item, main);\n            } else {\n                if (item !== null && item !== undefined) {\n                    if (!item.hasOwnProperty('vnodeSelector')) {\n                        item = toTextVNode(item);\n                    }\n                    main.push(item);\n                }\n            }\n        }\n    };\n    // Render helper functions\n    var missingTransition = function () {\n        throw new Error('Provide a transitions object to the projectionOptions to do animations');\n    };\n    var DEFAULT_PROJECTION_OPTIONS = {\n        namespace: undefined,\n        eventHandlerInterceptor: undefined,\n        styleApplyer: function (domNode, styleName, value) {\n            // Provides a hook to add vendor prefixes for browsers that still need it.\n            domNode.style[styleName] = value;\n        },\n        transitions: {\n            enter: missingTransition,\n            exit: missingTransition\n        }\n    };\n    var applyDefaultProjectionOptions = function (projectorOptions) {\n        return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);\n    };\n    var checkStyleValue = function (styleValue) {\n        if (typeof styleValue !== 'string') {\n            throw new Error('Style values must be strings');\n        }\n    };\n    var setProperties = function (domNode, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            /* tslint:disable:no-var-keyword: edge case */\n            var propValue = properties[propName];\n            /* tslint:enable:no-var-keyword */\n            if (propName === 'className') {\n                throw new Error('Property \"className\" is not supported, use \"class\".');\n            } else if (propName === 'class') {\n                if (domNode.className) {\n                    // May happen if classes is specified before class\n                    domNode.className += ' ' + propValue;\n                } else {\n                    domNode.className = propValue;\n                }\n            } else if (propName === 'classes') {\n                // object with string keys and boolean values\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    if (propValue[className]) {\n                        domNode.classList.add(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                // object with string keys and string (!) values\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var styleValue = propValue[styleName];\n                    if (styleValue) {\n                        checkStyleValue(styleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, styleValue);\n                    }\n                }\n            } else if (propName === 'key') {\n                continue;\n            } else if (propValue === null || propValue === undefined) {\n                continue;\n            } else {\n                var type = typeof propValue;\n                if (type === 'function') {\n                    if (propName.lastIndexOf('on', 0) === 0) {\n                        if (eventHandlerInterceptor) {\n                            propValue = eventHandlerInterceptor(propName, propValue, domNode, properties);    // intercept eventhandlers\n                        }\n                        if (propName === 'oninput') {\n                            (function () {\n                                // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput\n                                var oldPropValue = propValue;\n                                propValue = function (evt) {\n                                    evt.target['oninput-value'] = evt.target.value;\n                                    // may be HTMLTextAreaElement as well\n                                    oldPropValue.apply(this, [evt]);\n                                };\n                            }());\n                        }\n                        domNode[propName] = propValue;\n                    }\n                } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {\n                    if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                        domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                    } else {\n                        domNode.setAttribute(propName, propValue);\n                    }\n                } else {\n                    domNode[propName] = propValue;\n                }\n            }\n        }\n    };\n    var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var propertiesUpdated = false;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            // assuming that properties will be nullified instead of missing is by design\n            var propValue = properties[propName];\n            var previousValue = previousProperties[propName];\n            if (propName === 'class') {\n                if (previousValue !== propValue) {\n                    throw new Error('\"class\" property may not be updated. Use the \"classes\" property for conditional css classes.');\n                }\n            } else if (propName === 'classes') {\n                var classList = domNode.classList;\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    var on = !!propValue[className];\n                    var previousOn = !!previousValue[className];\n                    if (on === previousOn) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (on) {\n                        classList.add(className);\n                    } else {\n                        classList.remove(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var newStyleValue = propValue[styleName];\n                    var oldStyleValue = previousValue[styleName];\n                    if (newStyleValue === oldStyleValue) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (newStyleValue) {\n                        checkStyleValue(newStyleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, newStyleValue);\n                    } else {\n                        projectionOptions.styleApplyer(domNode, styleName, '');\n                    }\n                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };\n    var findIndexOfChild = function (children, sameAs, start) {\n        if (sameAs.vnodeSelector !== '') {\n            // Never scan for text-nodes\n            for (var i = start; i < children.length; i++) {\n                if (same(children[i], sameAs)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    var nodeAdded = function (vNode, transitions) {\n        if (vNode.properties) {\n            var enterAnimation = vNode.properties.enterAnimation;\n            if (enterAnimation) {\n                if (typeof enterAnimation === 'function') {\n                    enterAnimation(vNode.domNode, vNode.properties);\n                } else {\n                    transitions.enter(vNode.domNode, vNode.properties, enterAnimation);\n                }\n            }\n        }\n    };\n    var nodeToRemove = function (vNode, transitions) {\n        var domNode = vNode.domNode;\n        if (vNode.properties) {\n            var exitAnimation = vNode.properties.exitAnimation;\n            if (exitAnimation) {\n                domNode.style.pointerEvents = 'none';\n                var removeDomNode = function () {\n                    if (domNode.parentNode) {\n                        domNode.parentNode.removeChild(domNode);\n                    }\n                };\n                if (typeof exitAnimation === 'function') {\n                    exitAnimation(domNode, removeDomNode, vNode.properties);\n                    return;\n                } else {\n                    transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);\n                    return;\n                }\n            }\n        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;\n        var transitions = projectionOptions.transitions;\n        var oldIndex = 0;\n        var newIndex = 0;\n        var i;\n        var textUpdated = false;\n        while (newIndex < newChildrenLength) {\n            var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;\n            var newChild = newChildren[newIndex];\n            if (oldChild !== undefined && same(oldChild, newChild)) {\n                textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;\n                oldIndex++;\n            } else {\n                var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);\n                if (findOldIndex >= 0) {\n                    // Remove preceding missing children\n                    for (i = oldIndex; i < findOldIndex; i++) {\n                        nodeToRemove(oldChildren[i], transitions);\n                        checkDistinguishable(oldChildren, i, vnode, 'removed');\n                    }\n                    textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;\n                    oldIndex = findOldIndex + 1;\n                } else {\n                    // New child\n                    createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);\n                    nodeAdded(newChild, transitions);\n                    checkDistinguishable(newChildren, newIndex, vnode, 'added');\n                }\n            }\n            newIndex++;\n        }\n        if (oldChildrenLength > oldIndex) {\n            // Remove child fragments\n            for (i = oldIndex; i < oldChildrenLength; i++) {\n                nodeToRemove(oldChildren[i], transitions);\n                checkDistinguishable(oldChildren, i, vnode, 'removed');\n            }\n        }\n        return textUpdated;\n    };\n    var addChildren = function (domNode, children, projectionOptions) {\n        if (!children) {\n            return;\n        }\n        for (var i = 0; i < children.length; i++) {\n            createDom(children[i], domNode, undefined, projectionOptions);\n        }\n    };\n    var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {\n        addChildren(domNode, vnode.children, projectionOptions);\n        // children before properties, needed for value property of <select>.\n        if (vnode.text) {\n            domNode.textContent = vnode.text;\n        }\n        setProperties(domNode, vnode.properties, projectionOptions);\n        if (vnode.properties && vnode.properties.afterCreate) {\n            vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n        }\n    };\n    createDom = function (vnode, parentNode, insertBefore, projectionOptions) {\n        var domNode, i, c, start = 0, type, found;\n        var vnodeSelector = vnode.vnodeSelector;\n        if (vnodeSelector === '') {\n            domNode = vnode.domNode = document.createTextNode(vnode.text);\n            if (insertBefore !== undefined) {\n                parentNode.insertBefore(domNode, insertBefore);\n            } else {\n                parentNode.appendChild(domNode);\n            }\n        } else {\n            for (i = 0; i <= vnodeSelector.length; ++i) {\n                c = vnodeSelector.charAt(i);\n                if (i === vnodeSelector.length || c === '.' || c === '#') {\n                    type = vnodeSelector.charAt(start - 1);\n                    found = vnodeSelector.slice(start, i);\n                    if (type === '.') {\n                        domNode.classList.add(found);\n                    } else if (type === '#') {\n                        domNode.id = found;\n                    } else {\n                        if (found === 'svg') {\n                            projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n                        }\n                        if (projectionOptions.namespace !== undefined) {\n                            domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);\n                        } else {\n                            domNode = vnode.domNode = document.createElement(found);\n                        }\n                        if (insertBefore !== undefined) {\n                            parentNode.insertBefore(domNode, insertBefore);\n                        } else {\n                            parentNode.appendChild(domNode);\n                        }\n                    }\n                    start = i + 1;\n                }\n            }\n            initPropertiesAndChildren(domNode, vnode, projectionOptions);\n        }\n    };\n    updateDom = function (previous, vnode, projectionOptions) {\n        var domNode = previous.domNode;\n        var textUpdated = false;\n        if (previous === vnode) {\n            return false;    // By contract, VNode objects may not be modified anymore after passing them to maquette\n        }\n        var updated = false;\n        if (vnode.vnodeSelector === '') {\n            if (vnode.text !== previous.text) {\n                var newVNode = document.createTextNode(vnode.text);\n                domNode.parentNode.replaceChild(newVNode, domNode);\n                vnode.domNode = newVNode;\n                textUpdated = true;\n                return textUpdated;\n            }\n        } else {\n            if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {\n                projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n            }\n            if (previous.text !== vnode.text) {\n                updated = true;\n                if (vnode.text === undefined) {\n                    domNode.removeChild(domNode.firstChild);    // the only textnode presumably\n                } else {\n                    domNode.textContent = vnode.text;\n                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {\n            // Optional properties argument was omitted\n            properties = undefined;\n        }\n        var text = undefined;\n        var children = undefined;\n        var argsLength = arguments.length;\n        // Recognize a common special case where there is only a single text node\n        if (argsLength === childIndex + 1) {\n            var onlyChild = arguments[childIndex];\n            if (typeof onlyChild === 'string') {\n                text = onlyChild;\n            } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {\n                text = onlyChild[0];\n            }\n        }\n        if (text === undefined) {\n            children = [];\n            for (; childIndex < arguments.length; childIndex++) {\n                var child = arguments[childIndex];\n                if (child === null || child === undefined) {\n                    continue;\n                } else if (Array.isArray(child)) {\n                    appendChildren(selector, child, children);\n                } else if (child.hasOwnProperty('vnodeSelector')) {\n                    children.push(child);\n                } else {\n                    children.push(toTextVNode(child));\n                }\n            }\n        }\n        return {\n            vnodeSelector: selector,\n            properties: properties,\n            children: children,\n            text: text === '' ? undefined : text,\n            domNode: null\n        };\n    };\n    /**\n * Contains simple low-level utility functions to manipulate the real DOM.\n */\n    exports.dom = {\n        /**\n     * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in\n     * its [[Projection.domNode|domNode]] property.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection.\n     * @returns The [[Projection]] which also contains the DOM Node that was created.\n     */\n        create: function (vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, document.createElement('div'), undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Appends a new childnode to the DOM which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param parentNode - The parent node for the new childNode.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the [[Projection]].\n     * @returns The [[Projection]] that was created.\n     */\n        append: function (parentNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, parentNode, undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Inserts a new DOM node which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param beforeNode - The node that the DOM Node is inserted before.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.\n     * NOTE: [[VNode]] objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        insertBefore: function (beforeNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.\n     * This means that the virtual DOM and the real DOM will have one overlapping element.\n     * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects\n     * may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        merge: function (element, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            vnode.domNode = element;\n            initPropertiesAndChildren(element, vnode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        }\n    };\n    /**\n * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.\n * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.\n * For more information, see [[CalculationCache]].\n *\n * @param <Result> The type of the value that is cached.\n */\n    exports.createCache = function () {\n        var cachedInputs = undefined;\n        var cachedOutcome = undefined;\n        var result = {\n            invalidate: function () {\n                cachedOutcome = undefined;\n                cachedInputs = undefined;\n            },\n            result: function (inputs, calculation) {\n                if (cachedInputs) {\n                    for (var i = 0; i < inputs.length; i++) {\n                        if (cachedInputs[i] !== inputs[i]) {\n                            cachedOutcome = undefined;\n                        }\n                    }\n                }\n                if (!cachedOutcome) {\n                    cachedOutcome = calculation();\n                    cachedInputs = inputs;\n                }\n                return cachedOutcome;\n            }\n        };\n        return result;\n    };\n    /**\n * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.\n * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.\n *\n * @param <Source>       The type of source items. A database-record for instance.\n * @param <Target>       The type of target items. A [[Component]] for instance.\n * @param getSourceKey   `function(source)` that must return a key to identify each source object. The result must either be a string or a number.\n * @param createResult   `function(source, index)` that must create a new result object from a given source. This function is identical\n *                       to the `callback` argument in `Array.map(callback)`.\n * @param updateResult   `function(source, target, index)` that updates a result to an updated source.\n */\n    exports.createMapping = function (getSourceKey, createResult, updateResult) {\n        var keys = [];\n        var results = [];\n        return {\n            results: results,\n            map: function (newSources) {\n                var newKeys = newSources.map(getSourceKey);\n                var oldTargets = results.slice();\n                var oldIndex = 0;\n                for (var i = 0; i < newSources.length; i++) {\n                    var source = newSources[i];\n                    var sourceKey = newKeys[i];\n                    if (sourceKey === keys[oldIndex]) {\n                        results[i] = oldTargets[oldIndex];\n                        updateResult(source, oldTargets[oldIndex], i);\n                        oldIndex++;\n                    } else {\n                        var found = false;\n                        for (var j = 1; j < keys.length; j++) {\n                            var searchIndex = (oldIndex + j) % keys.length;\n                            if (keys[searchIndex] === sourceKey) {\n                                results[i] = oldTargets[searchIndex];\n                                updateResult(newSources[i], oldTargets[searchIndex], i);\n                                oldIndex = searchIndex + 1;\n                                found = true;\n                                break;\n                            }\n                        }\n                        if (!found) {\n                            results[i] = createResult(source, i);\n                        }\n                    }\n                }\n                results.length = newSources.length;\n                keys = newKeys;\n            }\n        };\n    };\n    /**\n * Creates a [[Projector]] instance using the provided projectionOptions.\n *\n * For more information, see [[Projector]].\n *\n * @param projectionOptions   Options that influence how the DOM is rendered and updated.\n */\n    exports.createProjector = function (projectorOptions) {\n        var projector;\n        var projectionOptions = applyDefaultProjectionOptions(projectorOptions);\n        projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {\n            return function () {\n                // intercept function calls (event handlers) to do a render afterwards.\n                projector.scheduleRender();\n                return eventHandler.apply(properties.bind || this, arguments);\n            };\n        };\n        var renderCompleted = true;\n        var scheduled;\n        var stopped = false;\n        var projections = [];\n        var renderFunctions = [];\n        // matches the projections array\n        var doRender = function () {\n            scheduled = undefined;\n            if (!renderCompleted) {\n                return;    // The last render threw an error, it should be logged in the browser console.\n            }\n            renderCompleted = false;\n            for (var i = 0; i < projections.length; i++) {\n                var updatedVnode = renderFunctions[i]();\n                projections[i].update(updatedVnode);\n            }\n            renderCompleted = true;\n        };\n        projector = {\n            scheduleRender: function () {\n                if (!scheduled && !stopped) {\n                    scheduled = requestAnimationFrame(doRender);\n                }\n            },\n            stop: function () {\n                if (scheduled) {\n                    cancelAnimationFrame(scheduled);\n                    scheduled = undefined;\n                }\n                stopped = true;\n            },\n            resume: function () {\n                stopped = false;\n                renderCompleted = true;\n                projector.scheduleRender();\n            },\n            append: function (parentNode, renderMaquetteFunction) {\n                projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            insertBefore: function (beforeNode, renderMaquetteFunction) {\n                projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            merge: function (domNode, renderMaquetteFunction) {\n                projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            replace: function (domNode, renderMaquetteFunction) {\n                var vnode = renderMaquetteFunction();\n                createDom(vnode, domNode.parentNode, domNode, projectionOptions);\n                domNode.parentNode.removeChild(domNode);\n                projections.push(createProjection(vnode, projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            detach: function (renderMaquetteFunction) {\n                for (var i = 0; i < renderFunctions.length; i++) {\n                    if (renderFunctions[i] === renderMaquetteFunction) {\n                        renderFunctions.splice(i, 1);\n                        return projections.splice(i, 1)[0];\n                    }\n                }\n                throw new Error('renderMaquetteFunction was not found');\n            }\n        };\n        return projector;\n    };\n}));\n"
  },
  {
    "path": "plugins/UiFileManager/media/list.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>List - ZeroNet</title>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <link rel=\"stylesheet\" href=\"css/all.css?site_modified={rev}\" />\n <base href=\"\" target=\"_top\" id=\"base\">\n <script>base.href = document.location.href.replace(\"/media\", \"\").replace(\"index.html\", \"\").replace(/[&?]wrapper=False/, \"\").replace(/[&?]wrapper_nonce=[A-Za-z0-9]+/, \"\")</script>\n</head>\n<body class=\"{themeclass}\">\n\n<div class=\"content\" id=\"content\"></div>\n\n<script type=\"text/javascript\" src=\"js/all.js?rev={rev}&lang={lang}\"></script>\n</body>\n</html>"
  },
  {
    "path": "plugins/UiPluginManager/UiPluginManagerPlugin.py",
    "content": "import io\nimport os\nimport json\nimport shutil\nimport time\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\nfrom Translate import Translate\nfrom util.Flag import flag\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"_\" not in locals():\n    _ = Translate(plugin_dir + \"/languages/\")\n\n\n# Convert non-str,int,float values to str in a dict\ndef restrictDictValues(input_dict):\n    allowed_types = (int, str, float)\n    return {\n        key: val if type(val) in allowed_types else str(val)\n        for key, val in input_dict.items()\n    }\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def actionWrapper(self, path, extra_headers=None):\n        if path.strip(\"/\") != \"Plugins\":\n            return super(UiRequestPlugin, self).actionWrapper(path, extra_headers)\n\n        if not extra_headers:\n            extra_headers = {}\n\n        script_nonce = self.getScriptNonce()\n\n        self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)\n        site = self.server.site_manager.get(config.homepage)\n        return iter([super(UiRequestPlugin, self).renderWrapper(\n            site, path, \"uimedia/plugins/plugin_manager/plugin_manager.html\",\n            \"Plugin Manager\", extra_headers, show_loadingscreen=False, script_nonce=script_nonce\n        )])\n\n    def actionUiMedia(self, path, *args, **kwargs):\n        if path.startswith(\"/uimedia/plugins/plugin_manager/\"):\n            file_path = path.replace(\"/uimedia/plugins/plugin_manager/\", plugin_dir + \"/media/\")\n            if config.debug and (file_path.endswith(\"all.js\") or file_path.endswith(\"all.css\")):\n                # If debugging merge *.css to all.css and *.js to all.js\n                from Debug import DebugMedia\n                DebugMedia.merge(file_path)\n\n            if file_path.endswith(\"js\"):\n                data = _.translateData(open(file_path).read(), mode=\"js\").encode(\"utf8\")\n            elif file_path.endswith(\"html\"):\n                data = _.translateData(open(file_path).read(), mode=\"html\").encode(\"utf8\")\n            else:\n                data = open(file_path, \"rb\").read()\n\n            return self.actionFile(file_path, file_obj=io.BytesIO(data), file_size=len(data))\n        else:\n            return super(UiRequestPlugin, self).actionUiMedia(path)\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    @flag.admin\n    def actionPluginList(self, to):\n        plugins = []\n        for plugin in PluginManager.plugin_manager.listPlugins(list_disabled=True):\n            plugin_info_path = plugin[\"dir_path\"] + \"/plugin_info.json\"\n            plugin_info = {}\n            if os.path.isfile(plugin_info_path):\n                try:\n                    plugin_info = json.load(open(plugin_info_path))\n                except Exception as err:\n                    self.log.error(\n                        \"Error loading plugin info for %s: %s\" %\n                        (plugin[\"name\"], Debug.formatException(err))\n                    )\n            if plugin_info:\n                plugin_info = restrictDictValues(plugin_info)  # For security reasons don't allow complex values\n                plugin[\"info\"] = plugin_info\n\n            if plugin[\"source\"] != \"builtin\":\n                plugin_site = self.server.sites.get(plugin[\"source\"])\n                if plugin_site:\n                    try:\n                        plugin_site_info = plugin_site.storage.loadJson(plugin[\"inner_path\"] + \"/plugin_info.json\")\n                        plugin_site_info = restrictDictValues(plugin_site_info)\n                        plugin[\"site_info\"] = plugin_site_info\n                        plugin[\"site_title\"] = plugin_site.content_manager.contents[\"content.json\"].get(\"title\")\n                        plugin_key = \"%s/%s\" % (plugin[\"source\"], plugin[\"inner_path\"])\n                        plugin[\"updated\"] = plugin_key in PluginManager.plugin_manager.plugins_updated\n                    except Exception:\n                        pass\n\n            plugins.append(plugin)\n\n        return {\"plugins\": plugins}\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionPluginConfigSet(self, to, source, inner_path, key, value):\n        plugin_manager = PluginManager.plugin_manager\n        plugins = plugin_manager.listPlugins(list_disabled=True)\n        plugin = None\n        for item in plugins:\n            if item[\"source\"] == source and item[\"inner_path\"] in (inner_path, \"disabled-\" + inner_path):\n                plugin = item\n                break\n\n        if not plugin:\n            return {\"error\": \"Plugin not found\"}\n\n        config_source = plugin_manager.config.setdefault(source, {})\n        config_plugin = config_source.setdefault(inner_path, {})\n\n        if key in config_plugin and value is None:\n            del config_plugin[key]\n        else:\n            config_plugin[key] = value\n\n        plugin_manager.saveConfig()\n\n        return \"ok\"\n\n    def pluginAction(self, action, address, inner_path):\n        site = self.server.sites.get(address)\n        plugin_manager = PluginManager.plugin_manager\n\n        # Install/update path should exists\n        if action in (\"add\", \"update\", \"add_request\"):\n            if not site:\n                raise Exception(\"Site not found\")\n\n            if not site.storage.isDir(inner_path):\n                raise Exception(\"Directory not found on the site\")\n\n            try:\n                plugin_info = site.storage.loadJson(inner_path + \"/plugin_info.json\")\n                plugin_data = (plugin_info[\"rev\"], plugin_info[\"description\"], plugin_info[\"name\"])\n            except Exception as err:\n                raise Exception(\"Invalid plugin_info.json: %s\" % Debug.formatExceptionMessage(err))\n\n            source_path = site.storage.getPath(inner_path)\n\n        target_path = plugin_manager.path_installed_plugins + \"/\" + address + \"/\" + inner_path\n        plugin_config = plugin_manager.config.setdefault(site.address, {}).setdefault(inner_path, {})\n\n        # Make sure plugin (not)installed\n        if action in (\"add\", \"add_request\") and os.path.isdir(target_path):\n            raise Exception(\"Plugin already installed\")\n\n        if action in (\"update\", \"remove\") and not os.path.isdir(target_path):\n            raise Exception(\"Plugin not installed\")\n\n        # Do actions\n        if action == \"add\":\n            shutil.copytree(source_path, target_path)\n\n            plugin_config[\"date_added\"] = int(time.time())\n            plugin_config[\"rev\"] = plugin_info[\"rev\"]\n            plugin_config[\"enabled\"] = True\n\n        if action == \"update\":\n            shutil.rmtree(target_path)\n\n            shutil.copytree(source_path, target_path)\n\n            plugin_config[\"rev\"] = plugin_info[\"rev\"]\n            plugin_config[\"date_updated\"] = time.time()\n\n        if action == \"remove\":\n            del plugin_manager.config[address][inner_path]\n            shutil.rmtree(target_path)\n\n    def doPluginAdd(self, to, inner_path, res):\n        if not res:\n            return None\n\n        self.pluginAction(\"add\", self.site.address, inner_path)\n        PluginManager.plugin_manager.saveConfig()\n\n        self.cmd(\n            \"confirm\",\n            [\"Plugin installed!<br>You have to restart the client to load the plugin\", \"Restart\"],\n            lambda res: self.actionServerShutdown(to, restart=True)\n        )\n\n        self.response(to, \"ok\")\n\n    @flag.no_multiuser\n    def actionPluginAddRequest(self, to, inner_path):\n        self.pluginAction(\"add_request\", self.site.address, inner_path)\n        plugin_info = self.site.storage.loadJson(inner_path + \"/plugin_info.json\")\n        warning = \"<b>Warning!<br/>Plugins has the same permissions as the ZeroNet client.<br/>\"\n        warning += \"Do not install it if you don't trust the developer.</b>\"\n\n        self.cmd(\n            \"confirm\",\n            [\"Install new plugin: %s?<br>%s\" % (plugin_info[\"name\"], warning), \"Trust & Install\"],\n            lambda res: self.doPluginAdd(to, inner_path, res)\n        )\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionPluginRemove(self, to, address, inner_path):\n        self.pluginAction(\"remove\", address, inner_path)\n        PluginManager.plugin_manager.saveConfig()\n        return \"ok\"\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionPluginUpdate(self, to, address, inner_path):\n        self.pluginAction(\"update\", address, inner_path)\n        PluginManager.plugin_manager.saveConfig()\n        PluginManager.plugin_manager.plugins_updated[\"%s/%s\" % (address, inner_path)] = True\n        return \"ok\"\n"
  },
  {
    "path": "plugins/UiPluginManager/__init__.py",
    "content": "from . import UiPluginManagerPlugin\n"
  },
  {
    "path": "plugins/UiPluginManager/media/css/PluginManager.css",
    "content": "body { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; backface-visibility: hidden; }\nh1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }\nh1 { background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }\nh2 { margin-top: 10px; }\nh3 { font-weight: normal }\nh4 { font-size: 19px; font-weight: lighter; margin-right: 100px; margin-top: 30px; }\na { color: #9760F9 }\na:hover { text-decoration: none }\n\n.link { background-color: transparent; outline: 5px solid transparent; transition: all 0.3s }\n.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; transition: none }\n\n.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; box-sizing: border-box; padding-bottom: 150px; }\n.section { margin: 0px 10%; }\n.plugins { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }\n.plugin { transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: relative; padding-bottom: 20px; padding-top: 10px; }\n.plugin.hidden { opacity: 0; height: 0px; padding: 0px; }\n.plugin .title { display: inline-block; line-height: 36px; }\n.plugin .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }\n.plugin .title .version { font-size: 70%; margin-left: 5px; }\n.plugin .title .version .version-latest { color: #2ecc71; font-weight: normal; }\n.plugin .title .version .version-missing { color: #ffa200; font-weight: normal; }\n.plugin .title .version .version-update { padding: 0px 15px; margin-left: 5px; line-height: 28px; }\n.plugin .description { font-size: 14px; color: #666; line-height: 24px; }\n.plugin .description .source { color: #999; font-size: 90%; }\n.plugin .description .source a { color: #666; }\n.plugin .value { display: inline-block;  white-space: nowrap; }\n.plugin .value-right { right: 0px; position: absolute; }\n.plugin .value-fullwidth { width: 100% }\n.plugin .marker {\n\tfont-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;\n\topacity: 0; pointer-events: none; transition: all 0.6s; transform: scale(2); color: #9760F9;\n}\n.plugin .marker.visible { opacity: 1; pointer-events: all; transform: scale(1); }\n.plugin .marker.changed { color: #2ecc71; }\n.plugin .marker.pending { color: #ffa200; }\n\n\n.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; border-radius: 3px; font-size: 17px; box-sizing: border-box; }\n.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }\n.input-textarea {  overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }\n\n.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }\n\n.value-right .input-text { text-align: right; width: 100px; }\n.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }\n.value-fullwidth { margin-top: 10px; }\n\n/* Checkbox */\n.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; border-radius: 15px; transition: all 0.3s ease-in-out; display: inline-block; }\n.checkbox-skin:before {\n\tcontent: \"\"; position: relative; width: 20px; background-color: white; height: 20px; display: block; border-radius: 100%; margin-top: 2px; margin-left: 2px;\n\ttransition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }\n.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }\n.checkbox.checked .checkbox-skin:before { margin-left: 27px; }\n.checkbox.checked .checkbox-skin { background-color: #2ECC71 }\n\n/* Bottom */\n\n.bottom {\n\twidth: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);\n\ttransition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: fixed; backface-visibility: hidden; box-sizing: border-box;\n}\n.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }\n.bottom .button { float: right; }\n.bottom.visible { bottom: 0px; box-shadow: 0px 0px 35px #dcdcdc; }\n.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }\n.bottom .title:before { content: \"•\"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }\n.bottom-restart .title:before { color: #ffa200; }\n\n.animate { transition: all 0.3s ease-out !important; }\n.animate-back { transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; }\n.animate-inout { transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; }"
  },
  {
    "path": "plugins/UiPluginManager/media/css/all.css",
    "content": "\n/* ---- PluginManager.css ---- */\n\n\nbody { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; }\nh1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }\nh1 { background: -webkit-linear-gradient(33deg,#af3bff,#0d99c9);background: -moz-linear-gradient(33deg,#af3bff,#0d99c9);background: -o-linear-gradient(33deg,#af3bff,#0d99c9);background: -ms-linear-gradient(33deg,#af3bff,#0d99c9);background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }\nh2 { margin-top: 10px; }\nh3 { font-weight: normal }\nh4 { font-size: 19px; font-weight: lighter; margin-right: 100px; margin-top: 30px; }\na { color: #9760F9 }\na:hover { text-decoration: none }\n\n.link { background-color: transparent; outline: 5px solid transparent; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n\n.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; padding-bottom: 150px; }\n.section { margin: 0px 10%; }\n.plugins { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }\n.plugin { -webkit-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1) ; position: relative; padding-bottom: 20px; padding-top: 10px; }\n.plugin.hidden { opacity: 0; height: 0px; padding: 0px; }\n.plugin .title { display: inline-block; line-height: 36px; }\n.plugin .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }\n.plugin .title .version { font-size: 70%; margin-left: 5px; }\n.plugin .title .version .version-latest { color: #2ecc71; font-weight: normal; }\n.plugin .title .version .version-missing { color: #ffa200; font-weight: normal; }\n.plugin .title .version .version-update { padding: 0px 15px; margin-left: 5px; line-height: 28px; }\n.plugin .description { font-size: 14px; color: #666; line-height: 24px; }\n.plugin .description .source { color: #999; font-size: 90%; }\n.plugin .description .source a { color: #666; }\n.plugin .value { display: inline-block;  white-space: nowrap; }\n.plugin .value-right { right: 0px; position: absolute; }\n.plugin .value-fullwidth { width: 100% }\n.plugin .marker {\n\tfont-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;\n\topacity: 0; pointer-events: none; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ; -webkit-transform: scale(2); -moz-transform: scale(2); -o-transform: scale(2); -ms-transform: scale(2); transform: scale(2) ; color: #9760F9;\n}\n.plugin .marker.visible { opacity: 1; pointer-events: all; -webkit-transform: scale(1); -moz-transform: scale(1); -o-transform: scale(1); -ms-transform: scale(1); transform: scale(1) ; }\n.plugin .marker.changed { color: #2ecc71; }\n.plugin .marker.pending { color: #ffa200; }\n\n\n.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ; font-size: 17px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; }\n.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }\n.input-textarea {  overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }\n\n.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }\n\n.value-right .input-text { text-align: right; width: 100px; }\n.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }\n.value-fullwidth { margin-top: 10px; }\n\n/* Checkbox */\n.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; -ms-border-radius: 15px; border-radius: 15px ; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; -ms-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out ; display: inline-block; }\n.checkbox-skin:before {\n\tcontent: \"\"; position: relative; width: 20px; background-color: white; height: 20px; display: block; -webkit-border-radius: 100%; -moz-border-radius: 100%; -o-border-radius: 100%; -ms-border-radius: 100%; border-radius: 100% ; margin-top: 2px; margin-left: 2px;\n\t-webkit-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -moz-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -o-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -ms-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86) ;\n}\n.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }\n.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }\n.checkbox.checked .checkbox-skin:before { margin-left: 27px; }\n.checkbox.checked .checkbox-skin { background-color: #2ECC71 }\n\n/* Bottom */\n\n.bottom {\n\twidth: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);\n\t-webkit-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1) ; position: fixed; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;\n}\n.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }\n.bottom .button { float: right; }\n.bottom.visible { bottom: 0px; -webkit-box-shadow: 0px 0px 35px #dcdcdc; -moz-box-shadow: 0px 0px 35px #dcdcdc; -o-box-shadow: 0px 0px 35px #dcdcdc; -ms-box-shadow: 0px 0px 35px #dcdcdc; box-shadow: 0px 0px 35px #dcdcdc ; }\n.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }\n.bottom .title:before { content: \"•\"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }\n.bottom-restart .title:before { color: #ffa200; }\n\n.animate { -webkit-transition: all 0.3s ease-out !important; -moz-transition: all 0.3s ease-out !important; -o-transition: all 0.3s ease-out !important; -ms-transition: all 0.3s ease-out !important; transition: all 0.3s ease-out !important ; }\n.animate-back { -webkit-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -moz-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -o-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -ms-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important ; }\n.animate-inout { -webkit-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -moz-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -o-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -ms-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important ; }\n\n/* ---- button.css ---- */\n\n\n/* Button */\n.button {\n\tbackground-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;\n\t-webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; border-bottom: 2px solid #E8BE29; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; text-decoration: none;\n}\n.button:hover { border-color: white; border-bottom: 2px solid #BD960C; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  ; background-color: #FDEB07 }\n.button:active { position: relative; top: 1px }\n.button.loading {\n\tcolor: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;\n\t-webkit-transition: all 0.5s ease-out  ; -moz-transition: all 0.5s ease-out  ; -o-transition: all 0.5s ease-out  ; -ms-transition: all 0.5s ease-out  ; transition: all 0.5s ease-out   ; pointer-events: none; border-bottom: 2px solid #666\n}\n.button.disabled { color: #DDD; background-color: #999;  pointer-events: none; border-bottom: 2px solid #666 }\n\n/* ---- fonts.css ---- */\n\n\n/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */\n/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */\n\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 400;\n    src:\n        local('Roboto'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: bold;\n  src:\n  \tlocal('Roboto Medium'),\n  \turl(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'),\n}\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 200;\n    src:\n        local('Roboto Light'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}"
  },
  {
    "path": "plugins/UiPluginManager/media/css/button.css",
    "content": "/* Button */\n.button {\n\tbackground-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;\n\tborder-radius: 2px; border-bottom: 2px solid #E8BE29; transition: all 0.5s ease-out; text-decoration: none;\n}\n.button:hover { border-color: white; border-bottom: 2px solid #BD960C; transition: none ; background-color: #FDEB07 }\n.button:active { position: relative; top: 1px }\n.button.loading {\n\tcolor: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;\n\ttransition: all 0.5s ease-out  ; pointer-events: none; border-bottom: 2px solid #666\n}\n.button.disabled { color: #DDD; background-color: #999;  pointer-events: none; border-bottom: 2px solid #666 }"
  },
  {
    "path": "plugins/UiPluginManager/media/css/fonts.css",
    "content": "/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */\n/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */\n\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 400;\n    src:\n        local('Roboto'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: bold;\n  src:\n  \tlocal('Roboto Medium'),\n  \turl(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAEbcABAAAAAAfQwAAQABAAAAAAAAAAAAAAAAAAAAAAAAAABHUE9TAAABbAAABOQAAAv2MtQEeUdTVUIAAAZQAAAAQQAAAFCyIrRQT1MvMgAABpQAAABXAAAAYLorAUBjbWFwAAAG7AAAAI8AAADEj/6wZGN2dCAAAAd8AAAAMAAAADAX3wLxZnBnbQAAB6wAAAE/AAABvC/mTqtnYXNwAAAI7AAAAAwAAAAMAAgAE2dseWYAAAj4AAA2eQAAYlxNsqlBaGVhZAAAP3QAAAA0AAAANve2KKdoaGVhAAA/qAAAAB8AAAAkDRcHFmhtdHgAAD/IAAACPAAAA3CPSUvWbG9jYQAAQgQAAAG6AAABusPVqwRtYXhwAABDwAAAACAAAAAgAwkC3m5hbWUAAEPgAAAAtAAAAU4XNjG1cG9zdAAARJQAAAF3AAACF7VLITZwcmVwAABGDAAAAM8AAAEuQJ9pDngBpJUDrCVbE0ZX9znX1ti2bdu2bU/w89nm1di2bdu2jXjqfWO7V1ajUru2Otk4QCD5qIRbqUqtRoT2aj+oDynwApjhwNN34fbsPKAPobrrDjggvbggAz21cOiHFyjoKeIpwkH3sHvRve4pxWVnojPdve7MdZY7e53zrq+bzL3r5nDzuTXcfm6iJ587Wa5U/lMuekp5hHv9Ge568okijyiFQ0F8CCSITGQhK9nITh7yUkDxQhSmKMUpQSlKU4bq1KExzWlBK9rwCZ/yGZ/zBV/yNd/wLd/xM7/yG7/zB3+SyFKWs4GNbGYLh/BSnBhKkI5SJCVR5iXs3j4iZGqZyX6nKNFUsq1UsSNUldVkDdnADtNIz8Z2mmZ2geZ2llbyE7X5VH4mP5dfyC/lCNUYKUfJ0XKMHCvHq8YEOVFOkpPlLNWeLefIuXKeXKg+FsnFcolcqr6Wy1XK36SxbpUOLWzxg/tsXJoSxlcWgw9FlVPcTlLCLlHKtpAovYruU/SyIptJlH6ay0K13Upva8e/rYNal2OcjWGB/Y2XYGIoR6SyjtOOaBQhXJEQRS4qEvag51P4ktuuUEzGyjgZLxNkAD4kI1AGk1Ets6lVSjaQjI1ys9wig6iicVaV1WQN2UiOlxPkRDlJTparpIfqRNGUGFpIH8IsgQiZWm6SW6VGpMxiMlbGyXiZID1ksBk0tasa+REcgrWbjua9k1ACbC+aMyG2RGONorqd1Ey3KvsMmr9WKUGrtEHZP2iV5miVZrPN5uFQXa21FgShu/bK9V7HCz4/+M4nBcnA9ltfW25z7ZKNs3G89bp3io+47JSdtbHvkX+Ct+dcfK7+Bdtpf+h+/o1trsvLQPQzsat2+pW5F3jvS5U0lhdi522PtbA9L6zn5efGkM/y3LsGAHbD/g22Tyv213N1GtoduwmSRzWG2go7BIS/cix/ameH20SbZFOJQFgyAFto4y3STgLhds2m2LIn+dtsB9i2JxWyA9hJ9fuNXeLF+uvtiB0DCWES6wxgl+WMN6zPWQDCnu6j/sUmGs+LuV1spo2wdRZrE4gkiiiLfNTvJRtgJ9RHpMZ/WqP4FIBQVAv5Qp3L2hFe3GM7/qa/5BWxg2/Iv/NsW7UG7Bzvdb0p326+Inb0PesfeLf56q+7BkDEK/LaAQBJXldHI9X96Q6+dVSX3m8mGhvy7ZdDbXSCE0YEqcn86BTP/eQUL0oxdIZTEp3iVKIyVahGTepRnwY0RCc6LWlF61ee4rHEEU8CiYxgJKMYzRjGMp4JTGQSk5nJLGYzh7nMYynLHp34m9CZz1YO4ZKfMOEQIRxSC4fMwiWL8JBVeMkmfMgtfMkj/Mgr/CkgvBQUARQVgRQTvhQXQZQQwZQUIZQSoZQWYVQS4VQWEVQRkVQTUdQU0WjmujcQMTQUETQWSWguktJSJKOVSEprkZyvhYdv+A4ffhZefuVP3WPRaUeiCGUEYwlnvIhkApOJYqaIZhbziGGpSMoyEcFykZRNwmGrcDgkfHDkP4WQhQ3EQBDE9pmZ+m/pK4ovGh2DLW8Y/0wRrZ3sTlWy/Ut6kPnlj7St3vzVJ3/zxZ878t9iVrSeNZdng1ty+3Z0tRvzw/zamDuNWXr9V2Q8vEZPedSbe/UNmH3D1uu4Sr5k7uHPvuMCT5oZE7a0fYJ4AWNgZGBg4GKQY9BhYHRx8wlh4GBgYQCC///BMow5memJQDEGCA8oxwKmOYBYCESDxa4xMDH4MDACoScANIcG1QAAAHgBY2BmWcj4hYGVgYF1FqsxAwOjPIRmvsiQxsTAwADEUPCAgel9AINCNJCpAOK75+enAyne/385kv5eZWDgSGLSVmBgnO/PyMDAYsW6gUEBCJkA3C8QGAB4AWNgYGACYmYgFgGSjGCahWEDkNZgUACyOBh4GeoYTjCcZPjPaMgYzHSM6RbTHQURBSkFOQUlBSsFF4UShTVKQv//A3XwAnUsAKo8BVQZBFUprCChIANUaYlQ+f/r/8f/DzEI/T/4f8L/gr///r7+++rBlgcbH2x4sPbB9Ad9D+IfaNw7DHQLkQAAN6c0ewAAKgDDAJIAmACHAGgAjACqAAAAFf5gABUEOgAVBbAAFQSNABADIQALBhgAFQAAAAB4AV2OBc4bMRCF7f4UlCoohmyFE1sRQ0WB3ZTbcDxlJlEPUOaGzvJWuBHmODlEaaFsGJ5PD0ydR7RnHM5X5PLv7/Eu40R3bt7Q4EoI+7EFfkvjkAKvSY0dJbrYKXYHJk9iJmZn781EVzy6fQ+7xcB7jfszagiwoXns2ZGRaFLqd3if6JTGro/ZDTAz8gBPAkDgg1Ljq8aeOi+wU+qZvsErK4WmRSkphY1Nz2BjpSSRxv5vjZ5//vh4qPZAYb+mEQkJQ4NmCoxmszDLS7yazVKzPP3ON//mLmf/F5p/F7BTtF3+qhd0XuVlyi/kZV56CsnSiKrzQ2N7EiVpxBSO2hpxhWOeSyinzD+J2dCsm2yX3XUj7NPIrNnRne1TSiHvwcUn9zD7XSMPkVRofnIFu2KcY8xKrdmxna1F+gexEIitAAABAAIACAAC//8AD3gBfFcFfBu5sx5pyWkuyW5iO0md15yzzboUqilQZmZmTCllZpcZjvnKTGs3x8x851duj5mZIcob2fGL3T/499uJZyWP5ht9+kYBCncDkB2SCQIoUAImdB5m0iJHkKa2GR5xRHRECzqy2aD5sCuOd4aHiEy19DKTFBWXEF1za7rXTXb8jB/ytfDCX/2+AsC4HcRUOkRuCCIkQUE0roChBGtdXAs6Fu4IqkljoU0ljDEVDBo1WZVzLpE2aCTlT3oD+xYNj90KQLwTc3ZALmyMxk7BcCmYcz0AzDmUnBLJNLmoum1y32Q6OqTQZP5CKQqKAl/UecXxy3CThM1kNWipf4OumRo2U1RTDZupqpkeNi2qmRs2bWFTUc2csGkPm0Q1s8MmVU0HT1oX9Azd64w8bsHNH5seedBm6PTEh72O9PqcSOU/E63PkT4f9DnaJ/xd+bt/9zqy+MPyD8ndrJLcfT8p20P2snH82cNeup9V0lJSBvghMLm2QDTke6AFTIsiTkKQSTHEeejkccTZeUkcYLYaFEg9nCTVvCHMrcptMCNuKI/j4tbFbbBZ/RCC8hguw/B6fH6v22a323SPoefJNqs9Ex2rrNh0r2H4/W6r3d3SJ7hnrz1//tVTe08889OcCZWVM7adf/Pcg3vOfi7Sb7ZNnb2MrBg8p7Dba2cOX7Jee6fhjy+tvHnmqCFVJb1ePn3qzYznns1497K0c1kVAEgwqfZraYv0AqSAA5qCHypgEZilRWZ5UT2PYsgNdAxLlEcNYjwKajQGgw8Es+JcAwHH5qETLIgby1WDHhpXgAyPz93SbkOsep7hjeL0eqNVIP9lTHKRzEmHdu0+dGjn7sPHunfq0LV7h47daMbhnXWvenbo0ql7x47dmLCSvrRSvDNw6uSa3oETJwLthg9r37v9iBHt/3lj9amTgT5rTpwMtBsxtGOfdiNGtPujmzivGwjQpvZr8WesjxPZUAYhMK1F/0qJXHRyLXWOAx0H50dxboQfxapphKtHGVUGHf1gc6PC6GkIo0NCsYGDIdUo5n9yHFb8Uz0qpyqHT8qpyOmZI4w2c1RTC1d7tc4anqdBGhkdmshNVo7GA2MF8+opFMrXcvAt55yfJNbVj8SKVhCJpBCfz+vGL5mK0yVjQRtLLX1+osicbALyzY/jkdK22by5e7c3z+x5acqYSaSkScEL3Xs8T9l3/Qc8NvUqY+SjNsv87OFG3YpXpZYUzytzDe7coy/ZsiQ4Yuzd/U688NSmCXd17sZub3v7oC2fjfhCGltW8VnjxjpZZy+dWjwpIJwormzTK79/iW/wBAAgqGEiyZKzQISGiQpWr1h4SISYUkm57FNqBQIBVkr3y8NAQ+3D36A4IWQV/JmZqJw2NT1T0Q3QAqTsQblg41NPbiqQH2Iv035kK206mGysZG3YMSs7xtrMDAyhTcjWSC4axqy4LiZRQdFdvnTNq1KX320HjVawZx6SCzc8/UKgUH6QtKPt2PKac4MDleRlMsxKBpFXpq4ZVBNmKyIxHbSvMAF1NBWyAQPW6z3nEIpfMhe2fL8kuIX8TClDEQQX6cwueUmTlNNpRPey/31uR/D0LuH14ccWkqFs//wTw9hv00gu+7IyEr8T3Cw2Ex+EZHAAktOEiPrIJO5s8hWcNqema06vU3PT02QFW/8NW0tWfSM432N9SfA9chuP5WOfkxnwHUgggyki+HwUXGw8M+65u8v3uexl0v7FyJpdaRIdRN8AAdJ5nYKQIGi4CB1U8zNNoUnPR3X1LjTb4EsQYnsMWACwJO6xk7e4bT/99GX0N7R2ndAo0jMzAOfHN02cnKkT94fv09bvr5QLAD8UpuJ51ev0rCK6SgOc3gCn19OKL9lADWokUbkS0ldBzwNNU8HdEjRXVGu0qPKIei288y5jBN59h9Cfl8yfv3jp/PmLaAn7hF0izUgO6U0cpAW7wD7NP3vy5Fk2o/rUyQeieM4C0DcRjwS+aHYSJiRhdokFkVRTjNUkvr1gffj25dM3f2ZXqEN85awnGncAgOhB3A1hQDSuhqG06+MGs+MEg0I21x4BImqiqcGk+kF0sY1xoc8M45pOL4mpgk13GVCnJSTTKXr+KSPXFgybNz6w4msqEctn537ZcSt7XKC7j1Bp9YE+E9bvXiU/S5K+eGzlJwfYcRkI9MM9smOuzWDV/+9pGmaYlnq9hLYFMjf0Fje13Izl5ntACdyDxkxTg0pcymnYlcImJDTWkK0ZcHQO3nrRBvWETcbdrEfVuA6VHa2IuhjrtnyGTjYeWzR1zsyJK7+iMpFevcjmTVuxkH176VX2rUy/Wls1d+3ilceELgtnTJs/d5R85OMrL40+Xdyiev7Ln15+Uh6/ZNmc5Qsj/CwFEIfj/jeANOgFJknoJonXwOrVZBeho02iBmkcTDlsEq4XIUsyjQo+3p84FpvOj7aLuIlTcynCvocf/qlml0xn/1WziWySrVR5nj1BOt4mXPlnKO1Lm0d5sxb3wsB8cmFylDcEVyexVFLRSeV8JAmXnJAllfClLUX8xpYRRhu0x6VoUYM5CS4WP7Qol4xGbc5ACRJ8Pr8v3WalWOW2FIsc2wbl3kECqXmlRfO5Xd/44pfPn2a/S/TjFRPnLl42d9J4O90m5J9jt9zYlFL2x6eX2A/nn5Us0xftWbf+UPvWQGEBYukSOQMu6B+nMDE0VnSsHA0kECeUCrz7ItigIy5ra0J7xQK3tGcqRoQsNh92U8w/JhEZmLktBoMe7bO7rLB0epebg632jH3uY/bP+ffYx6T9mVGBvNsWTF8WkF5wOh7Pcnz4lOJvxb4//z77iJSSLGJH3RhW06N96dRHXn5ww7qD0f3pDCC6cX9ugKIoomQEkXw9VczkxNMLnBCUCoruT0/3oxKL7r/NJmk/p7m+evWfGuE78Vt2lRns9N13kx40+4fnAD8CjMf6NcP6ZYKOq42NrmfDJWy4Xj1P+cEsSLLxkhUklCwkOAq4oqQVOOpuIs64nGxq0JVQz7ij5o27pAixmy+WM/67KC2ZsngH++XyNfbLtqVTF/36ykt/vrFletWG9bNnbDTmjRwzc/aYUbPF4lnHCwofXvLa5cuvLXm4qMWx2c+eP//PkRkbN1TNWrWa/j1u+eJJExcvjpzFAYg3s44vfRL+t0nkS3xjCynWFA5OSSRLynVkyecXVH67ol5PpINovJ8YLr/dnoHXLW8MFxXW7i3ZMSj8I0l96SOSyi5/3XNvxxtbB5aMDNy4dsmE9UtPPfNIx46difLpNfI/7DL7kp1g37C3GjV6NCeL/NStbO2ps2c2bD4CALW10f4qDgYDNPymcCtU8R4uYw/H8WnY1+/HcReOEKGKyJDmBj5OcRwItIUhwnqhFpJw9xFg6CkFlTYXTfVqZdf/tfIcAE0d79/dG2EECYYQQBQCAgoialiVLVpbFypuAUXFWRzUvVBcrQv3nv11zxCpv9pqh6DW0Up3ta4uW6uWCra1So7/3b3wfBfR//rVcsl7+ZL73nffffs7HTFBR5D3WpvCDmUdIQb1I01myQTjoQl2MRpRl/r3hG4oVpCF83Vw+kdwei2j93o4WagRrjD/Nw7YgU6IrsgAfQGRcYCTLxUZur5kPuL/lYuuNgU1XoSa+ueEfPon+J1yrD1J7UCC+5VG3BHBHVHcEcUdlSGKO3nPyzABMdyNFOv48MTEyEXCyPp9KK85NAqGGrz6I7y65gckiwz3dgAI+xivtAIDOA3LqyxbS9V3By2ZYgWxj1KxdrMPUEhIZKJWxzrtdWqXG6lJNABmTO6TO6EgZ/pvgvDn0c+vb5z6WEvxzh24q2xeXq9VAwomDR8q2098/X7JuWGdhg3GY64xvHvgZPkLaR2wgixCI1vHWKJpbdGx3G7mDCO77O7d6Eeg+9T6IJEoXP9qW0dDeSvNbVsrcjvaUN5aC9pa0c2ZWrhMKvyhjOgmkGUyEsFkpRLVKsh0dyc2B5YQICBgIe/NBCIEGNktqHxMBISRCV+50v3qzz2L/GNX5i4ra+5/7cXJK/oKktUtLnpWmZsBf4zfwZ/i9d7NYU+YMLgiIyLr7Gi8AA/zaQ6/hPNgCdx2D3ukdEseEwlhjDkuaOZ8eO9b/PGA3n2za6oggAlxCaLjSGGvi6/CKXAHfhxvwhtxbhtLaVQsrIM2+DLywL6O+mUrO6a7GfRIcPf8hNHZAIBE7VQd8ASDAWfec3ESdiGTC5nSGsiiwiLUtMnjuEOk1kzFcI9JHoR5kz0Y+SwCsXdhGH0VKhzHp/+FzFeRz9+O7fCtL2Q4AL8u2e72RcFosiLP9wIgHmY+hxmEgGJg84/lVDxnGtpH+FMziw5T/GGx/Sx9V+NPbS1/uvSGcm/t5vGnTEK3rUG9y6yEYO1+tfpYOon3TSpILhmHhztfw/bCn2qhobiwdDW+fQN/CjstfKZ4Dj4A9dOWrFx2S7KdOD56V0TLD0s++Qptwe2eLpq+6O1Jo56aACCYSGT3GbIfW4Kuj9KLgIabbN50LDdy1C0P5CSL2U+190OAThfGG/zHkIjP1Tfgj2ByPUSwrYiu7925+a0D27bugj/KF/F1OBh6QhP0gEPxrZ/ljc/fsONrFTee28R4g67DL2Qd3IERJIOHLwGln4cGSUJdTxdyhgDi1AKL4NMYAdkLvyXzDscv4Os/X3r77Nm3JRt+Ef9xEdfgl8Wb97668d7lQzcAZDjMIDh4glxAaHWfDV1JZj/rSS1tOuz1hHmUcIAjHG+MklgeL6F9LCbnn+jtWIJ+rI8SzjpaowWoDFuPSrZKXAiAE5+ZjCY9wHwiifwfvmXsI9wJMhnuBBn3B5CRXWYPc85tcJTWCd84gtBCVOTYSOfNYvNOJnxzgfBNCMgDJG7zSAeR2NXUTWzOuYmcC5VObFq7NxloMKYVZwDIYliIk59EGoTQ8FMi1WHihc7472r8D34dZmIIYUsBXXXbuXHroZP7iteG4MvI91jOCtgbusEO5K+347Q8e+MPb+JPbT/Gt4ZtDjppKBnYmi4D3IJyT8WxGL/UbqKsmPH2vW7kQdLd4LSKMre9bogIAvLe7u0GiyvOul0mNypGuE2h989SwFg6lJAPH3RNyQJYyWiVDLWO6XV1aHWtQn/HIrSI4vwGGfYxf74lFwHn0WS/ZYX76uoIKFu35IbrwlVyYQCxLpa96kTTx3OvJq5zuRfv5Pnw7hyqq8P1Z75rABK6Pm/yyAWS7d6fZ34//7k8f/ry4ka6xjKbeygnyTXR9CbFOhNBTIUiJtZlQleZiHWo4RgPKCvqPoxRivhqEFpQ55fr6lbBkzDE8TtKxt+gmY6VhGRb0QTHkw6dul8oThJo+wjtwodgwulWsMINaHf91LqjZPMpvyPTOJQPmKOhI8f8PFG13EQvVGfduUdgdUUc7AqJkgqDxNrKgaMhs+eobTNFT+700efrUV5FO30KebG5Uc8EWtlONUbCMKgzknfwPPyXDJ+HyXX+Mu77L9xf9q8jy7JPHHm3L/wDzYL3tomF0LEaU3YHPO9P/D/xPpFcNlR9sDfKQ0VIyDvYAkWjZCRQzAmOFb5urd0QeRq30fSlk1sX8kKZEurossFEhcHnyoTDl8u1YiS69x3B9zwSWwMExpGYerP/TAzKwmQIe+FjUFIzXI7/xHfxIdgdStAT9q2tfHHfu+/uf+kjNJB8sB+OIDdl6AFH4n34L3Twt98O4jvvXP/tEFB10nkWhzCCLoBffFVBMRMFCoqJUu7Jo9qcQ5WQhel6UVXuFrihDj12C/rgmlv4Xfj4imeeWYHfRW0c30q2f05/8nfluilTqH6k9PKT+hJ6GYEFpCu4GMj0BlevUyth7YJ7K4qXwVBu5hBhkW1IDMiHUy53QO1z+HbC7IyHkG/FrwOur4fAz/Q/oGEDoWEgCAODHkFDdtGcXDTnCMq5zh4tAL0r8H4kpavGhqLpIBNRJVTz83QOvA09Zkyd91RIxN025kVT8WEYuGH50hX4HMp1PC/ZLpyZ9q+OkeWL52TMDTFb1nadMXVp5dSnJy9Q9tJwohNfko6pURM+HNWSXLSkiJtbsnyG2TXfxfFwS0N5+AN5LeLfk+CaalbRx3ANsgkVK167jf+BYVf/gGESurZtzbKynQeu38YXb/6EX5bQb+9sXLEFzhw+vX3GF6/ZfsL4bXnqqum5OZM7pl96/eA3tz6Xly0pAhAEAyCWMjs8lpcL/M4jdosEtVlJxXhgirkUP1GHnxBHE/PJKN6sVGi0nNDoFpObCZzc5HQCL2Jc1JAPCxfF+1idfOgj3sJVDXfxqbrX12+xS7b6DrXYAcVbQnV9h+07dmwXqum83gBIErOT0h6ti1Svgj5NhjuVyQPgGCjm2X0hcx7M1kRooc4DKgqUA2AuFBx3fnH8AwW4oHC0GH+3L9MPbQCQf2TPuZTjaH4+bo9y+oEPGxL9IFfbfYkSzHAPk61ylpwjE4wKyA1qmgtMS6QQLWHPpkMRHYZTpdFCH61HFGtTIrRCc6KRuj30nxUBCMOOwggIr9bgFy/iizK+cAm/VAOXIklse+9LnYfY9m5f0XTvOnueTgCIvzM9MZCzvDVYu64bu9CRCx3brjqoeDokgUJH8jwTKfoEd3emyyzq/2glwTUEZ8DP8AVcRf5dgafIVSthCwp0tHeEojDHRXQJfU7X1YvgdY3g5QZ6cnhpZn/AMhdEigqdGRClC7oCqqHAaIAYNrITG6pOLWguHAm9sa4We0NvdANV1WdjiPTC83TuIWTuaYynHgfcdA+1JewiQCzqxW0bu7vEwj/M0IinwRkTnIPu3PsFfeeIFu4ePbpNHFi5Qdk/S/FhFCSvBTrQmuaUyJS8Jc8JFaXYgdrxKOiFF/B4uE2q/ueVI7rPld8ykZxQQWNOCMVqtyP5KmUV0w008gZRM18weD0Rhy865yaANFUl8m6WjsuY0hgTKbXQ00qBl16S195pf0QeDCCIR+eEeMWP421XpZaC+eZCZJgOCp/C6Ndg1Ccv6GU9Ooe+cbSFuxMSGC5CQ6awjXnnQZr99YDpJtEo17b6ScLmDz5g3+srHkZm6TgQWX5HiRfY3yJDRTCIBYg47TQ3EguI536ZvstWkibUTqdDOh28yXA/rXTQWwwWY0Uhj6GeaEHmKuxAUC8ehqKsxkeh2AeEgGiwWcE2gGAboOcEjmscwUumaSUSSa34wOusF7ELa7zgtAz3Eq8yr71eb3mJxRXZXiO8iEdB7xAOrvFq8ELFtgBOj9h9A2RmQvMxZC8X7WKJUKJJLHRs5YNnVN+bw2mwVVE5gqeXj9DpX4WvvH3n+yNj8nJG/QZ1dZVHfm3u67iSu9H/o4mz+7XtE9lr3Jvbdr81YuDIvunyouMfVuDgrHnJb+Ym75vQPe1JgMAiQpME2R/4gGAwUKMtfbWiT8+rG16i0GSJiTelgngLhgXJdNQ9YHkGH0Vr6nz8lGBEwsWThZs7+Z+p67Q67/TFuukL+xWFBE/OWVgM/7mJL/fPXi37O17q1oPIn/pXqp/IwJ0zu5dvpTzUj/hQf4p91JiJYsfrtbKdZ0SWuhGqaWbNl47lZtcYt9XsR7Q4IgYJjeapCp5GttOHzr2AJNzwdk1DQ01lnYguzsh/trj4jQnZ8rYLMO5G2HUY/+Nb8tD5J7aEbT9G+S2H0FbgacuI5qslp57XMbyF+N/R1mhgQUdaSBWpROetTo9c8c9zLp0csspad8Y/bkPBiUt1Ty/oPSk09Kke82eiZlCAqd27oJx/fl3eKxuG3thi75IKv03J+uxltleGEtreEbOBH8E9T4O73nV7BAEdZeygWHtZEPGuS4LKSMkHZ1u7BNV0LmSXQgEhNzCTBJTJoqM8wQKmAuEQs4Xmn/pexTXQ+8x31xx5SF41b9TqzD6pp/YPm94MwTcmmGDMjTY3YCLEf18ukxY/3yFmb0IPYV/ZZClgXCmAIAoAdF6OAWYwABCWeJDuRnJhdH0qSmjIJwC9ubggrebyI0KSVbDRzapJptHE5dkXXqi0hT0RE+DbMSg7+8IFYXnFwgNHPT0Oi/KwAQsr6udSGg/APUU3xr/RYAxwRc2F4HpyofdwXgSSi0CKp54PAwby4oU8RZsm2CVRiSCw7A2LuzXFOgN+OFmw0ep/CuOb2f/uEZeyvvfSudZVw078UDdrQZ9JltBJPRfMIVyEYFpOnzX3jn/2U0z4B8Fh02ZMycwi3LT5QGYqPJ+c9flLAAJilot6sg+MVD+rvgO/CzihojXInKuh50RKgiIQw3zY9lR82KkJO/Nf/6hu7Nju08Lr6oQ3ew0494OjCG1eVJwcV/8rmZ7x9ToA4BJywXI2Gq2nd/VxkMEmqbVesraew1m2uISWLYqdoftXAKAGG+4J15Lf9SZPmcFJI43RQ5aP2xlEDvmoczRX56C2taxZHx+WMFn77outO4c08+lkSut+k858b8WBSjf3o5Ju4DBxDkMDQLAYADGF4KGn/K5OzFVO6h8d63FDSqznvw/zwCtFtbWF0Ae2wjuJbXEVnsORsn/9UriHpBTszLZR6c3Hx3ybjo8RkrJ1YvkvIM8geyMcjNY8h15r53Kblhej/DZRLsLIRRgz4vk9E0xtHTPjKLMLX/nyPAbzveL3TZi4LaLT85P/daRuxIg+T/mjuoL8HuNakeVY03vAyJHDxl7+0TEdrVk5dUB3bz8PRxZas2zGY3H1V8XOynMtBED0FPvQvcA9F/covAK7n5yjFyIXDlRR5xHNbRa/v/CVI3WF47pPbU1w25WT98k5xxD04txx6Yn1NQwZRT/FEVx8QBhIcsFGTR5TDerHW7bBfD1eIpnfTJ15HWHaSFrPaCZsm0jj+ZEEIx1RQ0uX/3xt6bJlS3/5ddnSurTUJSXpGRnpi0vS01DkrZ07d+6oNd3eQXzEuj1jRo8es8e0c0xhYeEOhuMiPJLiqNWhbIk5TuCkhwdvrPxP7RPK1+Ym7ZO4S8dz11rrPvGP21jw8eXaBfN7TQwJmdhn/jz4zw18qUuGo046/0yvvrgSO178IrMzNj+W+u/NjL54pFDvxL3/o+S7qvI9XLj4kYir0pyg/hDln7/OGnSsrtMzg5ny7zEuNHR890bl3+fJJXcjkJyaRpX/weQkeCch9auXnXsPvUPw9gbdAC82VEWkd42p6g022CjAKkbAKTSA6g71itCIdMpo5y5DO8d3HxFYd8nQdvEAvwiDMEJMSXQYxM67c/J1EoDUThfOkvkjQZnGItW7xm8EFr+pGCpMEIjZPVNYTl6U6qGKF5sdbEbu6ZsFkRf7oGbEWTA1g9NYcIenqJmL9dhCq+1DQ4kTIoQaQ1Fe09EfZ12Ha/SHJYETrYxp0JWRS46euHr4+DUS+hk7dEju4GVnjt069sVtGf0gLsrNHwsjknoEtd1a+syHlevkrJHZjz2WFRi1femGg9+ulvMHPaHICnPDdbRAygRm0E/jU1M6qIUsetcINl/YRG1cN+6BaXWTL5V4PtRMUfjFrLgcVKv5wDePHu3cwTfCJzB4UPvl2154QcrE/1Q4Xs16TCfbfYy7X0aDKqBOwW8ekR8eYmcmy3iGVrU37zloTa6m9Hq4ExGrEzGqaYVQ666xb1bV5uYNmRVa9+WeQXmXfkMrHLPWFqenCM3uHQcQhAAg/EnwcAddeCnGMS/v4iESE0etEalOtqIslINICfNI5IwrKdEZK7zTXDZ+cw8v+gIvvAcnDxmCztw73ijHwwGQqsmFASzmrAiNNqUXTdsBD5j5Is07sMBWhiedOQvSvINEyw6IL27vRWtW8nRFOsLTQbp2OppBJ7ds0FkqxxAWInU0nW40G61ikvzKNfztiasI/nQCf3vtDfn7cpgEBXjvOPrRw8PRUuzs8IDobwCBBQDhJnkOT1DM8RgnXR8VT3LXeTir9kC1PZy65WPp4EuHAWSgnwjVdCSRpmgZ5h3sIQ+TJ8rMTzdSM0IQ6IjEj6EZvw7z8Y3PPsO/wXzy3hedgE87rjku0speFIbMCu0NuKdQT3A2gWGcVNVUOel5VtNwAhWxRkrug0pIkSz8KEjQdON5kfIBwU7W2GGJNN74i798E3rgjOhdZa26hbTw6qDvkh3QBs+C7tD+FLp9L3TaPr0biTgMSx4lxgBIdBYQqihv8nvkPxKbKiWFSetRqOOa0OPo0b3om6odCn2S8Da0Xk4FrUBbQMtjQCxNiWa70doHMnC1gmadmyKjnVH4eJaHZzLBpInSo4LKF0aMGjXihcoOo/oNGjx4UL9ReFviH6+dHj/dPn3i6ddqEldbXp5/evz+mNj9Y0/Pf9lC8XgT18KBD611htTiG/jSS7hWfl/BuwXBe4YG71axNj+Ctx/FmwxaWW3Xmf0Y3uYEBV+GPlspiq/VFKqg36IgZ2he3tCcgg5HX8wfMyb/xaPfUTwn7GsXvX8SxXN1Ys1rpyeShxh/+rU/EhU8ZsAl4gUhFgSARGAzECSaqly2GfjqJxb7JTdtAXRHKva7oocjFffQaU1csC0bvD4ncUj7lAGvvr5i0Na+CYNikweh37d+mdm9fbtxT/ht+SSra4eooh6Kv1KGV8JSsTPzV6IYFVUxpqc6EFC7nBb1y5oKa01zVSn1UvBKoQrC60puxFNokCJAGJio8cU4ueUaM/GkG5iObmz0uO+xEG2ivTBV0zGQjuUtm4isKF0/LLjCuoL4+MqTQ+deQsIH6z/+6PTpjz7ecVBAlxoDLNLiMy2v/xoMIz8Pq4ZtQq583/KbLVJjoAUS7QjEiSTfEwoKwH0R4JpG0O4m8ih2i8SqZC2x2gwVLZGw0AIbe4CvhX7s62otmglX0S1oJYwXSSgcyRsDZrIvf5FiotBX9REesbHSczvdf608+5OIrhcNHDTKHS5DQ4r7b+t89KhXef7cyt/P3jxnlycULpn5e6Wy3nkNP0vZ4i1WsdoeECXPB1Uj+QLUmAe1Z6QuUik9TYxMdNpbiWa6jZVEoi+xGZvHxxGTF4mpvQ+NKXyn5+I1Kzpak+LXrVnbw1Yw0t5z/dpN1iRr7Kq19bNrXnu1pubV12ompXbJTF267tleB0YVHsreuG59Ykpq0qb1W/v8e0xBec8169G8QxhDdOgdCBqUPRQIgPg+2ft+YKqyJn7kEfy4TGIzrUFJVYm3UYi2Az3d2OQ9DfWSwWZk7Gfk61bkaqYa6VjeTHPfw5k0sJiUf6SlTvkHLegpmAW98dPQF++Go/HuOrwTFpK/YDwNGoQOaJEjofLpyps3yYBOsbV4hsivIqW/ka4F4KuM7FDZezDWLsmAvpNiK7ylYAnRsnCy/ajF+8zPP/+Ma4UW9T8LH6O/AAK5uLW4mvCqldjWs1hni+qb0t80u4c5c5Kp2tywOVWtjHexYe0dwpSuLK5Nyt4ysQO9G0Z788hYHt1kpTJXru5s1yMjTW6KvHkbzgLTyntzAgUXVw/tn9UV1/zyA/6UGLmvzp27evl7tT8P7p/VBRqv/g71JMe5ekHp0rlVt392fBLVJzwxfv7R+MdDElOegSfyVkZ1Wlnw1vFT52U4d/Lo3r2HJWW8++aw1e06rSp45dPLJ+XC5YW9Bw2K63KonUdAM9PAzkOHJxpMnn4DH+tboOyT58WfhDnOtWnFMjCwmppROrVc1VtHDH5E+YHsUon8CXNqa3HQrVviT2fOnKEZi8GkruEHqQq0JPomHsxQ+DSGLEVMI2tayYWV7juLeJ/HYkjht6hR15ZISmox1u4ZaVFaRu0GT5G8KzeKfIWeqFkgkXaTskI9ZvO6+BTO6vtwpV2H9e4ISvKfjeIgJNp27ztyZN/uchFtGjYsv7Awf9hQhzcc/OdtOBi/cvsv/OpcuAe2gZFwDy7A5/G3eBQaIG/d/eVbs974eu9mOX/gymmzn342Z+QyfAdvhROgG9TBcXg7yVknQxvui4/hKtwH2mkfAqoQfFiNWTR4i1Zf30+dUJ4tkWnqhg4hZKCKCFSz9IemXlYvs4phfaz9sp4UZQXrY/WouCJdn61HJJdyRn9Bf0NfrxfzKjz1LfSImI/6gMZ0iforzMmMaFzfDPcPI6ojrkT8EUG+BSIMEWjaQeVamHaQXodECMWEvk1lVCKbzqigkW4egmVKn1mlrzz3bPJjXZ54Acqvrl6+W98Mr7BOav5Mj5zO6KgpNjA2de7EKbOtaZlxsV7yqNK1y/Fx65Co0s5hEzLaR8coteujwAxhlrAJRIDqvy4BHaiGXRsuAQhK4EzhqBAOJNCccm25IPBZQponO/qxY5mQBWdC8TX2W86+NCTTqlwgqnzrCcygE0gGa/jMNl9j4i1y/q5Jw4MB3ibW8BtbUR1wJYDk3FqYvFlzEVmlFiTdZg1oQS+tseX+mm+F+luVNmFbdDWpvKZNSJ1FbVhCw6dGDf8qpR9+TZV+RDZ2JQ12Zdm5WoaGh7fCgK1vpianJeo8drqLWb32lHXN71NQis7xPAtTXHj6DfyW0H9ZSfKw4KCneia1zTQZTP2iErp3XZ6a+ERnpq9WSM2FfCZPDLSLievSpGuS72iLvpGa76Gyp0SwoVXSMUb/ni60d1flz1l3wugfuJ91RySF6U52ByBD08vBtwwrkQRNF1HJzqJJ27dPKtq56sk4a/fu1rgnxXcm7907efKOHZPjuz+ekNCjB5OJIxquCXWSB8HLG3SluoWL4hHF0WQXpV3ycle0l82LU6Z8eyUkI9pFl+IbvAOO/QaG1x8RsoSVJ/AMuOoEXHT3chWl41NoJ/pKOgECwRjXrgKVMm8B2ssAYLGS1Z1C34XQevFAzV5H1do2A/SQTj6CFWyqy4CkjtBXjv2wY0Yba0JqxttIfn39qp0FsxcjmI92rocg4fG27ZJSOsjj1pfO6DdzwmQZQDAKlaHrJCcdBT7URBoJ7uUy0liItFCCjoHqA10OJE/wViD1UwLJAwXTyyl0KKNDOh1q6AfZdGhQgOkzk2+Uh2qkZFQosyiiyP6LgsUHY6PSo7KjBPKVKMJK3lHBUURmXo6qiSIC8gNyq7ytZlv6to2i3w00KAHtTk0QRY1SaRsB4+H+zNTMtPh0SqPSza93T328Z8XmFYdk9Ha31Ixe3bvNE5+O7xAZ3y5UHjV71uTE4QH+I7pOnT9nqhxtjYtJSlyi2HuzST7/cWc+n+rCdJHab3RooEO2SLP5IqULeVdBE/VE3rxFPxpBB286XCYf2cD9fD6gpQACaxQw05Q+9EK45oh0XMb1bM4NJDYczOIAOeAh4XMuDuDhEizjC328XZtzNEEopkJYjBguHVMweErLusu6mFk9U0dH1JJQyqaXZqemCM3vHR8Un9AiCKdJ5xWapAEgTGU1ia01cdQHGhUQUFxwstVCAW2vsvigBTnXsAMK1+DjyA0Kn52F0t2+7Df3of5wg9BFkVNC7H1yKXYO3FBbi/r/ocxfhDPhSQLpDTowf9pNZdipLAwgcnHCZqLWl3AyS6RiGibCNM+MQa/u1qX17NY/REjw7N937Jxn28W0ay2tUuYajLbDLUQmSqAH3wf8P9j3XHewTeC82LD4cLjlwxKYjrajki1mJudmEXuknbMeNQOQFeREsL3Eg9ojdAghA033uB7p8D89p2HW4T17jhzevffIW0MG9h8yNGfAYHHmpvfe2zR986FDmweOGzdwes748TlMR08EW4VVAjE8wGd+AOjAZ3Aqu28DQLpMdHUkOA+Gom3k9XPoD4heAt+gdwEABo5aBB/lOzKQqhhsOHBr/C75zjkhmn6Hr2pk3ykm39klnWDfOcu+840wi3XNfQsMaCf9juposO8ABEbimcIXYmfWA9YDEEl9v/NL///p/JJZl5eye6xO+zaOdYPRQ03Q6yh9ct9h40f3m45+E+CfH35xfcO0pGDS+oV2r5ubm/1sTsGkXNb6dZi0fnUcPhjuvsZsKqUnSReKIkBr9mRZ0APmAndwwEsSxWjySCqMRYWZCT+CwymMwRWmuwpTBV6BQylMM1niYUarMMfB6/ApCuMtu/yOlwozESyHecCbzEVhaCzIi4hiLe5lKuwxmAEPUFiTRGFNylEwzLdp+AsA3WDJxnLJW7iqz0c1PwiiMxRkHyHAPJdOFrsnkJ2+CSCtMNpQpw3wLrTAl2vINGVgL6LueAodcslAO+gF8o/aB0b2By0k/Dy4fqE39ngHXyJ2wRXHXB/U2vGTL9p69yac00JS2rmO4fHHcAIchxZAoOwbnEr7nghdIgDdN3PhkYZ6cp/197C1bqOsNahqXGuZ0V+F6a7CVIESZR0NsguMlwozEQxvXCPZZY0avqC9HGzOdsqcDUuUOSUJNf7eGwCghTqLCjMTJCn85abCNJwjMHMZXgpMVUOagpebrMK8T2A2MrwUmIkNgQpeDIbWKUmN/ABaKzWzTN7Nf8QpC3ZBAk4WuExYoOKscFkgWjZdoL1PAlXFArUjhGABFZcjQSP9q12LdCSuL4haW4GN1S5q05bRonZtERvxyPbt91u3WmEHa966BAW0/lU0Q23hQutxR9bChfswmit9D2yfdXTus98b95nOSSul/0CXSGA6Ofe9H5xGYYIkDx4mQYWZCT+BUylMsCtMrgpTRaT0ZArTSnaBma3CHAdfwMXsd1xhQlWYieANWEzXLoTC2EIMtpbOtYOgN/hauCEuB55ExgYQx8K/QoBG2lEismMPdGykUSsjhIkQmiHUQdgbpuCqTTAZpmzCVWzAx+BTsAvssgW/zwb8/haYiT+gcwgEn/2kP+N3EADCCRUH8B0HfPywPR/ADtWGjNqH0sBbcGh7+tJWeYlmN5XWDVbER+ND1LdjiWdqJEDiyJmhEum2EFMhEvppGjr6b0wftKk0bwztSih47cn+m5b0GVjfM8wiwzux07vtexdV+ptk7BOZH9/Y59G69YaLA26XKW0KJAp5acD3i/Dd7BWxUBjWpt1vB1OLomD9wRYtfjvE+IfVsbO1SHLyhlnZs0bJna2XCmNRYWbCT5U96+cK012FqSJ6dCiDkV1gvFSYieBNZc8yGJsfkZSqvGf10GzOFOec65Q5vSSFrwECmwjMQtaXZQLZfBU+Z5raIfBwRhrdPegOp64d5OpAbO6urpuPVWlfoQU7Rh+ntQ9X/FULvfGt2r/q6v5aQf6TbPjXusqqWvwleReOA1eNHb+G8e0z5Fl3ysEgEgzSSBxfrhrFtbVGLzUaB/4avgrxkZh7SZqqXZrrGt1dky8wcQVPccQMbvRf4Nzav069+t1M2PX8sf6vRHRsOy8tLx+/t3BE+vApYrcrd//9xrSzaV3xTysrKkKDjgW0yeneC5rWD/y8Z9+CTcuUtWB1v9IVshZdnbpkMQika9FODmBrocJcVmFmwiQQQGFiXWBkyQkjg6oUM4Vor1MgwH0YiwpzPC2K/coDMNJpFWaifwvKRR0oDD1eK6ZaO19vFadj4DMwjULGyxQy3mBLdsoZAcQ1XJeXin1Ae/AY6AJOc9XNmkO9Hl3qLLBSZ3s6CKYrlh5bUZJelk4rntOJ3shOH5GOpim3iitq0hvIC1GeTRc624PYiy2dO6GGapk2fLdtrOaSRKut1bTztDNfH/rwCB5LcPB1o5p4HmwsIRWvLj2Tlfz15opjt375NG9Q3qRrSK49Oem1pPSXx3x9wzFEEFevGrWw35OPnaqflrWh7ZmiucOFjPHTPRA8OM40NKfHqAM79rzeffi4YZnN5TWHumSkZ+G7P62Rl+xv3/6FmF6Hnux4ZFS3zGz0S9kMqdWEUrbG/XAqrU0ma/e4065JY3YNq6uVvif3n3Dy4hLQgnJIiFPfqTBXVJiZsLPCr2EuMLLMYBgvpvlTiFCdAgFUGOmMCjMxMIhyT2sKY2ttsFkUPmugzbeljB8/cto9Y4HE7B7VXgFlAKAC6ZQTRgYzW4hai4bZT4cJTJ70B4NR7B4LQAxKp9o9+wnMTOmgCjMRO4AMvBmMq92TQvi/j3QTWAhX7wSkxJivPAgOIiaNV5BOqc637/Uil4AOJq8ges8Um2EONsWa0k3ZphGmKaYSU5lpr+kt0wcmT+IaBpkoTEis3dcUwvReiIm+AF/K+zQS1lbD1AavtvRDczBLGepcm9r8CAv6Aqf3TjUjCTpLkYnxEVSi0fwbDceQK2fh/uJRk/CX3/+IL0GfSwO3xon6/hn4dp/vLL0jew7Y1uVsH9x8wfaw9eMWbtwq6SfgG/86ewcfhwHVP0BzepyUvztlS9E82aeVvsqY1X560b3U6n1LO2RUPDvnTbpOrL6QyZ9+ivwZyuSPWSeq66TU/TH+6u/kwT0Kf7WWFSgV5rIKMxMOVORhpAuMLDEYxoNDmTyMeGAu2aLCHB/O8Il8EJ/TKszEeCYP21AYWxuDLZxxhEDwfFVMFA+ynI8nSOXPaFOsVLGaNeOowQRAT5aiXs9U2vvvxgd1w6k1S/7ExHq9cBsvpqly9PiXH1y8d/simY/gNZPUHh7m7Cq+1oQZWa52lcDbVa14u4pdqXaVkTCMakpRHlKNLOtD7Koc6H41fnTME+vGDx+F//6lw7CoJ9aNHT2+rmUrGUb4x7cqWQDrA/1lfNm3fUBJCYqshfFGnw1f9LhWZrqNP/FutuFs9z+29FnUBqIhnl4nd3ad2RY67G5uJ/Yoa8FquthaDHHyxm5FFphkN7ZiKswpFWYmHACYNPB3hfmDwTDeGIIYhI5BaOc6qMJMjGOSgMHY/Gk9gfJbrN6HzZfrnM9fmS9QNjXaUitJLDDtv+tj+U/ViTbdx5Km1InWdVozvOkyUd07jje6dOfrRNXnY3TIVehwl9EhUEeejgZ0zYz/IZXBrBaEr6XWN11LXUpLxBU5WthwXdeDnYMVTmxOEgvlDxhRQ6KPbjD35jxE+wgj9SppROAseUfz8768ojfzRcP+XEUJX0Nssaj9zdSxUE/ckNRiVpqq0/WoX5y7OAvXEx8oEwrd1mYLs+lJHPRUjnsF1sKO8YUd9x6o8PCEPaEH7ADdYS+9eyUurMRWX6LykmS3Tyrxp1WfAra3CU0QsZdCQQdiMc3WnJb1yMYQ/ribBGCk+iCBGEoJZQkoj3tmwB8aF1FNlUqM5k7HatW4UVpgmjZoIBeSVG0aadjiM5mZJxb9iv8mEmHxycyMD6fxLTL3xs0vLSkpWVyyQLjT2C0zetjwUTCuzkSkQuHw4YXaphkUuff4CVJ7ffLkTjhG7Z/ZSfLsKcS3dAOhLMuO+Cz7QW9dsC5WJ+Qpx3GSbIOORGytQkpl2dqPoFuZWO+/alXgHwoflooDUIR0geXNOrL8lKCWDKcL2c7yXe/7kWAiAhovms6OUeKVzhs6eM6cwUPnTU6OjkpKiopOlvwGFBcPGFhUNDC6c1JMTDKEyUpPgfi10E/6GxhBAmAlU9qZ3KtpqMtLe8ugXngprh1kk6s1XQwHod/sYd1fsEYmLJk1LOlAXESSVD1i+dDMmLD8VUMz2jM59xIqEn8WOhJL8KvzIMeaweJIqEhy3rOBsWMzKH5dhL/hcCLDJGDQ1GL6siZQo1UwhXV5blbKRfEALMQ73iPw3YQ7MF8Lz/Yqg4fKCaf59AvSIPwczK0CgM2B78Lh0Is/C5WIi+E7F6Zc9MVXoTv0IPhRXNDz5LcjwEkmc0/CJwEARpceDp3q7xJc0FsM/hSDPwX7MXjed/RQbbsuDWa0HYYCiXCDO8WEfRbO0JbYCAc8NzXla9iNjk/iT2HkT+fIGHsBKP4pbEBdhTvAi3CmXfAQol0j+c/MLhw7Z/bYwjmCJX/O7BG9R86YOYLmJ8FWZBUOApl8L4Bsa39ahRoG46EVpvz9Er4CQ15CEXgaXG6Ey+k8Awh8CxVeovBGaIJhRuEeDMFXXvr7b+EgnmvEc2EZXEfgY0CRME2KBAJ9KhDLjqJLjITmV+lhzUXsEGb2/OmogzCIyGQP0Ayk8/H8+31HdllydzbjeAoaycJYVSmq9XIelUkrnSKhVfCJFNCXpaVV2CrCMyer5NvC7G0221Q0w3EAPonw2/SZehK/4AqZOxqUgvsh/wfKsaIjSTlWbDQ7EI2zs/T8YQOAnupMYMhR53bvSHqcDhlskbyrZ6omd+jR5y1cjWeLSa1CZ3KQGGTsLw5om+os9J+wC8ftWPbY1DjfpHlpN/F3G8h/MOxmyvQs34RpSUu3wzM4Dp6BJ9HUV318jnkbYIuPUOWiSv1x2NrgfcJgPFDcrHKRwj97UJHwvdDx4Wf9Ct/T/DYqqlLWyx8A0cz6CFuAyY/qJNS2HjWpPfzJhf9/oseQqvkjL7xw9ewTa3PD02Y/XjT2q6/QuLo60muYW/llcMuTphYFBbmk17DRDugNgBAuWAjPGUA3Dc81d00lIHeRsh2KLYfajLzBeVarnnGeN8950Gz1idShA8XFH+DRHvDFD/EY4bysh6Hr16+fjoKwLEET8mW0H9XwJ7outANRYIsmz95cSznFHnsw726PCmymSZE7s+FqplxJkudpE+aPzpTbHw+GeeStNg3/n82ew3OPzp4zmQTQV4QegaCPpmai+QNnHf+vqyMs/4fqiIfURgwGAG4hOEogRiPTmzd1zjOZnmuXVFO4LIGr5mQsak5mJpzXmKNT8jb/Bbts07oAAAB4AWNgZGAAYen931bF89t8ZZDkYACBIx8E9UD0OZEzun+E/l7lLOKoBHI5GZhAogBOMQvyeAFjYGRg4Ej6e5WBgdPoj9B/I44FQBFUcAcAiWcGPQB4AW2RUxidTQwG52Szv22ztm3btm3btm3btm3bvqvd03y1LuaZrPGGngCA+RkSkWEyhHR6jhTag4r+DBX8n6QKFSOdLKaNrOBb15rftSEZQrtIJGPILCkY6jIjNr+KMd/IZ+QxkhjtjAZGRqNsMCYRGSr/UFW/JbX2oq9Go427QIyP/yWbj8I3/h9G+5+o5tMxWscbE6xdmVp+DqMlJzO1Bclt3mgtwOiPxcbmGI2o7KObO5lzmD+huI7lb9+ATv4Hvv74B6KY4+kdvtQ1FJG4dHCF+dH8hatOQjcCJwPszsXs7l1oo/HJa86vKSgqu4lmdQGjpXxPH/k1PEfj0DaoP7ptc7vQKphrtAksG81RySdb+NnazfUr/vEPiGj+1/jGKCizSSLCLPPvPi8Nn/39X/TWlnbvheT1IympZ/gt9Igueo8S+hcTPspAYdeXBu4c5bQmrYO/f9Z3nM7uM1prdkq7stRw5Sknc2miy+mn35BK0jFGvqGmJLS5k2ls66t99AVzPqpkHKWehigT/PuH+Lhj+E6QRZDDSyRneH+Qg/moscqXIcLLDN5FM5DTN7facniTZzlsY4Bepkvw5x/io7UkeJaDZfAm8lt4kfxGb/MKY6wuI8UbGbxNX9JrV7Pl8BZBDoPpFjjY6+MFVPw4OfndJYbLPNq5I7TxnZn8UVtmhEaSzsgYWK4ZN8gox83b6SL1qCFVKeBGENNNJbXmJLu2Z5RO4RfXnZyuEuVcQZsTn8LB3z0FW2/CPAAAAAAAAAAAAAAALABaANQBSgHaAo4CqgLUAv4DLgNUA2gDgAOaA7IEAgQuBIQFAgVKBbAGGgZQBsgHMAdAB1AHgAeuB94IOgjuCTgJpgn8Cj4KhgrCCygLggueC9QMHgxCDKYM9A1GDYwN6A5MDrIO3g8aD1IPuhAGEEQQfhCkELwQ4BECER4RWBHiEkASkBLuE1IToBQUFFoUhhTKFRIVLhWaFeAWMhaQFuwXLBewGAAYRBh+GOIZPBmSGcwaEBooGmwashqyGtobRBuqHA4ccByaHT4dYB30Ho4emh60HrwfZh98H8ggCiBoIQYhQCGQIboh0CIGIjwihiKSIqwixiLgIzgjSiNcI24jgCOWI6wkIiQuJEAkUiRoJHokjCSeJLQlIiU0JUYlWCVqJXwlkiXEJkImVCZmJngmjiagJu4nVCdmJ3gniiecJ7AnxiiOKJoorCi+KNAo5Cj2KQgpGikwKcop3CnuKgAqEiokKjgqcCrqKvwrDisgKzQrRiukK7gr1CxeLPItGC1YLZQtni2oLcAt2i3uLgYuHi4+Llouci6KLp4u3C9eL3Yv2DAcMKQw9jEcMS4AAAABAAAA3ACXABYAXwAFAAEAAAAAAA4AAAIAAeYAAwABeAF9zANyI2AYBuBnt+YBMsqwjkfpsLY9qmL7Bj1Hb1pbP7+X6HOmy7/uAf8EeJn/GxV4mbvEjL/M3R88Pabfsr0Cbl7mUQdu7am4VNFUEbQp5VpOS8melIyWogt1yyoqMopSkn+kkmIiouKOpNQ15FSUBUWFREWe1ISoWcE378e+mU99WU1NVUlhYZ2nHXKh6sKVrJSQirqMsKKcKyllDSkNYRtWzVu0Zd+iGTEhkXtU0y0IeAFswQOWQgEAAMDZv7Zt27ZtZddTZ+4udYFmBEC5qKCaEjWBQK069Ro0atKsRas27Tp06tKtR68+/QYMGjJsxKgx4yZMmjJtxqw58xYsWrJsxao16zZs2rJtx649+w4cOnLsxKkz5y5cunLtxq079x48evLsxas37z58+vLtx68//0LCIqJi4hKSUtIyshWC4GErEAAAAOAs/3NtI+tluy7Ztm3zZZ6z69yMBuVixBqU50icNMkK1ap48kySXdGy3biVKl+CcYeuFalz786DMo1mTWvy2hsZ3po3Y86yBYuWHHtvzYpVzT64kmnTug0fnTqX6LNPvvjmq+9K/PDLT7/98c9f/wU4EShYkBBhQvUoFSFcpChnLvTZ0qLVtgM72rTr0m1Ch06T4g0ZNvDk+ZMXLo08efk4RnZGDkZOhlQWv1AfH/bSvEwDA0cXEG1kYG7C4lpalM+Rll9apFdcWsBZklGUmgpisZeU54Pp/DwwHwBPQXTqAHgBLc4lXMVQFIDxe5+/Ke4uCXd3KLhLWsWdhvWynugFl7ieRu+dnsb5flD+V44+W03Pqkm96nSsSX3pwfbG8hyVafqKLY53NhRyi8/1/P8l1md6//6SRzsznWXcUiuTXQ3F3NJTfU3V3NRrJp2WrjUzN3sl06/thr54PYV7+IYaQ1++jlly8+AO2iz5W4IT8OEJIqi29NXrGHhwB65DLfxAtSN5HvgQQgRjjiSfQJDDoBz5e4AA3BwJtOVAHgtBBGGeRNsK5DYGd8IvM61XFAA=) format('woff'),\n}\n\n@font-face {\n    font-family: 'Roboto';\n    font-style: normal;\n    font-weight: 200;\n    src:\n        local('Roboto Light'),\n        url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n}"
  },
  {
    "path": "plugins/UiPluginManager/media/js/PluginList.coffee",
    "content": "class PluginList extends Class\n\tconstructor: (plugins) ->\n\t\t@plugins = plugins\n\n\tsavePluginStatus: (plugin, is_enabled) =>\n\t\tPage.cmd \"pluginConfigSet\", [plugin.source, plugin.inner_path, \"enabled\", is_enabled], (res) =>\n\t\t\tif res == \"ok\"\n\t\t\t\tPage.updatePlugins()\n\t\t\telse\n\t\t\t\tPage.cmd \"wrapperNotification\", [\"error\", res.error]\n\n\t\tPage.projector.scheduleRender()\n\n\thandleCheckboxChange: (e) =>\n\t\tnode = e.currentTarget\n\t\tplugin = node[\"data-plugin\"]\n\t\tnode.classList.toggle(\"checked\")\n\t\tvalue = node.classList.contains(\"checked\")\n\n\t\t@savePluginStatus(plugin, value)\n\n\thandleResetClick: (e) =>\n\t\tnode = e.currentTarget\n\t\tplugin = node[\"data-plugin\"]\n\n\t\t@savePluginStatus(plugin, null)\n\n\thandleUpdateClick: (e) =>\n\t\tnode = e.currentTarget\n\t\tplugin = node[\"data-plugin\"]\n\t\tnode.classList.add(\"loading\")\n\n\t\tPage.cmd \"pluginUpdate\", [plugin.source, plugin.inner_path], (res) =>\n\t\t\tif res == \"ok\"\n\t\t\t\tPage.cmd \"wrapperNotification\", [\"done\", \"Plugin #{plugin.name} updated to latest version\"]\n\t\t\t\tPage.updatePlugins()\n\t\t\telse\n\t\t\t\tPage.cmd \"wrapperNotification\", [\"error\", res.error]\n\t\t\tnode.classList.remove(\"loading\")\n\n\t\treturn false\n\n\thandleDeleteClick: (e) =>\n\t\tnode = e.currentTarget\n\t\tplugin = node[\"data-plugin\"]\n\t\tif plugin.loaded\n\t\t\tPage.cmd \"wrapperNotification\", [\"info\", \"You can only delete plugin that are not currently active\"]\n\t\t\treturn false\n\n\t\tnode.classList.add(\"loading\")\n\n\t\tPage.cmd \"wrapperConfirm\", [\"Delete #{plugin.name} plugin?\", \"Delete\"], (res) =>\n\t\t\tif not res\n\t\t\t\tnode.classList.remove(\"loading\")\n\t\t\t\treturn false\n\n\t\t\tPage.cmd \"pluginRemove\", [plugin.source, plugin.inner_path], (res) =>\n\t\t\t\tif res == \"ok\"\n\t\t\t\t\tPage.cmd \"wrapperNotification\", [\"done\", \"Plugin #{plugin.name} deleted\"]\n\t\t\t\t\tPage.updatePlugins()\n\t\t\t\telse\n\t\t\t\t\tPage.cmd \"wrapperNotification\", [\"error\", res.error]\n\t\t\t\tnode.classList.remove(\"loading\")\n\n\t\treturn false\n\n\trender: ->\n\t\th(\"div.plugins\", @plugins.map (plugin) =>\n\t\t\tif not plugin.info\n\t\t\t\treturn\n\t\t\tdescr = plugin.info.description\n\t\t\tplugin.info.default ?= \"enabled\"\n\t\t\tif plugin.info.default\n\t\t\t\tdescr += \" (default: #{plugin.info.default})\"\n\n\t\t\ttag_version = \"\"\n\t\t\ttag_source = \"\"\n\t\t\ttag_delete = \"\"\n\t\t\tif plugin.source != \"builtin\"\n\t\t\t\ttag_update = \"\"\n\t\t\t\tif plugin.site_info?.rev\n\t\t\t\t\tif plugin.site_info.rev > plugin.info.rev\n\t\t\t\t\t\ttag_update = h(\"a.version-update.button\",\n\t\t\t\t\t\t\t{href: \"#Update+plugin\", onclick: @handleUpdateClick, \"data-plugin\": plugin},\n\t\t\t\t\t\t\t\"Update to rev#{plugin.site_info.rev}\"\n\t\t\t\t\t\t)\n\n\t\t\t\telse\n\t\t\t\t\ttag_update = h(\"span.version-missing\", \"(unable to get latest vesion: update site missing)\")\n\n\t\t\t\ttag_version = h(\"span.version\",[\n\t\t\t\t\t\"rev#{plugin.info.rev} \",\n\t\t\t\t\ttag_update,\n\t\t\t\t])\n\n\t\t\t\ttag_source = h(\"div.source\",[\n\t\t\t\t\t\"Source: \",\n\t\t\t\t\th(\"a\", {\"href\": \"/#{plugin.source}\", \"target\": \"_top\"}, if plugin.site_title then plugin.site_title else plugin.source),\n\t\t\t\t\t\" /\" + plugin.inner_path\n\t\t\t\t])\n\n\t\t\t\ttag_delete = h(\"a.delete\", {\"href\": \"#Delete+plugin\", onclick: @handleDeleteClick, \"data-plugin\": plugin}, \"Delete plugin\")\n\n\n\t\t\tenabled_default = plugin.info.default == \"enabled\"\n\t\t\tif plugin.enabled != plugin.loaded or plugin.updated\n\t\t\t\tmarker_title = \"Change pending\"\n\t\t\t\tis_pending = true\n\t\t\telse\n\t\t\t\tmarker_title = \"Changed from default status (click to reset to #{plugin.info.default})\"\n\t\t\t\tis_pending = false\n\n\t\t\tis_changed = plugin.enabled != enabled_default and plugin.owner == \"builtin\"\n\n\t\t\th(\"div.plugin\", {key: plugin.name}, [\n\t\t\t\th(\"div.title\", [\n\t\t\t\t\th(\"h3\", [plugin.name, tag_version]),\n\t\t\t\t\th(\"div.description\", [descr, tag_source, tag_delete]),\n\t\t\t\t])\n\t\t\t\th(\"div.value.value-right\",\n\t\t\t\t\th(\"div.checkbox\", {onclick: @handleCheckboxChange, \"data-plugin\": plugin, classes: {checked: plugin.enabled}}, h(\"div.checkbox-skin\"))\n\t\t\t\th(\"a.marker\", {\n\t\t\t\t\thref: \"#Reset\", title: marker_title,\n\t\t\t\t\tonclick: @handleResetClick, \"data-plugin\": plugin,\n\t\t\t\t\tclasses: {visible: is_pending or is_changed, pending: is_pending}\n\t\t\t\t}, \"\\u2022\")\n\t\t\t\t)\n\t\t\t])\n\t\t)\n\n\nwindow.PluginList = PluginList"
  },
  {
    "path": "plugins/UiPluginManager/media/js/UiPluginManager.coffee",
    "content": "window.h = maquette.h\n\nclass UiPluginManager extends ZeroFrame\n\tinit: ->\n\t\t@plugin_list_builtin = new PluginList()\n\t\t@plugin_list_custom = new PluginList()\n\t\t@plugins_changed = null\n\t\t@need_restart = null\n\t\t@\n\n\tonOpenWebsocket: =>\n\t\t@cmd(\"wrapperSetTitle\", \"Plugin manager - ZeroNet\")\n\t\t@cmd \"serverInfo\", {}, (server_info) =>\n\t\t\t@server_info = server_info\n\t\t@updatePlugins()\n\n\tupdatePlugins: (cb) =>\n\t\t@cmd \"pluginList\", [], (res) =>\n\t\t\t@plugins_changed = (item for item in res.plugins when item.enabled != item.loaded or item.updated)\n\n\t\t\tplugins_builtin = (item for item in res.plugins when item.source == \"builtin\")\n\t\t\t@plugin_list_builtin.plugins = plugins_builtin.sort (a, b) ->\n\t\t\t\treturn a.name.localeCompare(b.name)\n\n\t\t\tplugins_custom = (item for item in res.plugins when item.source != \"builtin\")\n\t\t\t@plugin_list_custom.plugins = plugins_custom.sort (a, b) ->\n\t\t\t\treturn a.name.localeCompare(b.name)\n\n\t\t\t@projector.scheduleRender()\n\t\t\tcb?()\n\n\tcreateProjector: =>\n\t\t@projector = maquette.createProjector()\n\t\t@projector.replace($(\"#content\"), @render)\n\t\t@projector.replace($(\"#bottom-restart\"), @renderBottomRestart)\n\n\trender: =>\n\t\tif not @plugin_list_builtin.plugins\n\t\t\treturn h(\"div.content\")\n\n\t\th(\"div.content\", [\n\t\t\th(\"div.section\", [\n\t\t\t\tif @plugin_list_custom.plugins?.length\n\t\t\t\t\t[\n\t\t\t\t\t\th(\"h2\", \"Installed third-party plugins\"),\n\t\t\t\t\t\t@plugin_list_custom.render()\n\t\t\t\t\t]\n\t\t\t\th(\"h2\", \"Built-in plugins\")\n\t\t\t\t@plugin_list_builtin.render()\n\t\t\t])\n\t\t])\n\n\thandleRestartClick: =>\n\t\t@restart_loading = true\n\t\tsetTimeout ( =>\n\t\t\tPage.cmd(\"serverShutdown\", {restart: true})\n\t\t), 300\n\t\tPage.projector.scheduleRender()\n\t\treturn false\n\n\trenderBottomRestart: =>\n\t\th(\"div.bottom.bottom-restart\", {classes: {visible: @plugins_changed?.length}}, h(\"div.bottom-content\", [\n\t\t\th(\"div.title\", \"Some plugins status has been changed\"),\n\t\t\th(\"a.button.button-submit.button-restart\",\n\t\t\t\t{href: \"#Restart\", classes: {loading: @restart_loading}, onclick: @handleRestartClick},\n\t\t\t\t\"Restart ZeroNet client\"\n\t\t\t)\n\t\t]))\n\nwindow.Page = new UiPluginManager()\nwindow.Page.createProjector()\n"
  },
  {
    "path": "plugins/UiPluginManager/media/js/all.js",
    "content": "\n/* ---- lib/Class.coffee ---- */\n\n\n(function() {\n  var Class,\n    slice = [].slice;\n\n  Class = (function() {\n    function Class() {}\n\n    Class.prototype.trace = true;\n\n    Class.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      if (!this.trace) {\n        return;\n      }\n      if (typeof console === 'undefined') {\n        return;\n      }\n      args.unshift(\"[\" + this.constructor.name + \"]\");\n      console.log.apply(console, args);\n      return this;\n    };\n\n    Class.prototype.logStart = function() {\n      var args, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      if (!this.trace) {\n        return;\n      }\n      this.logtimers || (this.logtimers = {});\n      this.logtimers[name] = +(new Date);\n      if (args.length > 0) {\n        this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(started)\"]));\n      }\n      return this;\n    };\n\n    Class.prototype.logEnd = function() {\n      var args, ms, name;\n      name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];\n      ms = +(new Date) - this.logtimers[name];\n      this.log.apply(this, [\"\" + name].concat(slice.call(args), [\"(Done in \" + ms + \"ms)\"]));\n      return this;\n    };\n\n    return Class;\n\n  })();\n\n  window.Class = Class;\n\n}).call(this);\n\n\n/* ---- lib/Promise.coffee ---- */\n\n\n(function() {\n  var Promise,\n    slice = [].slice;\n\n  Promise = (function() {\n    Promise.when = function() {\n      var args, fn, i, len, num_uncompleted, promise, task, task_id, tasks;\n      tasks = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      num_uncompleted = tasks.length;\n      args = new Array(num_uncompleted);\n      promise = new Promise();\n      fn = function(task_id) {\n        return task.then(function() {\n          args[task_id] = Array.prototype.slice.call(arguments);\n          num_uncompleted--;\n          if (num_uncompleted === 0) {\n            return promise.complete.apply(promise, args);\n          }\n        });\n      };\n      for (task_id = i = 0, len = tasks.length; i < len; task_id = ++i) {\n        task = tasks[task_id];\n        fn(task_id);\n      }\n      return promise;\n    };\n\n    function Promise() {\n      this.resolved = false;\n      this.end_promise = null;\n      this.result = null;\n      this.callbacks = [];\n    }\n\n    Promise.prototype.resolve = function() {\n      var back, callback, i, len, ref;\n      if (this.resolved) {\n        return false;\n      }\n      this.resolved = true;\n      this.data = arguments;\n      if (!arguments.length) {\n        this.data = [true];\n      }\n      this.result = this.data[0];\n      ref = this.callbacks;\n      for (i = 0, len = ref.length; i < len; i++) {\n        callback = ref[i];\n        back = callback.apply(callback, this.data);\n      }\n      if (this.end_promise) {\n        return this.end_promise.resolve(back);\n      }\n    };\n\n    Promise.prototype.fail = function() {\n      return this.resolve(false);\n    };\n\n    Promise.prototype.then = function(callback) {\n      if (this.resolved === true) {\n        callback.apply(callback, this.data);\n        return;\n      }\n      this.callbacks.push(callback);\n      return this.end_promise = new Promise();\n    };\n\n    return Promise;\n\n  })();\n\n  window.Promise = Promise;\n\n\n  /*\n  s = Date.now()\n  log = (text) ->\n  \tconsole.log Date.now()-s, Array.prototype.slice.call(arguments).join(\", \")\n  \n  log \"Started\"\n  \n  cmd = (query) ->\n  \tp = new Promise()\n  \tsetTimeout ( ->\n  \t\tp.resolve query+\" Result\"\n  \t), 100\n  \treturn p\n  \n  back = cmd(\"SELECT * FROM message\").then (res) ->\n  \tlog res\n  \treturn \"Return from query\"\n  .then (res) ->\n  \tlog \"Back then\", res\n  \n  log \"Query started\", back\n   */\n\n}).call(this);\n\n\n/* ---- lib/Prototypes.coffee ---- */\n\n\n(function() {\n  String.prototype.startsWith = function(s) {\n    return this.slice(0, s.length) === s;\n  };\n\n  String.prototype.endsWith = function(s) {\n    return s === '' || this.slice(-s.length) === s;\n  };\n\n  String.prototype.repeat = function(count) {\n    return new Array(count + 1).join(this);\n  };\n\n  window.isEmpty = function(obj) {\n    var key;\n    for (key in obj) {\n      return false;\n    }\n    return true;\n  };\n\n}).call(this);\n\n\n/* ---- lib/maquette.js ---- */\n\n\n(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['exports'], factory);\n    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {\n        // CommonJS\n        factory(exports);\n    } else {\n        // Browser globals\n        factory(root.maquette = {});\n    }\n}(this, function (exports) {\n    'use strict';\n    ;\n    ;\n    ;\n    ;\n    var NAMESPACE_W3 = 'http://www.w3.org/';\n    var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';\n    var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';\n    // Utilities\n    var emptyArray = [];\n    var extend = function (base, overrides) {\n        var result = {};\n        Object.keys(base).forEach(function (key) {\n            result[key] = base[key];\n        });\n        if (overrides) {\n            Object.keys(overrides).forEach(function (key) {\n                result[key] = overrides[key];\n            });\n        }\n        return result;\n    };\n    // Hyperscript helper functions\n    var same = function (vnode1, vnode2) {\n        if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {\n            return false;\n        }\n        if (vnode1.properties && vnode2.properties) {\n            if (vnode1.properties.key !== vnode2.properties.key) {\n                return false;\n            }\n            return vnode1.properties.bind === vnode2.properties.bind;\n        }\n        return !vnode1.properties && !vnode2.properties;\n    };\n    var toTextVNode = function (data) {\n        return {\n            vnodeSelector: '',\n            properties: undefined,\n            children: undefined,\n            text: data.toString(),\n            domNode: null\n        };\n    };\n    var appendChildren = function (parentSelector, insertions, main) {\n        for (var i = 0; i < insertions.length; i++) {\n            var item = insertions[i];\n            if (Array.isArray(item)) {\n                appendChildren(parentSelector, item, main);\n            } else {\n                if (item !== null && item !== undefined) {\n                    if (!item.hasOwnProperty('vnodeSelector')) {\n                        item = toTextVNode(item);\n                    }\n                    main.push(item);\n                }\n            }\n        }\n    };\n    // Render helper functions\n    var missingTransition = function () {\n        throw new Error('Provide a transitions object to the projectionOptions to do animations');\n    };\n    var DEFAULT_PROJECTION_OPTIONS = {\n        namespace: undefined,\n        eventHandlerInterceptor: undefined,\n        styleApplyer: function (domNode, styleName, value) {\n            // Provides a hook to add vendor prefixes for browsers that still need it.\n            domNode.style[styleName] = value;\n        },\n        transitions: {\n            enter: missingTransition,\n            exit: missingTransition\n        }\n    };\n    var applyDefaultProjectionOptions = function (projectorOptions) {\n        return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);\n    };\n    var checkStyleValue = function (styleValue) {\n        if (typeof styleValue !== 'string') {\n            throw new Error('Style values must be strings');\n        }\n    };\n    var setProperties = function (domNode, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            /* tslint:disable:no-var-keyword: edge case */\n            var propValue = properties[propName];\n            /* tslint:enable:no-var-keyword */\n            if (propName === 'className') {\n                throw new Error('Property \"className\" is not supported, use \"class\".');\n            } else if (propName === 'class') {\n                if (domNode.className) {\n                    // May happen if classes is specified before class\n                    domNode.className += ' ' + propValue;\n                } else {\n                    domNode.className = propValue;\n                }\n            } else if (propName === 'classes') {\n                // object with string keys and boolean values\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    if (propValue[className]) {\n                        domNode.classList.add(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                // object with string keys and string (!) values\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var styleValue = propValue[styleName];\n                    if (styleValue) {\n                        checkStyleValue(styleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, styleValue);\n                    }\n                }\n            } else if (propName === 'key') {\n                continue;\n            } else if (propValue === null || propValue === undefined) {\n                continue;\n            } else {\n                var type = typeof propValue;\n                if (type === 'function') {\n                    if (propName.lastIndexOf('on', 0) === 0) {\n                        if (eventHandlerInterceptor) {\n                            propValue = eventHandlerInterceptor(propName, propValue, domNode, properties);    // intercept eventhandlers\n                        }\n                        if (propName === 'oninput') {\n                            (function () {\n                                // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput\n                                var oldPropValue = propValue;\n                                propValue = function (evt) {\n                                    evt.target['oninput-value'] = evt.target.value;\n                                    // may be HTMLTextAreaElement as well\n                                    oldPropValue.apply(this, [evt]);\n                                };\n                            }());\n                        }\n                        domNode[propName] = propValue;\n                    }\n                } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {\n                    if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                        domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                    } else {\n                        domNode.setAttribute(propName, propValue);\n                    }\n                } else {\n                    domNode[propName] = propValue;\n                }\n            }\n        }\n    };\n    var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var propertiesUpdated = false;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            // assuming that properties will be nullified instead of missing is by design\n            var propValue = properties[propName];\n            var previousValue = previousProperties[propName];\n            if (propName === 'class') {\n                if (previousValue !== propValue) {\n                    throw new Error('\"class\" property may not be updated. Use the \"classes\" property for conditional css classes.');\n                }\n            } else if (propName === 'classes') {\n                var classList = domNode.classList;\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    var on = !!propValue[className];\n                    var previousOn = !!previousValue[className];\n                    if (on === previousOn) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (on) {\n                        classList.add(className);\n                    } else {\n                        classList.remove(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var newStyleValue = propValue[styleName];\n                    var oldStyleValue = previousValue[styleName];\n                    if (newStyleValue === oldStyleValue) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (newStyleValue) {\n                        checkStyleValue(newStyleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, newStyleValue);\n                    } else {\n                        projectionOptions.styleApplyer(domNode, styleName, '');\n                    }\n                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };\n    var findIndexOfChild = function (children, sameAs, start) {\n        if (sameAs.vnodeSelector !== '') {\n            // Never scan for text-nodes\n            for (var i = start; i < children.length; i++) {\n                if (same(children[i], sameAs)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    var nodeAdded = function (vNode, transitions) {\n        if (vNode.properties) {\n            var enterAnimation = vNode.properties.enterAnimation;\n            if (enterAnimation) {\n                if (typeof enterAnimation === 'function') {\n                    enterAnimation(vNode.domNode, vNode.properties);\n                } else {\n                    transitions.enter(vNode.domNode, vNode.properties, enterAnimation);\n                }\n            }\n        }\n    };\n    var nodeToRemove = function (vNode, transitions) {\n        var domNode = vNode.domNode;\n        if (vNode.properties) {\n            var exitAnimation = vNode.properties.exitAnimation;\n            if (exitAnimation) {\n                domNode.style.pointerEvents = 'none';\n                var removeDomNode = function () {\n                    if (domNode.parentNode) {\n                        domNode.parentNode.removeChild(domNode);\n                    }\n                };\n                if (typeof exitAnimation === 'function') {\n                    exitAnimation(domNode, removeDomNode, vNode.properties);\n                    return;\n                } else {\n                    transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);\n                    return;\n                }\n            }\n        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;\n        var transitions = projectionOptions.transitions;\n        var oldIndex = 0;\n        var newIndex = 0;\n        var i;\n        var textUpdated = false;\n        while (newIndex < newChildrenLength) {\n            var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;\n            var newChild = newChildren[newIndex];\n            if (oldChild !== undefined && same(oldChild, newChild)) {\n                textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;\n                oldIndex++;\n            } else {\n                var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);\n                if (findOldIndex >= 0) {\n                    // Remove preceding missing children\n                    for (i = oldIndex; i < findOldIndex; i++) {\n                        nodeToRemove(oldChildren[i], transitions);\n                        checkDistinguishable(oldChildren, i, vnode, 'removed');\n                    }\n                    textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;\n                    oldIndex = findOldIndex + 1;\n                } else {\n                    // New child\n                    createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);\n                    nodeAdded(newChild, transitions);\n                    checkDistinguishable(newChildren, newIndex, vnode, 'added');\n                }\n            }\n            newIndex++;\n        }\n        if (oldChildrenLength > oldIndex) {\n            // Remove child fragments\n            for (i = oldIndex; i < oldChildrenLength; i++) {\n                nodeToRemove(oldChildren[i], transitions);\n                checkDistinguishable(oldChildren, i, vnode, 'removed');\n            }\n        }\n        return textUpdated;\n    };\n    var addChildren = function (domNode, children, projectionOptions) {\n        if (!children) {\n            return;\n        }\n        for (var i = 0; i < children.length; i++) {\n            createDom(children[i], domNode, undefined, projectionOptions);\n        }\n    };\n    var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {\n        addChildren(domNode, vnode.children, projectionOptions);\n        // children before properties, needed for value property of <select>.\n        if (vnode.text) {\n            domNode.textContent = vnode.text;\n        }\n        setProperties(domNode, vnode.properties, projectionOptions);\n        if (vnode.properties && vnode.properties.afterCreate) {\n            vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n        }\n    };\n    createDom = function (vnode, parentNode, insertBefore, projectionOptions) {\n        var domNode, i, c, start = 0, type, found;\n        var vnodeSelector = vnode.vnodeSelector;\n        if (vnodeSelector === '') {\n            domNode = vnode.domNode = document.createTextNode(vnode.text);\n            if (insertBefore !== undefined) {\n                parentNode.insertBefore(domNode, insertBefore);\n            } else {\n                parentNode.appendChild(domNode);\n            }\n        } else {\n            for (i = 0; i <= vnodeSelector.length; ++i) {\n                c = vnodeSelector.charAt(i);\n                if (i === vnodeSelector.length || c === '.' || c === '#') {\n                    type = vnodeSelector.charAt(start - 1);\n                    found = vnodeSelector.slice(start, i);\n                    if (type === '.') {\n                        domNode.classList.add(found);\n                    } else if (type === '#') {\n                        domNode.id = found;\n                    } else {\n                        if (found === 'svg') {\n                            projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n                        }\n                        if (projectionOptions.namespace !== undefined) {\n                            domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);\n                        } else {\n                            domNode = vnode.domNode = document.createElement(found);\n                        }\n                        if (insertBefore !== undefined) {\n                            parentNode.insertBefore(domNode, insertBefore);\n                        } else {\n                            parentNode.appendChild(domNode);\n                        }\n                    }\n                    start = i + 1;\n                }\n            }\n            initPropertiesAndChildren(domNode, vnode, projectionOptions);\n        }\n    };\n    updateDom = function (previous, vnode, projectionOptions) {\n        var domNode = previous.domNode;\n        var textUpdated = false;\n        if (previous === vnode) {\n            return false;    // By contract, VNode objects may not be modified anymore after passing them to maquette\n        }\n        var updated = false;\n        if (vnode.vnodeSelector === '') {\n            if (vnode.text !== previous.text) {\n                var newVNode = document.createTextNode(vnode.text);\n                domNode.parentNode.replaceChild(newVNode, domNode);\n                vnode.domNode = newVNode;\n                textUpdated = true;\n                return textUpdated;\n            }\n        } else {\n            if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {\n                projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n            }\n            if (previous.text !== vnode.text) {\n                updated = true;\n                if (vnode.text === undefined) {\n                    domNode.removeChild(domNode.firstChild);    // the only textnode presumably\n                } else {\n                    domNode.textContent = vnode.text;\n                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {\n            // Optional properties argument was omitted\n            properties = undefined;\n        }\n        var text = undefined;\n        var children = undefined;\n        var argsLength = arguments.length;\n        // Recognize a common special case where there is only a single text node\n        if (argsLength === childIndex + 1) {\n            var onlyChild = arguments[childIndex];\n            if (typeof onlyChild === 'string') {\n                text = onlyChild;\n            } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {\n                text = onlyChild[0];\n            }\n        }\n        if (text === undefined) {\n            children = [];\n            for (; childIndex < arguments.length; childIndex++) {\n                var child = arguments[childIndex];\n                if (child === null || child === undefined) {\n                    continue;\n                } else if (Array.isArray(child)) {\n                    appendChildren(selector, child, children);\n                } else if (child.hasOwnProperty('vnodeSelector')) {\n                    children.push(child);\n                } else {\n                    children.push(toTextVNode(child));\n                }\n            }\n        }\n        return {\n            vnodeSelector: selector,\n            properties: properties,\n            children: children,\n            text: text === '' ? undefined : text,\n            domNode: null\n        };\n    };\n    /**\n * Contains simple low-level utility functions to manipulate the real DOM.\n */\n    exports.dom = {\n        /**\n     * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in\n     * its [[Projection.domNode|domNode]] property.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection.\n     * @returns The [[Projection]] which also contains the DOM Node that was created.\n     */\n        create: function (vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, document.createElement('div'), undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Appends a new childnode to the DOM which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param parentNode - The parent node for the new childNode.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the [[Projection]].\n     * @returns The [[Projection]] that was created.\n     */\n        append: function (parentNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, parentNode, undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Inserts a new DOM node which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param beforeNode - The node that the DOM Node is inserted before.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.\n     * NOTE: [[VNode]] objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        insertBefore: function (beforeNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.\n     * This means that the virtual DOM and the real DOM will have one overlapping element.\n     * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects\n     * may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        merge: function (element, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            vnode.domNode = element;\n            initPropertiesAndChildren(element, vnode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        }\n    };\n    /**\n * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.\n * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.\n * For more information, see [[CalculationCache]].\n *\n * @param <Result> The type of the value that is cached.\n */\n    exports.createCache = function () {\n        var cachedInputs = undefined;\n        var cachedOutcome = undefined;\n        var result = {\n            invalidate: function () {\n                cachedOutcome = undefined;\n                cachedInputs = undefined;\n            },\n            result: function (inputs, calculation) {\n                if (cachedInputs) {\n                    for (var i = 0; i < inputs.length; i++) {\n                        if (cachedInputs[i] !== inputs[i]) {\n                            cachedOutcome = undefined;\n                        }\n                    }\n                }\n                if (!cachedOutcome) {\n                    cachedOutcome = calculation();\n                    cachedInputs = inputs;\n                }\n                return cachedOutcome;\n            }\n        };\n        return result;\n    };\n    /**\n * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.\n * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.\n *\n * @param <Source>       The type of source items. A database-record for instance.\n * @param <Target>       The type of target items. A [[Component]] for instance.\n * @param getSourceKey   `function(source)` that must return a key to identify each source object. The result must either be a string or a number.\n * @param createResult   `function(source, index)` that must create a new result object from a given source. This function is identical\n *                       to the `callback` argument in `Array.map(callback)`.\n * @param updateResult   `function(source, target, index)` that updates a result to an updated source.\n */\n    exports.createMapping = function (getSourceKey, createResult, updateResult) {\n        var keys = [];\n        var results = [];\n        return {\n            results: results,\n            map: function (newSources) {\n                var newKeys = newSources.map(getSourceKey);\n                var oldTargets = results.slice();\n                var oldIndex = 0;\n                for (var i = 0; i < newSources.length; i++) {\n                    var source = newSources[i];\n                    var sourceKey = newKeys[i];\n                    if (sourceKey === keys[oldIndex]) {\n                        results[i] = oldTargets[oldIndex];\n                        updateResult(source, oldTargets[oldIndex], i);\n                        oldIndex++;\n                    } else {\n                        var found = false;\n                        for (var j = 1; j < keys.length; j++) {\n                            var searchIndex = (oldIndex + j) % keys.length;\n                            if (keys[searchIndex] === sourceKey) {\n                                results[i] = oldTargets[searchIndex];\n                                updateResult(newSources[i], oldTargets[searchIndex], i);\n                                oldIndex = searchIndex + 1;\n                                found = true;\n                                break;\n                            }\n                        }\n                        if (!found) {\n                            results[i] = createResult(source, i);\n                        }\n                    }\n                }\n                results.length = newSources.length;\n                keys = newKeys;\n            }\n        };\n    };\n    /**\n * Creates a [[Projector]] instance using the provided projectionOptions.\n *\n * For more information, see [[Projector]].\n *\n * @param projectionOptions   Options that influence how the DOM is rendered and updated.\n */\n    exports.createProjector = function (projectorOptions) {\n        var projector;\n        var projectionOptions = applyDefaultProjectionOptions(projectorOptions);\n        projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {\n            return function () {\n                // intercept function calls (event handlers) to do a render afterwards.\n                projector.scheduleRender();\n                return eventHandler.apply(properties.bind || this, arguments);\n            };\n        };\n        var renderCompleted = true;\n        var scheduled;\n        var stopped = false;\n        var projections = [];\n        var renderFunctions = [];\n        // matches the projections array\n        var doRender = function () {\n            scheduled = undefined;\n            if (!renderCompleted) {\n                return;    // The last render threw an error, it should be logged in the browser console.\n            }\n            renderCompleted = false;\n            for (var i = 0; i < projections.length; i++) {\n                var updatedVnode = renderFunctions[i]();\n                projections[i].update(updatedVnode);\n            }\n            renderCompleted = true;\n        };\n        projector = {\n            scheduleRender: function () {\n                if (!scheduled && !stopped) {\n                    scheduled = requestAnimationFrame(doRender);\n                }\n            },\n            stop: function () {\n                if (scheduled) {\n                    cancelAnimationFrame(scheduled);\n                    scheduled = undefined;\n                }\n                stopped = true;\n            },\n            resume: function () {\n                stopped = false;\n                renderCompleted = true;\n                projector.scheduleRender();\n            },\n            append: function (parentNode, renderMaquetteFunction) {\n                projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            insertBefore: function (beforeNode, renderMaquetteFunction) {\n                projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            merge: function (domNode, renderMaquetteFunction) {\n                projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            replace: function (domNode, renderMaquetteFunction) {\n                var vnode = renderMaquetteFunction();\n                createDom(vnode, domNode.parentNode, domNode, projectionOptions);\n                domNode.parentNode.removeChild(domNode);\n                projections.push(createProjection(vnode, projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            detach: function (renderMaquetteFunction) {\n                for (var i = 0; i < renderFunctions.length; i++) {\n                    if (renderFunctions[i] === renderMaquetteFunction) {\n                        renderFunctions.splice(i, 1);\n                        return projections.splice(i, 1)[0];\n                    }\n                }\n                throw new Error('renderMaquetteFunction was not found');\n            }\n        };\n        return projector;\n    };\n}));\n\n\n/* ---- utils/Animation.coffee ---- */\n\n\n(function() {\n  var Animation;\n\n  Animation = (function() {\n    function Animation() {}\n\n    Animation.prototype.slideDown = function(elem, props) {\n      var cstyle, h, margin_bottom, margin_top, padding_bottom, padding_top, transition;\n      if (elem.offsetTop > 2000) {\n        return;\n      }\n      h = elem.offsetHeight;\n      cstyle = window.getComputedStyle(elem);\n      margin_top = cstyle.marginTop;\n      margin_bottom = cstyle.marginBottom;\n      padding_top = cstyle.paddingTop;\n      padding_bottom = cstyle.paddingBottom;\n      transition = cstyle.transition;\n      elem.style.boxSizing = \"border-box\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(0.6)\";\n      elem.style.opacity = \"0\";\n      elem.style.height = \"0px\";\n      elem.style.marginTop = \"0px\";\n      elem.style.marginBottom = \"0px\";\n      elem.style.paddingTop = \"0px\";\n      elem.style.paddingBottom = \"0px\";\n      elem.style.transition = \"none\";\n      setTimeout((function() {\n        elem.className += \" animate-inout\";\n        elem.style.height = h + \"px\";\n        elem.style.transform = \"scale(1)\";\n        elem.style.opacity = \"1\";\n        elem.style.marginTop = margin_top;\n        elem.style.marginBottom = margin_bottom;\n        elem.style.paddingTop = padding_top;\n        return elem.style.paddingBottom = padding_bottom;\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate-inout\");\n        elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null;\n        elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null;\n        elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null;\n        return elem.removeEventListener(\"transitionend\", arguments.callee, false);\n      });\n    };\n\n    Animation.prototype.slideUp = function(elem, remove_func, props) {\n      if (elem.offsetTop > 1000) {\n        return remove_func();\n      }\n      elem.className += \" animate-back\";\n      elem.style.boxSizing = \"border-box\";\n      elem.style.height = elem.offsetHeight + \"px\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(1)\";\n      elem.style.opacity = \"1\";\n      elem.style.pointerEvents = \"none\";\n      setTimeout((function() {\n        elem.style.height = \"0px\";\n        elem.style.marginTop = \"0px\";\n        elem.style.marginBottom = \"0px\";\n        elem.style.paddingTop = \"0px\";\n        elem.style.paddingBottom = \"0px\";\n        elem.style.transform = \"scale(0.8)\";\n        elem.style.borderTopWidth = \"0px\";\n        elem.style.borderBottomWidth = \"0px\";\n        return elem.style.opacity = \"0\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\" || e.elapsedTime >= 0.6) {\n          elem.removeEventListener(\"transitionend\", arguments.callee, false);\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.slideUpInout = function(elem, remove_func, props) {\n      elem.className += \" animate-inout\";\n      elem.style.boxSizing = \"border-box\";\n      elem.style.height = elem.offsetHeight + \"px\";\n      elem.style.overflow = \"hidden\";\n      elem.style.transform = \"scale(1)\";\n      elem.style.opacity = \"1\";\n      elem.style.pointerEvents = \"none\";\n      setTimeout((function() {\n        elem.style.height = \"0px\";\n        elem.style.marginTop = \"0px\";\n        elem.style.marginBottom = \"0px\";\n        elem.style.paddingTop = \"0px\";\n        elem.style.paddingBottom = \"0px\";\n        elem.style.transform = \"scale(0.8)\";\n        elem.style.borderTopWidth = \"0px\";\n        elem.style.borderBottomWidth = \"0px\";\n        return elem.style.opacity = \"0\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\" || e.elapsedTime >= 0.6) {\n          elem.removeEventListener(\"transitionend\", arguments.callee, false);\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.showRight = function(elem, props) {\n      elem.className += \" animate\";\n      elem.style.opacity = 0;\n      elem.style.transform = \"TranslateX(-20px) Scale(1.01)\";\n      setTimeout((function() {\n        elem.style.opacity = 1;\n        return elem.style.transform = \"TranslateX(0px) Scale(1)\";\n      }), 1);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate\");\n        return elem.style.transform = elem.style.opacity = null;\n      });\n    };\n\n    Animation.prototype.show = function(elem, props) {\n      var delay, ref;\n      delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;\n      elem.style.opacity = 0;\n      setTimeout((function() {\n        return elem.className += \" animate\";\n      }), 1);\n      setTimeout((function() {\n        return elem.style.opacity = 1;\n      }), delay);\n      return elem.addEventListener(\"transitionend\", function() {\n        elem.classList.remove(\"animate\");\n        elem.style.opacity = null;\n        return elem.removeEventListener(\"transitionend\", arguments.callee, false);\n      });\n    };\n\n    Animation.prototype.hide = function(elem, remove_func, props) {\n      var delay, ref;\n      delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;\n      elem.className += \" animate\";\n      setTimeout((function() {\n        return elem.style.opacity = 0;\n      }), delay);\n      return elem.addEventListener(\"transitionend\", function(e) {\n        if (e.propertyName === \"opacity\") {\n          return remove_func();\n        }\n      });\n    };\n\n    Animation.prototype.addVisibleClass = function(elem, props) {\n      return setTimeout(function() {\n        return elem.classList.add(\"visible\");\n      });\n    };\n\n    return Animation;\n\n  })();\n\n  window.Animation = new Animation();\n\n}).call(this);\n\n\n/* ---- utils/Dollar.coffee ---- */\n\n\n(function() {\n  window.$ = function(selector) {\n    if (selector.startsWith(\"#\")) {\n      return document.getElementById(selector.replace(\"#\", \"\"));\n    }\n  };\n\n}).call(this);\n\n\n/* ---- utils/ZeroFrame.coffee ---- */\n\n\n(function() {\n  var ZeroFrame,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  ZeroFrame = (function(superClass) {\n    extend(ZeroFrame, superClass);\n\n    function ZeroFrame(url) {\n      this.onCloseWebsocket = bind(this.onCloseWebsocket, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      this.onRequest = bind(this.onRequest, this);\n      this.onMessage = bind(this.onMessage, this);\n      this.url = url;\n      this.waiting_cb = {};\n      this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\");\n      this.connect();\n      this.next_message_id = 1;\n      this.history_state = {};\n      this.init();\n    }\n\n    ZeroFrame.prototype.init = function() {\n      return this;\n    };\n\n    ZeroFrame.prototype.connect = function() {\n      this.target = window.parent;\n      window.addEventListener(\"message\", this.onMessage, false);\n      this.cmd(\"innerReady\");\n      window.addEventListener(\"beforeunload\", (function(_this) {\n        return function(e) {\n          _this.log(\"save scrollTop\", window.pageYOffset);\n          _this.history_state[\"scrollTop\"] = window.pageYOffset;\n          return _this.cmd(\"wrapperReplaceState\", [_this.history_state, null]);\n        };\n      })(this));\n      return this.cmd(\"wrapperGetState\", [], (function(_this) {\n        return function(state) {\n          if (state != null) {\n            _this.history_state = state;\n          }\n          _this.log(\"restore scrollTop\", state, window.pageYOffset);\n          if (window.pageYOffset === 0 && state) {\n            return window.scroll(window.pageXOffset, state.scrollTop);\n          }\n        };\n      })(this));\n    };\n\n    ZeroFrame.prototype.onMessage = function(e) {\n      var cmd, message;\n      message = e.data;\n      cmd = message.cmd;\n      if (cmd === \"response\") {\n        if (this.waiting_cb[message.to] != null) {\n          return this.waiting_cb[message.to](message.result);\n        } else {\n          return this.log(\"Websocket callback not found:\", message);\n        }\n      } else if (cmd === \"wrapperReady\") {\n        return this.cmd(\"innerReady\");\n      } else if (cmd === \"ping\") {\n        return this.response(message.id, \"pong\");\n      } else if (cmd === \"wrapperOpenedWebsocket\") {\n        return this.onOpenWebsocket();\n      } else if (cmd === \"wrapperClosedWebsocket\") {\n        return this.onCloseWebsocket();\n      } else {\n        return this.onRequest(cmd, message.params);\n      }\n    };\n\n    ZeroFrame.prototype.onRequest = function(cmd, message) {\n      return this.log(\"Unknown request\", message);\n    };\n\n    ZeroFrame.prototype.response = function(to, result) {\n      return this.send({\n        \"cmd\": \"response\",\n        \"to\": to,\n        \"result\": result\n      });\n    };\n\n    ZeroFrame.prototype.cmd = function(cmd, params, cb) {\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      return this.send({\n        \"cmd\": cmd,\n        \"params\": params\n      }, cb);\n    };\n\n    ZeroFrame.prototype.send = function(message, cb) {\n      if (cb == null) {\n        cb = null;\n      }\n      message.wrapper_nonce = this.wrapper_nonce;\n      message.id = this.next_message_id;\n      this.next_message_id += 1;\n      this.target.postMessage(message, \"*\");\n      if (cb) {\n        return this.waiting_cb[message.id] = cb;\n      }\n    };\n\n    ZeroFrame.prototype.onOpenWebsocket = function() {\n      return this.log(\"Websocket open\");\n    };\n\n    ZeroFrame.prototype.onCloseWebsocket = function() {\n      return this.log(\"Websocket close\");\n    };\n\n    return ZeroFrame;\n\n  })(Class);\n\n  window.ZeroFrame = ZeroFrame;\n\n}).call(this);\n\n\n/* ---- PluginList.coffee ---- */\n\n\n(function() {\n  var PluginList,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  PluginList = (function(superClass) {\n    extend(PluginList, superClass);\n\n    function PluginList(plugins) {\n      this.handleDeleteClick = bind(this.handleDeleteClick, this);\n      this.handleUpdateClick = bind(this.handleUpdateClick, this);\n      this.handleResetClick = bind(this.handleResetClick, this);\n      this.handleCheckboxChange = bind(this.handleCheckboxChange, this);\n      this.savePluginStatus = bind(this.savePluginStatus, this);\n      this.plugins = plugins;\n    }\n\n    PluginList.prototype.savePluginStatus = function(plugin, is_enabled) {\n      Page.cmd(\"pluginConfigSet\", [plugin.source, plugin.inner_path, \"enabled\", is_enabled], (function(_this) {\n        return function(res) {\n          if (res === \"ok\") {\n            return Page.updatePlugins();\n          } else {\n            return Page.cmd(\"wrapperNotification\", [\"error\", res.error]);\n          }\n        };\n      })(this));\n      return Page.projector.scheduleRender();\n    };\n\n    PluginList.prototype.handleCheckboxChange = function(e) {\n      var node, plugin, value;\n      node = e.currentTarget;\n      plugin = node[\"data-plugin\"];\n      node.classList.toggle(\"checked\");\n      value = node.classList.contains(\"checked\");\n      return this.savePluginStatus(plugin, value);\n    };\n\n    PluginList.prototype.handleResetClick = function(e) {\n      var node, plugin;\n      node = e.currentTarget;\n      plugin = node[\"data-plugin\"];\n      return this.savePluginStatus(plugin, null);\n    };\n\n    PluginList.prototype.handleUpdateClick = function(e) {\n      var node, plugin;\n      node = e.currentTarget;\n      plugin = node[\"data-plugin\"];\n      node.classList.add(\"loading\");\n      Page.cmd(\"pluginUpdate\", [plugin.source, plugin.inner_path], (function(_this) {\n        return function(res) {\n          if (res === \"ok\") {\n            Page.cmd(\"wrapperNotification\", [\"done\", \"Plugin \" + plugin.name + \" updated to latest version\"]);\n            Page.updatePlugins();\n          } else {\n            Page.cmd(\"wrapperNotification\", [\"error\", res.error]);\n          }\n          return node.classList.remove(\"loading\");\n        };\n      })(this));\n      return false;\n    };\n\n    PluginList.prototype.handleDeleteClick = function(e) {\n      var node, plugin;\n      node = e.currentTarget;\n      plugin = node[\"data-plugin\"];\n      if (plugin.loaded) {\n        Page.cmd(\"wrapperNotification\", [\"info\", \"You can only delete plugin that are not currently active\"]);\n        return false;\n      }\n      node.classList.add(\"loading\");\n      Page.cmd(\"wrapperConfirm\", [\"Delete \" + plugin.name + \" plugin?\", \"Delete\"], (function(_this) {\n        return function(res) {\n          if (!res) {\n            node.classList.remove(\"loading\");\n            return false;\n          }\n          return Page.cmd(\"pluginRemove\", [plugin.source, plugin.inner_path], function(res) {\n            if (res === \"ok\") {\n              Page.cmd(\"wrapperNotification\", [\"done\", \"Plugin \" + plugin.name + \" deleted\"]);\n              Page.updatePlugins();\n            } else {\n              Page.cmd(\"wrapperNotification\", [\"error\", res.error]);\n            }\n            return node.classList.remove(\"loading\");\n          });\n        };\n      })(this));\n      return false;\n    };\n\n    PluginList.prototype.render = function() {\n      return h(\"div.plugins\", this.plugins.map((function(_this) {\n        return function(plugin) {\n          var base, descr, enabled_default, is_changed, is_pending, marker_title, ref, tag_delete, tag_source, tag_update, tag_version;\n          if (!plugin.info) {\n            return;\n          }\n          descr = plugin.info.description;\n          if ((base = plugin.info)[\"default\"] == null) {\n            base[\"default\"] = \"enabled\";\n          }\n          if (plugin.info[\"default\"]) {\n            descr += \" (default: \" + plugin.info[\"default\"] + \")\";\n          }\n          tag_version = \"\";\n          tag_source = \"\";\n          tag_delete = \"\";\n          if (plugin.source !== \"builtin\") {\n            tag_update = \"\";\n            if ((ref = plugin.site_info) != null ? ref.rev : void 0) {\n              if (plugin.site_info.rev > plugin.info.rev) {\n                tag_update = h(\"a.version-update.button\", {\n                  href: \"#Update+plugin\",\n                  onclick: _this.handleUpdateClick,\n                  \"data-plugin\": plugin\n                }, \"Update to rev\" + plugin.site_info.rev);\n              }\n            } else {\n              tag_update = h(\"span.version-missing\", \"(unable to get latest vesion: update site missing)\");\n            }\n            tag_version = h(\"span.version\", [\"rev\" + plugin.info.rev + \" \", tag_update]);\n            tag_source = h(\"div.source\", [\n              \"Source: \", h(\"a\", {\n                \"href\": \"/\" + plugin.source,\n                \"target\": \"_top\"\n              }, plugin.site_title ? plugin.site_title : plugin.source), \" /\" + plugin.inner_path\n            ]);\n            tag_delete = h(\"a.delete\", {\n              \"href\": \"#Delete+plugin\",\n              onclick: _this.handleDeleteClick,\n              \"data-plugin\": plugin\n            }, \"Delete plugin\");\n          }\n          enabled_default = plugin.info[\"default\"] === \"enabled\";\n          if (plugin.enabled !== plugin.loaded || plugin.updated) {\n            marker_title = \"Change pending\";\n            is_pending = true;\n          } else {\n            marker_title = \"Changed from default status (click to reset to \" + plugin.info[\"default\"] + \")\";\n            is_pending = false;\n          }\n          is_changed = plugin.enabled !== enabled_default && plugin.owner === \"builtin\";\n          return h(\"div.plugin\", {\n            key: plugin.name\n          }, [\n            h(\"div.title\", [h(\"h3\", [plugin.name, tag_version]), h(\"div.description\", [descr, tag_source, tag_delete])]), h(\"div.value.value-right\", h(\"div.checkbox\", {\n              onclick: _this.handleCheckboxChange,\n              \"data-plugin\": plugin,\n              classes: {\n                checked: plugin.enabled\n              }\n            }, h(\"div.checkbox-skin\")), h(\"a.marker\", {\n              href: \"#Reset\",\n              title: marker_title,\n              onclick: _this.handleResetClick,\n              \"data-plugin\": plugin,\n              classes: {\n                visible: is_pending || is_changed,\n                pending: is_pending\n              }\n            }, \"\\u2022\"))\n          ]);\n        };\n      })(this)));\n    };\n\n    return PluginList;\n\n  })(Class);\n\n  window.PluginList = PluginList;\n\n}).call(this);\n\n\n/* ---- UiPluginManager.coffee ---- */\n\n\n(function() {\n  var UiPluginManager,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    hasProp = {}.hasOwnProperty;\n\n  window.h = maquette.h;\n\n  UiPluginManager = (function(superClass) {\n    extend(UiPluginManager, superClass);\n\n    function UiPluginManager() {\n      this.renderBottomRestart = bind(this.renderBottomRestart, this);\n      this.handleRestartClick = bind(this.handleRestartClick, this);\n      this.render = bind(this.render, this);\n      this.createProjector = bind(this.createProjector, this);\n      this.updatePlugins = bind(this.updatePlugins, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      return UiPluginManager.__super__.constructor.apply(this, arguments);\n    }\n\n    UiPluginManager.prototype.init = function() {\n      this.plugin_list_builtin = new PluginList();\n      this.plugin_list_custom = new PluginList();\n      this.plugins_changed = null;\n      this.need_restart = null;\n      return this;\n    };\n\n    UiPluginManager.prototype.onOpenWebsocket = function() {\n      this.cmd(\"wrapperSetTitle\", \"Plugin manager - ZeroNet\");\n      this.cmd(\"serverInfo\", {}, (function(_this) {\n        return function(server_info) {\n          return _this.server_info = server_info;\n        };\n      })(this));\n      return this.updatePlugins();\n    };\n\n    UiPluginManager.prototype.updatePlugins = function(cb) {\n      return this.cmd(\"pluginList\", [], (function(_this) {\n        return function(res) {\n          var item, plugins_builtin, plugins_custom;\n          _this.plugins_changed = (function() {\n            var i, len, ref, results;\n            ref = res.plugins;\n            results = [];\n            for (i = 0, len = ref.length; i < len; i++) {\n              item = ref[i];\n              if (item.enabled !== item.loaded || item.updated) {\n                results.push(item);\n              }\n            }\n            return results;\n          })();\n          plugins_builtin = (function() {\n            var i, len, ref, results;\n            ref = res.plugins;\n            results = [];\n            for (i = 0, len = ref.length; i < len; i++) {\n              item = ref[i];\n              if (item.source === \"builtin\") {\n                results.push(item);\n              }\n            }\n            return results;\n          })();\n          _this.plugin_list_builtin.plugins = plugins_builtin.sort(function(a, b) {\n            return a.name.localeCompare(b.name);\n          });\n          plugins_custom = (function() {\n            var i, len, ref, results;\n            ref = res.plugins;\n            results = [];\n            for (i = 0, len = ref.length; i < len; i++) {\n              item = ref[i];\n              if (item.source !== \"builtin\") {\n                results.push(item);\n              }\n            }\n            return results;\n          })();\n          _this.plugin_list_custom.plugins = plugins_custom.sort(function(a, b) {\n            return a.name.localeCompare(b.name);\n          });\n          _this.projector.scheduleRender();\n          return typeof cb === \"function\" ? cb() : void 0;\n        };\n      })(this));\n    };\n\n    UiPluginManager.prototype.createProjector = function() {\n      this.projector = maquette.createProjector();\n      this.projector.replace($(\"#content\"), this.render);\n      return this.projector.replace($(\"#bottom-restart\"), this.renderBottomRestart);\n    };\n\n    UiPluginManager.prototype.render = function() {\n      var ref;\n      if (!this.plugin_list_builtin.plugins) {\n        return h(\"div.content\");\n      }\n      return h(\"div.content\", [h(\"div.section\", [((ref = this.plugin_list_custom.plugins) != null ? ref.length : void 0) ? [h(\"h2\", \"Installed third-party plugins\"), this.plugin_list_custom.render()] : void 0, h(\"h2\", \"Built-in plugins\"), this.plugin_list_builtin.render()])]);\n    };\n\n    UiPluginManager.prototype.handleRestartClick = function() {\n      this.restart_loading = true;\n      setTimeout(((function(_this) {\n        return function() {\n          return Page.cmd(\"serverShutdown\", {\n            restart: true\n          });\n        };\n      })(this)), 300);\n      Page.projector.scheduleRender();\n      return false;\n    };\n\n    UiPluginManager.prototype.renderBottomRestart = function() {\n      var ref;\n      return h(\"div.bottom.bottom-restart\", {\n        classes: {\n          visible: (ref = this.plugins_changed) != null ? ref.length : void 0\n        }\n      }, h(\"div.bottom-content\", [\n        h(\"div.title\", \"Some plugins status has been changed\"), h(\"a.button.button-submit.button-restart\", {\n          href: \"#Restart\",\n          classes: {\n            loading: this.restart_loading\n          },\n          onclick: this.handleRestartClick\n        }, \"Restart ZeroNet client\")\n      ]));\n    };\n\n    return UiPluginManager;\n\n  })(ZeroFrame);\n\n  window.Page = new UiPluginManager();\n\n  window.Page.createProjector();\n\n}).call(this);\n"
  },
  {
    "path": "plugins/UiPluginManager/media/js/lib/Class.coffee",
    "content": "class Class\n\ttrace: true\n\n\tlog: (args...) ->\n\t\treturn unless @trace\n\t\treturn if typeof console is 'undefined'\n\t\targs.unshift(\"[#{@.constructor.name}]\")\n\t\tconsole.log(args...)\n\t\t@\n\t\t\n\tlogStart: (name, args...) ->\n\t\treturn unless @trace\n\t\t@logtimers or= {}\n\t\t@logtimers[name] = +(new Date)\n\t\t@log \"#{name}\", args..., \"(started)\" if args.length > 0\n\t\t@\n\t\t\n\tlogEnd: (name, args...) ->\n\t\tms = +(new Date)-@logtimers[name]\n\t\t@log \"#{name}\", args..., \"(Done in #{ms}ms)\"\n\t\t@ \n\nwindow.Class = Class"
  },
  {
    "path": "plugins/UiPluginManager/media/js/lib/Promise.coffee",
    "content": "# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html\n\nclass Promise\n\t@when: (tasks...) ->\n\t\tnum_uncompleted = tasks.length\n\t\targs = new Array(num_uncompleted)\n\t\tpromise = new Promise()\n\n\t\tfor task, task_id in tasks\n\t\t\t((task_id) ->\n\t\t\t\ttask.then(() ->\n\t\t\t\t\targs[task_id] = Array.prototype.slice.call(arguments)\n\t\t\t\t\tnum_uncompleted--\n\t\t\t\t\tpromise.complete.apply(promise, args) if num_uncompleted == 0\n\t\t\t\t)\n\t\t\t)(task_id)\n\n\t\treturn promise\n\n\tconstructor: ->\n\t\t@resolved = false\n\t\t@end_promise = null\n\t\t@result = null\n\t\t@callbacks = []\n\n\tresolve: ->\n\t\tif @resolved\n\t\t\treturn false\n\t\t@resolved = true\n\t\t@data = arguments\n\t\tif not arguments.length\n\t\t\t@data = [true]\n\t\t@result = @data[0]\n\t\tfor callback in @callbacks\n\t\t\tback = callback.apply callback, @data\n\t\tif @end_promise\n\t\t\t@end_promise.resolve(back)\n\n\tfail: ->\n\t\t@resolve(false)\n\n\tthen: (callback) ->\n\t\tif @resolved == true\n\t\t\tcallback.apply callback, @data\n\t\t\treturn\n\n\t\t@callbacks.push callback\n\n\t\t@end_promise = new Promise()\n\nwindow.Promise = Promise\n\n###\ns = Date.now()\nlog = (text) ->\n\tconsole.log Date.now()-s, Array.prototype.slice.call(arguments).join(\", \")\n\nlog \"Started\"\n\ncmd = (query) ->\n\tp = new Promise()\n\tsetTimeout ( ->\n\t\tp.resolve query+\" Result\"\n\t), 100\n\treturn p\n\nback = cmd(\"SELECT * FROM message\").then (res) ->\n\tlog res\n\treturn \"Return from query\"\n.then (res) ->\n\tlog \"Back then\", res\n\nlog \"Query started\", back\n###"
  },
  {
    "path": "plugins/UiPluginManager/media/js/lib/Prototypes.coffee",
    "content": "String::startsWith = (s) -> @[...s.length] is s\nString::endsWith = (s) -> s is '' or @[-s.length..] is s\nString::repeat = (count) -> new Array( count + 1 ).join(@)\n\nwindow.isEmpty = (obj) ->\n\tfor key of obj\n\t\treturn false\n\treturn true\n"
  },
  {
    "path": "plugins/UiPluginManager/media/js/lib/maquette.js",
    "content": "(function (root, factory) {\n    if (typeof define === 'function' && define.amd) {\n        // AMD. Register as an anonymous module.\n        define(['exports'], factory);\n    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {\n        // CommonJS\n        factory(exports);\n    } else {\n        // Browser globals\n        factory(root.maquette = {});\n    }\n}(this, function (exports) {\n    'use strict';\n    ;\n    ;\n    ;\n    ;\n    var NAMESPACE_W3 = 'http://www.w3.org/';\n    var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';\n    var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';\n    // Utilities\n    var emptyArray = [];\n    var extend = function (base, overrides) {\n        var result = {};\n        Object.keys(base).forEach(function (key) {\n            result[key] = base[key];\n        });\n        if (overrides) {\n            Object.keys(overrides).forEach(function (key) {\n                result[key] = overrides[key];\n            });\n        }\n        return result;\n    };\n    // Hyperscript helper functions\n    var same = function (vnode1, vnode2) {\n        if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {\n            return false;\n        }\n        if (vnode1.properties && vnode2.properties) {\n            if (vnode1.properties.key !== vnode2.properties.key) {\n                return false;\n            }\n            return vnode1.properties.bind === vnode2.properties.bind;\n        }\n        return !vnode1.properties && !vnode2.properties;\n    };\n    var toTextVNode = function (data) {\n        return {\n            vnodeSelector: '',\n            properties: undefined,\n            children: undefined,\n            text: data.toString(),\n            domNode: null\n        };\n    };\n    var appendChildren = function (parentSelector, insertions, main) {\n        for (var i = 0; i < insertions.length; i++) {\n            var item = insertions[i];\n            if (Array.isArray(item)) {\n                appendChildren(parentSelector, item, main);\n            } else {\n                if (item !== null && item !== undefined) {\n                    if (!item.hasOwnProperty('vnodeSelector')) {\n                        item = toTextVNode(item);\n                    }\n                    main.push(item);\n                }\n            }\n        }\n    };\n    // Render helper functions\n    var missingTransition = function () {\n        throw new Error('Provide a transitions object to the projectionOptions to do animations');\n    };\n    var DEFAULT_PROJECTION_OPTIONS = {\n        namespace: undefined,\n        eventHandlerInterceptor: undefined,\n        styleApplyer: function (domNode, styleName, value) {\n            // Provides a hook to add vendor prefixes for browsers that still need it.\n            domNode.style[styleName] = value;\n        },\n        transitions: {\n            enter: missingTransition,\n            exit: missingTransition\n        }\n    };\n    var applyDefaultProjectionOptions = function (projectorOptions) {\n        return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);\n    };\n    var checkStyleValue = function (styleValue) {\n        if (typeof styleValue !== 'string') {\n            throw new Error('Style values must be strings');\n        }\n    };\n    var setProperties = function (domNode, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            /* tslint:disable:no-var-keyword: edge case */\n            var propValue = properties[propName];\n            /* tslint:enable:no-var-keyword */\n            if (propName === 'className') {\n                throw new Error('Property \"className\" is not supported, use \"class\".');\n            } else if (propName === 'class') {\n                if (domNode.className) {\n                    // May happen if classes is specified before class\n                    domNode.className += ' ' + propValue;\n                } else {\n                    domNode.className = propValue;\n                }\n            } else if (propName === 'classes') {\n                // object with string keys and boolean values\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    if (propValue[className]) {\n                        domNode.classList.add(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                // object with string keys and string (!) values\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var styleValue = propValue[styleName];\n                    if (styleValue) {\n                        checkStyleValue(styleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, styleValue);\n                    }\n                }\n            } else if (propName === 'key') {\n                continue;\n            } else if (propValue === null || propValue === undefined) {\n                continue;\n            } else {\n                var type = typeof propValue;\n                if (type === 'function') {\n                    if (propName.lastIndexOf('on', 0) === 0) {\n                        if (eventHandlerInterceptor) {\n                            propValue = eventHandlerInterceptor(propName, propValue, domNode, properties);    // intercept eventhandlers\n                        }\n                        if (propName === 'oninput') {\n                            (function () {\n                                // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput\n                                var oldPropValue = propValue;\n                                propValue = function (evt) {\n                                    evt.target['oninput-value'] = evt.target.value;\n                                    // may be HTMLTextAreaElement as well\n                                    oldPropValue.apply(this, [evt]);\n                                };\n                            }());\n                        }\n                        domNode[propName] = propValue;\n                    }\n                } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {\n                    if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                        domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                    } else {\n                        domNode.setAttribute(propName, propValue);\n                    }\n                } else {\n                    domNode[propName] = propValue;\n                }\n            }\n        }\n    };\n    var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {\n        if (!properties) {\n            return;\n        }\n        var propertiesUpdated = false;\n        var propNames = Object.keys(properties);\n        var propCount = propNames.length;\n        for (var i = 0; i < propCount; i++) {\n            var propName = propNames[i];\n            // assuming that properties will be nullified instead of missing is by design\n            var propValue = properties[propName];\n            var previousValue = previousProperties[propName];\n            if (propName === 'class') {\n                if (previousValue !== propValue) {\n                    throw new Error('\"class\" property may not be updated. Use the \"classes\" property for conditional css classes.');\n                }\n            } else if (propName === 'classes') {\n                var classList = domNode.classList;\n                var classNames = Object.keys(propValue);\n                var classNameCount = classNames.length;\n                for (var j = 0; j < classNameCount; j++) {\n                    var className = classNames[j];\n                    var on = !!propValue[className];\n                    var previousOn = !!previousValue[className];\n                    if (on === previousOn) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (on) {\n                        classList.add(className);\n                    } else {\n                        classList.remove(className);\n                    }\n                }\n            } else if (propName === 'styles') {\n                var styleNames = Object.keys(propValue);\n                var styleCount = styleNames.length;\n                for (var j = 0; j < styleCount; j++) {\n                    var styleName = styleNames[j];\n                    var newStyleValue = propValue[styleName];\n                    var oldStyleValue = previousValue[styleName];\n                    if (newStyleValue === oldStyleValue) {\n                        continue;\n                    }\n                    propertiesUpdated = true;\n                    if (newStyleValue) {\n                        checkStyleValue(newStyleValue);\n                        projectionOptions.styleApplyer(domNode, styleName, newStyleValue);\n                    } else {\n                        projectionOptions.styleApplyer(domNode, styleName, '');\n                    }\n                }\n            } else {\n                if (!propValue && typeof previousValue === 'string') {\n                    propValue = '';\n                }\n                if (propName === 'value') {\n                    if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {\n                        domNode[propName] = propValue;\n                        // Reset the value, even if the virtual DOM did not change\n                        domNode['oninput-value'] = undefined;\n                    }\n                    // else do not update the domNode, otherwise the cursor position would be changed\n                    if (propValue !== previousValue) {\n                        propertiesUpdated = true;\n                    }\n                } else if (propValue !== previousValue) {\n                    var type = typeof propValue;\n                    if (type === 'function') {\n                        throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');\n                    }\n                    if (type === 'string' && propName !== 'innerHTML') {\n                        if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {\n                            domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);\n                        } else {\n                            domNode.setAttribute(propName, propValue);\n                        }\n                    } else {\n                        if (domNode[propName] !== propValue) {\n                            domNode[propName] = propValue;\n                        }\n                    }\n                    propertiesUpdated = true;\n                }\n            }\n        }\n        return propertiesUpdated;\n    };\n    var findIndexOfChild = function (children, sameAs, start) {\n        if (sameAs.vnodeSelector !== '') {\n            // Never scan for text-nodes\n            for (var i = start; i < children.length; i++) {\n                if (same(children[i], sameAs)) {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    };\n    var nodeAdded = function (vNode, transitions) {\n        if (vNode.properties) {\n            var enterAnimation = vNode.properties.enterAnimation;\n            if (enterAnimation) {\n                if (typeof enterAnimation === 'function') {\n                    enterAnimation(vNode.domNode, vNode.properties);\n                } else {\n                    transitions.enter(vNode.domNode, vNode.properties, enterAnimation);\n                }\n            }\n        }\n    };\n    var nodeToRemove = function (vNode, transitions) {\n        var domNode = vNode.domNode;\n        if (vNode.properties) {\n            var exitAnimation = vNode.properties.exitAnimation;\n            if (exitAnimation) {\n                domNode.style.pointerEvents = 'none';\n                var removeDomNode = function () {\n                    if (domNode.parentNode) {\n                        domNode.parentNode.removeChild(domNode);\n                    }\n                };\n                if (typeof exitAnimation === 'function') {\n                    exitAnimation(domNode, removeDomNode, vNode.properties);\n                    return;\n                } else {\n                    transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);\n                    return;\n                }\n            }\n        }\n        if (domNode.parentNode) {\n            domNode.parentNode.removeChild(domNode);\n        }\n    };\n    var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {\n        var childNode = childNodes[indexToCheck];\n        if (childNode.vnodeSelector === '') {\n            return;    // Text nodes need not be distinguishable\n        }\n        var properties = childNode.properties;\n        var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;\n        if (!key) {\n            for (var i = 0; i < childNodes.length; i++) {\n                if (i !== indexToCheck) {\n                    var node = childNodes[i];\n                    if (same(node, childNode)) {\n                        if (operation === 'added') {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');\n                        } else {\n                            throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');\n                        }\n                    }\n                }\n            }\n        }\n    };\n    var createDom;\n    var updateDom;\n    var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {\n        if (oldChildren === newChildren) {\n            return false;\n        }\n        oldChildren = oldChildren || emptyArray;\n        newChildren = newChildren || emptyArray;\n        var oldChildrenLength = oldChildren.length;\n        var newChildrenLength = newChildren.length;\n        var transitions = projectionOptions.transitions;\n        var oldIndex = 0;\n        var newIndex = 0;\n        var i;\n        var textUpdated = false;\n        while (newIndex < newChildrenLength) {\n            var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;\n            var newChild = newChildren[newIndex];\n            if (oldChild !== undefined && same(oldChild, newChild)) {\n                textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;\n                oldIndex++;\n            } else {\n                var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);\n                if (findOldIndex >= 0) {\n                    // Remove preceding missing children\n                    for (i = oldIndex; i < findOldIndex; i++) {\n                        nodeToRemove(oldChildren[i], transitions);\n                        checkDistinguishable(oldChildren, i, vnode, 'removed');\n                    }\n                    textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;\n                    oldIndex = findOldIndex + 1;\n                } else {\n                    // New child\n                    createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);\n                    nodeAdded(newChild, transitions);\n                    checkDistinguishable(newChildren, newIndex, vnode, 'added');\n                }\n            }\n            newIndex++;\n        }\n        if (oldChildrenLength > oldIndex) {\n            // Remove child fragments\n            for (i = oldIndex; i < oldChildrenLength; i++) {\n                nodeToRemove(oldChildren[i], transitions);\n                checkDistinguishable(oldChildren, i, vnode, 'removed');\n            }\n        }\n        return textUpdated;\n    };\n    var addChildren = function (domNode, children, projectionOptions) {\n        if (!children) {\n            return;\n        }\n        for (var i = 0; i < children.length; i++) {\n            createDom(children[i], domNode, undefined, projectionOptions);\n        }\n    };\n    var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {\n        addChildren(domNode, vnode.children, projectionOptions);\n        // children before properties, needed for value property of <select>.\n        if (vnode.text) {\n            domNode.textContent = vnode.text;\n        }\n        setProperties(domNode, vnode.properties, projectionOptions);\n        if (vnode.properties && vnode.properties.afterCreate) {\n            vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n        }\n    };\n    createDom = function (vnode, parentNode, insertBefore, projectionOptions) {\n        var domNode, i, c, start = 0, type, found;\n        var vnodeSelector = vnode.vnodeSelector;\n        if (vnodeSelector === '') {\n            domNode = vnode.domNode = document.createTextNode(vnode.text);\n            if (insertBefore !== undefined) {\n                parentNode.insertBefore(domNode, insertBefore);\n            } else {\n                parentNode.appendChild(domNode);\n            }\n        } else {\n            for (i = 0; i <= vnodeSelector.length; ++i) {\n                c = vnodeSelector.charAt(i);\n                if (i === vnodeSelector.length || c === '.' || c === '#') {\n                    type = vnodeSelector.charAt(start - 1);\n                    found = vnodeSelector.slice(start, i);\n                    if (type === '.') {\n                        domNode.classList.add(found);\n                    } else if (type === '#') {\n                        domNode.id = found;\n                    } else {\n                        if (found === 'svg') {\n                            projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n                        }\n                        if (projectionOptions.namespace !== undefined) {\n                            domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);\n                        } else {\n                            domNode = vnode.domNode = document.createElement(found);\n                        }\n                        if (insertBefore !== undefined) {\n                            parentNode.insertBefore(domNode, insertBefore);\n                        } else {\n                            parentNode.appendChild(domNode);\n                        }\n                    }\n                    start = i + 1;\n                }\n            }\n            initPropertiesAndChildren(domNode, vnode, projectionOptions);\n        }\n    };\n    updateDom = function (previous, vnode, projectionOptions) {\n        var domNode = previous.domNode;\n        var textUpdated = false;\n        if (previous === vnode) {\n            return false;    // By contract, VNode objects may not be modified anymore after passing them to maquette\n        }\n        var updated = false;\n        if (vnode.vnodeSelector === '') {\n            if (vnode.text !== previous.text) {\n                var newVNode = document.createTextNode(vnode.text);\n                domNode.parentNode.replaceChild(newVNode, domNode);\n                vnode.domNode = newVNode;\n                textUpdated = true;\n                return textUpdated;\n            }\n        } else {\n            if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {\n                projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });\n            }\n            if (previous.text !== vnode.text) {\n                updated = true;\n                if (vnode.text === undefined) {\n                    domNode.removeChild(domNode.firstChild);    // the only textnode presumably\n                } else {\n                    domNode.textContent = vnode.text;\n                }\n            }\n            updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;\n            updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;\n            if (vnode.properties && vnode.properties.afterUpdate) {\n                vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);\n            }\n        }\n        if (updated && vnode.properties && vnode.properties.updateAnimation) {\n            vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);\n        }\n        vnode.domNode = previous.domNode;\n        return textUpdated;\n    };\n    var createProjection = function (vnode, projectionOptions) {\n        return {\n            update: function (updatedVnode) {\n                if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {\n                    throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');\n                }\n                updateDom(vnode, updatedVnode, projectionOptions);\n                vnode = updatedVnode;\n            },\n            domNode: vnode.domNode\n        };\n    };\n    ;\n    // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.\n    exports.h = function (selector) {\n        var properties = arguments[1];\n        if (typeof selector !== 'string') {\n            throw new Error();\n        }\n        var childIndex = 1;\n        if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {\n            childIndex = 2;\n        } else {\n            // Optional properties argument was omitted\n            properties = undefined;\n        }\n        var text = undefined;\n        var children = undefined;\n        var argsLength = arguments.length;\n        // Recognize a common special case where there is only a single text node\n        if (argsLength === childIndex + 1) {\n            var onlyChild = arguments[childIndex];\n            if (typeof onlyChild === 'string') {\n                text = onlyChild;\n            } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {\n                text = onlyChild[0];\n            }\n        }\n        if (text === undefined) {\n            children = [];\n            for (; childIndex < arguments.length; childIndex++) {\n                var child = arguments[childIndex];\n                if (child === null || child === undefined) {\n                    continue;\n                } else if (Array.isArray(child)) {\n                    appendChildren(selector, child, children);\n                } else if (child.hasOwnProperty('vnodeSelector')) {\n                    children.push(child);\n                } else {\n                    children.push(toTextVNode(child));\n                }\n            }\n        }\n        return {\n            vnodeSelector: selector,\n            properties: properties,\n            children: children,\n            text: text === '' ? undefined : text,\n            domNode: null\n        };\n    };\n    /**\n * Contains simple low-level utility functions to manipulate the real DOM.\n */\n    exports.dom = {\n        /**\n     * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in\n     * its [[Projection.domNode|domNode]] property.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection.\n     * @returns The [[Projection]] which also contains the DOM Node that was created.\n     */\n        create: function (vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, document.createElement('div'), undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Appends a new childnode to the DOM which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param parentNode - The parent node for the new childNode.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]\n     * objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the [[Projection]].\n     * @returns The [[Projection]] that was created.\n     */\n        append: function (parentNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, parentNode, undefined, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Inserts a new DOM node which is generated from a [[VNode]].\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param beforeNode - The node that the DOM Node is inserted before.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.\n     * NOTE: [[VNode]] objects may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        insertBefore: function (beforeNode, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        },\n        /**\n     * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.\n     * This means that the virtual DOM and the real DOM will have one overlapping element.\n     * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.\n     * This is a low-level method. Users wil typically use a [[Projector]] instead.\n     * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.\n     * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects\n     * may only be rendered once.\n     * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].\n     * @returns The [[Projection]] that was created.\n     */\n        merge: function (element, vnode, projectionOptions) {\n            projectionOptions = applyDefaultProjectionOptions(projectionOptions);\n            vnode.domNode = element;\n            initPropertiesAndChildren(element, vnode, projectionOptions);\n            return createProjection(vnode, projectionOptions);\n        }\n    };\n    /**\n * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.\n * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.\n * For more information, see [[CalculationCache]].\n *\n * @param <Result> The type of the value that is cached.\n */\n    exports.createCache = function () {\n        var cachedInputs = undefined;\n        var cachedOutcome = undefined;\n        var result = {\n            invalidate: function () {\n                cachedOutcome = undefined;\n                cachedInputs = undefined;\n            },\n            result: function (inputs, calculation) {\n                if (cachedInputs) {\n                    for (var i = 0; i < inputs.length; i++) {\n                        if (cachedInputs[i] !== inputs[i]) {\n                            cachedOutcome = undefined;\n                        }\n                    }\n                }\n                if (!cachedOutcome) {\n                    cachedOutcome = calculation();\n                    cachedInputs = inputs;\n                }\n                return cachedOutcome;\n            }\n        };\n        return result;\n    };\n    /**\n * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.\n * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.\n *\n * @param <Source>       The type of source items. A database-record for instance.\n * @param <Target>       The type of target items. A [[Component]] for instance.\n * @param getSourceKey   `function(source)` that must return a key to identify each source object. The result must either be a string or a number.\n * @param createResult   `function(source, index)` that must create a new result object from a given source. This function is identical\n *                       to the `callback` argument in `Array.map(callback)`.\n * @param updateResult   `function(source, target, index)` that updates a result to an updated source.\n */\n    exports.createMapping = function (getSourceKey, createResult, updateResult) {\n        var keys = [];\n        var results = [];\n        return {\n            results: results,\n            map: function (newSources) {\n                var newKeys = newSources.map(getSourceKey);\n                var oldTargets = results.slice();\n                var oldIndex = 0;\n                for (var i = 0; i < newSources.length; i++) {\n                    var source = newSources[i];\n                    var sourceKey = newKeys[i];\n                    if (sourceKey === keys[oldIndex]) {\n                        results[i] = oldTargets[oldIndex];\n                        updateResult(source, oldTargets[oldIndex], i);\n                        oldIndex++;\n                    } else {\n                        var found = false;\n                        for (var j = 1; j < keys.length; j++) {\n                            var searchIndex = (oldIndex + j) % keys.length;\n                            if (keys[searchIndex] === sourceKey) {\n                                results[i] = oldTargets[searchIndex];\n                                updateResult(newSources[i], oldTargets[searchIndex], i);\n                                oldIndex = searchIndex + 1;\n                                found = true;\n                                break;\n                            }\n                        }\n                        if (!found) {\n                            results[i] = createResult(source, i);\n                        }\n                    }\n                }\n                results.length = newSources.length;\n                keys = newKeys;\n            }\n        };\n    };\n    /**\n * Creates a [[Projector]] instance using the provided projectionOptions.\n *\n * For more information, see [[Projector]].\n *\n * @param projectionOptions   Options that influence how the DOM is rendered and updated.\n */\n    exports.createProjector = function (projectorOptions) {\n        var projector;\n        var projectionOptions = applyDefaultProjectionOptions(projectorOptions);\n        projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {\n            return function () {\n                // intercept function calls (event handlers) to do a render afterwards.\n                projector.scheduleRender();\n                return eventHandler.apply(properties.bind || this, arguments);\n            };\n        };\n        var renderCompleted = true;\n        var scheduled;\n        var stopped = false;\n        var projections = [];\n        var renderFunctions = [];\n        // matches the projections array\n        var doRender = function () {\n            scheduled = undefined;\n            if (!renderCompleted) {\n                return;    // The last render threw an error, it should be logged in the browser console.\n            }\n            renderCompleted = false;\n            for (var i = 0; i < projections.length; i++) {\n                var updatedVnode = renderFunctions[i]();\n                projections[i].update(updatedVnode);\n            }\n            renderCompleted = true;\n        };\n        projector = {\n            scheduleRender: function () {\n                if (!scheduled && !stopped) {\n                    scheduled = requestAnimationFrame(doRender);\n                }\n            },\n            stop: function () {\n                if (scheduled) {\n                    cancelAnimationFrame(scheduled);\n                    scheduled = undefined;\n                }\n                stopped = true;\n            },\n            resume: function () {\n                stopped = false;\n                renderCompleted = true;\n                projector.scheduleRender();\n            },\n            append: function (parentNode, renderMaquetteFunction) {\n                projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            insertBefore: function (beforeNode, renderMaquetteFunction) {\n                projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            merge: function (domNode, renderMaquetteFunction) {\n                projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            replace: function (domNode, renderMaquetteFunction) {\n                var vnode = renderMaquetteFunction();\n                createDom(vnode, domNode.parentNode, domNode, projectionOptions);\n                domNode.parentNode.removeChild(domNode);\n                projections.push(createProjection(vnode, projectionOptions));\n                renderFunctions.push(renderMaquetteFunction);\n            },\n            detach: function (renderMaquetteFunction) {\n                for (var i = 0; i < renderFunctions.length; i++) {\n                    if (renderFunctions[i] === renderMaquetteFunction) {\n                        renderFunctions.splice(i, 1);\n                        return projections.splice(i, 1)[0];\n                    }\n                }\n                throw new Error('renderMaquetteFunction was not found');\n            }\n        };\n        return projector;\n    };\n}));\n"
  },
  {
    "path": "plugins/UiPluginManager/media/js/utils/Animation.coffee",
    "content": "class Animation\n\tslideDown: (elem, props) ->\n\t\tif elem.offsetTop > 2000\n\t\t\treturn\n\n\t\th = elem.offsetHeight\n\t\tcstyle = window.getComputedStyle(elem)\n\t\tmargin_top = cstyle.marginTop\n\t\tmargin_bottom = cstyle.marginBottom\n\t\tpadding_top = cstyle.paddingTop\n\t\tpadding_bottom = cstyle.paddingBottom\n\t\ttransition = cstyle.transition\n\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(0.6)\"\n\t\telem.style.opacity = \"0\"\n\t\telem.style.height = \"0px\"\n\t\telem.style.marginTop = \"0px\"\n\t\telem.style.marginBottom = \"0px\"\n\t\telem.style.paddingTop = \"0px\"\n\t\telem.style.paddingBottom = \"0px\"\n\t\telem.style.transition = \"none\"\n\n\t\tsetTimeout (->\n\t\t\telem.className += \" animate-inout\"\n\t\t\telem.style.height = h+\"px\"\n\t\t\telem.style.transform = \"scale(1)\"\n\t\t\telem.style.opacity = \"1\"\n\t\t\telem.style.marginTop = margin_top\n\t\t\telem.style.marginBottom = margin_bottom\n\t\t\telem.style.paddingTop = padding_top\n\t\t\telem.style.paddingBottom = padding_bottom\n\t\t), 1\n\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate-inout\")\n\t\t\telem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null\n\t\t\telem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null\n\t\t\telem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null\n\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\n\n\tslideUp: (elem, remove_func, props) ->\n\t\tif elem.offsetTop > 1000\n\t\t\treturn remove_func()\n\n\t\telem.className += \" animate-back\"\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.height = elem.offsetHeight+\"px\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(1)\"\n\t\telem.style.opacity = \"1\"\n\t\telem.style.pointerEvents = \"none\"\n\t\tsetTimeout (->\n\t\t\telem.style.height = \"0px\"\n\t\t\telem.style.marginTop = \"0px\"\n\t\t\telem.style.marginBottom = \"0px\"\n\t\t\telem.style.paddingTop = \"0px\"\n\t\t\telem.style.paddingBottom = \"0px\"\n\t\t\telem.style.transform = \"scale(0.8)\"\n\t\t\telem.style.borderTopWidth = \"0px\"\n\t\t\telem.style.borderBottomWidth = \"0px\"\n\t\t\telem.style.opacity = \"0\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\" or e.elapsedTime >= 0.6\n\t\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\t\t\t\tremove_func()\n\n\n\tslideUpInout: (elem, remove_func, props) ->\n\t\telem.className += \" animate-inout\"\n\t\telem.style.boxSizing = \"border-box\"\n\t\telem.style.height = elem.offsetHeight+\"px\"\n\t\telem.style.overflow = \"hidden\"\n\t\telem.style.transform = \"scale(1)\"\n\t\telem.style.opacity = \"1\"\n\t\telem.style.pointerEvents = \"none\"\n\t\tsetTimeout (->\n\t\t\telem.style.height = \"0px\"\n\t\t\telem.style.marginTop = \"0px\"\n\t\t\telem.style.marginBottom = \"0px\"\n\t\t\telem.style.paddingTop = \"0px\"\n\t\t\telem.style.paddingBottom = \"0px\"\n\t\t\telem.style.transform = \"scale(0.8)\"\n\t\t\telem.style.borderTopWidth = \"0px\"\n\t\t\telem.style.borderBottomWidth = \"0px\"\n\t\t\telem.style.opacity = \"0\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\" or e.elapsedTime >= 0.6\n\t\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\t\t\t\tremove_func()\n\n\n\tshowRight: (elem, props) ->\n\t\telem.className += \" animate\"\n\t\telem.style.opacity = 0\n\t\telem.style.transform = \"TranslateX(-20px) Scale(1.01)\"\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 1\n\t\t\telem.style.transform = \"TranslateX(0px) Scale(1)\"\n\t\t), 1\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate\")\n\t\t\telem.style.transform = elem.style.opacity = null\n\n\n\tshow: (elem, props) ->\n\t\tdelay = arguments[arguments.length-2]?.delay*1000 or 1\n\t\telem.style.opacity = 0\n\t\tsetTimeout (->\n\t\t\telem.className += \" animate\"\n\t\t), 1\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 1\n\t\t), delay\n\t\telem.addEventListener \"transitionend\", ->\n\t\t\telem.classList.remove(\"animate\")\n\t\t\telem.style.opacity = null\n\t\t\telem.removeEventListener \"transitionend\", arguments.callee, false\n\n\thide: (elem, remove_func, props) ->\n\t\tdelay = arguments[arguments.length-2]?.delay*1000 or 1\n\t\telem.className += \" animate\"\n\t\tsetTimeout (->\n\t\t\telem.style.opacity = 0\n\t\t), delay\n\t\telem.addEventListener \"transitionend\", (e) ->\n\t\t\tif e.propertyName == \"opacity\"\n\t\t\t\tremove_func()\n\n\taddVisibleClass: (elem, props) ->\n\t\tsetTimeout ->\n\t\t\telem.classList.add(\"visible\")\n\nwindow.Animation = new Animation()"
  },
  {
    "path": "plugins/UiPluginManager/media/js/utils/Dollar.coffee",
    "content": "window.$ = (selector) ->\n\tif selector.startsWith(\"#\")\n\t\treturn document.getElementById(selector.replace(\"#\", \"\"))\n"
  },
  {
    "path": "plugins/UiPluginManager/media/js/utils/ZeroFrame.coffee",
    "content": "class ZeroFrame extends Class\n\tconstructor: (url) ->\n\t\t@url = url\n\t\t@waiting_cb = {}\n\t\t@wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, \"$1\")\n\t\t@connect()\n\t\t@next_message_id = 1\n\t\t@history_state = {}\n\t\t@init()\n\n\n\tinit: ->\n\t\t@\n\n\n\tconnect: ->\n\t\t@target = window.parent\n\t\twindow.addEventListener(\"message\", @onMessage, false)\n\t\t@cmd(\"innerReady\")\n\n\t\t# Save scrollTop\n\t\twindow.addEventListener \"beforeunload\", (e) =>\n\t\t\t@log \"save scrollTop\", window.pageYOffset\n\t\t\t@history_state[\"scrollTop\"] = window.pageYOffset\n\t\t\t@cmd \"wrapperReplaceState\", [@history_state, null]\n\n\t\t# Restore scrollTop\n\t\t@cmd \"wrapperGetState\", [], (state) =>\n\t\t\t@history_state = state if state?\n\t\t\t@log \"restore scrollTop\", state, window.pageYOffset\n\t\t\tif window.pageYOffset == 0 and state\n\t\t\t\twindow.scroll(window.pageXOffset, state.scrollTop)\n\n\n\tonMessage: (e) =>\n\t\tmessage = e.data\n\t\tcmd = message.cmd\n\t\tif cmd == \"response\"\n\t\t\tif @waiting_cb[message.to]?\n\t\t\t\t@waiting_cb[message.to](message.result)\n\t\t\telse\n\t\t\t\t@log \"Websocket callback not found:\", message\n\t\telse if cmd == \"wrapperReady\" # Wrapper inited later\n\t\t\t@cmd(\"innerReady\")\n\t\telse if cmd == \"ping\"\n\t\t\t@response message.id, \"pong\"\n\t\telse if cmd == \"wrapperOpenedWebsocket\"\n\t\t\t@onOpenWebsocket()\n\t\telse if cmd == \"wrapperClosedWebsocket\"\n\t\t\t@onCloseWebsocket()\n\t\telse\n\t\t\t@onRequest cmd, message.params\n\n\n\tonRequest: (cmd, message) =>\n\t\t@log \"Unknown request\", message\n\n\n\tresponse: (to, result) ->\n\t\t@send {\"cmd\": \"response\", \"to\": to, \"result\": result}\n\n\n\tcmd: (cmd, params={}, cb=null) ->\n\t\t@send {\"cmd\": cmd, \"params\": params}, cb\n\n\n\tsend: (message, cb=null) ->\n\t\tmessage.wrapper_nonce = @wrapper_nonce\n\t\tmessage.id = @next_message_id\n\t\t@next_message_id += 1\n\t\t@target.postMessage(message, \"*\")\n\t\tif cb\n\t\t\t@waiting_cb[message.id] = cb\n\n\n\tonOpenWebsocket: =>\n\t\t@log \"Websocket open\"\n\n\n\tonCloseWebsocket: =>\n\t\t@log \"Websocket close\"\n\n\n\nwindow.ZeroFrame = ZeroFrame\n"
  },
  {
    "path": "plugins/UiPluginManager/media/plugin_manager.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>Settings - ZeroNet</title>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <link rel=\"stylesheet\" href=\"css/all.css?rev={rev}\" />\n</head>\n\n\n<h1>ZeroNet plugin manager</h1>\n\n<div class=\"content\" id=\"content\"></div>\n<div class=\"bottom\" id=\"bottom-restart\"></div>\n\n<script type=\"text/javascript\" src=\"js/all.js?rev={rev}&lang={lang}\"></script>\n</body>\n</html>"
  },
  {
    "path": "plugins/Zeroname/README.md",
    "content": "# ZeroName\n\nZeroname plugin to connect Namecoin and register all the .bit domain name.\n\n## Start\n\nYou can create your own Zeroname.\n\n### Namecoin node\n\nYou need to run a namecoin node.\n\n[Namecoin](https://namecoin.org/download/)\n\nYou will need to start it as a RPC server.\n\nExample of `~/.namecoin/namecoin.conf` minimal setup:\n```\ndaemon=1\nrpcuser=your-name\nrpcpassword=your-password\nrpcport=8336\nserver=1\ntxindex=1\nvalueencoding=utf8\n```\n\nDon't forget to change the `rpcuser` value and `rpcpassword` value!\n\nYou can start your node : `./namecoind`\n\n### Create a Zeroname site\n\nYou will also need to create a site `python zeronet.py createSite` and regitser the info.\n\nIn the site you will need to create a file `./data/<your-site>/data/names.json` with this is it:\n```\n{}\n```\n\n### `zeroname_config.json` file\n\nIn `~/.namecoin/zeroname_config.json`\n```\n{\n  \"lastprocessed\": 223910,\n  \"zeronet_path\": \"/root/ZeroNet\", # Update with your path\n  \"privatekey\": \"\", # Update with your private key of your site\n  \"site\": \"\" # Update with the address of your site\n}\n```\n\n### Run updater\n\nYou can now run the script : `updater/zeroname_updater.py` and wait until it is fully sync (it might take a while).\n"
  },
  {
    "path": "plugins/Zeroname/SiteManagerPlugin.py",
    "content": "import logging\nimport re\nimport time\n\nfrom Config import config\nfrom Plugin import PluginManager\n\nallow_reload = False  # No reload supported\n\nlog = logging.getLogger(\"ZeronamePlugin\")\n\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n    site_zeroname = None\n    db_domains = {}\n    db_domains_modified = None\n\n    def load(self, *args, **kwargs):\n        super(SiteManagerPlugin, self).load(*args, **kwargs)\n        if not self.get(config.bit_resolver):\n            self.need(config.bit_resolver)  # Need ZeroName site\n\n    # Return: True if the address is .bit domain\n    def isBitDomain(self, address):\n        return re.match(r\"(.*?)([A-Za-z0-9_-]+\\.bit)$\", address)\n\n    # Resolve domain\n    # Return: The address or None\n    def resolveBitDomain(self, domain):\n        domain = domain.lower()\n        if not self.site_zeroname:\n            self.site_zeroname = self.need(config.bit_resolver)\n\n        site_zeroname_modified = self.site_zeroname.content_manager.contents.get(\"content.json\", {}).get(\"modified\", 0)\n        if not self.db_domains or self.db_domains_modified != site_zeroname_modified:\n            self.site_zeroname.needFile(\"data/names.json\", priority=10)\n            s = time.time()\n            try:\n                self.db_domains = self.site_zeroname.storage.loadJson(\"data/names.json\")\n            except Exception as err:\n                log.error(\"Error loading names.json: %s\" % err)\n\n            log.debug(\n                \"Domain db with %s entries loaded in %.3fs (modification: %s -> %s)\" %\n                (len(self.db_domains), time.time() - s, self.db_domains_modified, site_zeroname_modified)\n            )\n            self.db_domains_modified = site_zeroname_modified\n        return self.db_domains.get(domain)\n\n    # Turn domain into address\n    def resolveDomain(self, domain):\n        return self.resolveBitDomain(domain) or super(SiteManagerPlugin, self).resolveDomain(domain)\n\n    # Return: True if the address is domain\n    def isDomain(self, address):\n        return self.isBitDomain(address) or super(SiteManagerPlugin, self).isDomain(address)\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"Zeroname plugin\")\n        group.add_argument(\n            \"--bit_resolver\", help=\"ZeroNet site to resolve .bit domains\",\n            default=\"1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F\", metavar=\"address\"\n        )\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/Zeroname/__init__.py",
    "content": "from . import SiteManagerPlugin\n"
  },
  {
    "path": "plugins/Zeroname/updater/zeroname_updater.py",
    "content": "from __future__ import print_function\nimport time\nimport json\nimport os\nimport sys\nimport re\nimport socket\n\nfrom six import string_types\n\nfrom subprocess import call\nfrom bitcoinrpc.authproxy import AuthServiceProxy\n\n\ndef publish():\n    print(\"* Signing and Publishing...\")\n    call(\" \".join(command_sign_publish), shell=True)\n\n\ndef processNameOp(domain, value, test=False):\n    if not value.strip().startswith(\"{\"):\n        return False\n    try:\n        data = json.loads(value)\n    except Exception as err:\n        print(\"Json load error: %s\" % err)\n        return False\n    if \"zeronet\" not in data and \"map\" not in data:\n    # Namecoin standard use {\"map\": { \"blog\": {\"zeronet\": \"1D...\"} }}\n        print(\"No zeronet and no map in \", data.keys())\n        return False\n    if \"map\" in data:\n    # If subdomains using the Namecoin standard is present, just re-write in the Zeronet way\n    # and call the function again\n        data_map = data[\"map\"]\n        new_value = {}\n        for subdomain in data_map:\n            if \"zeronet\" in data_map[subdomain]:\n                new_value[subdomain] = data_map[subdomain][\"zeronet\"]\n        if \"zeronet\" in data and isinstance(data[\"zeronet\"], string_types):\n        # {\n        #    \"zeronet\":\"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9\",\n        #    ....\n        # }\n            new_value[\"\"] = data[\"zeronet\"]\n        if len(new_value) > 0:\n            return processNameOp(domain, json.dumps({\"zeronet\": new_value}), test)\n        else:\n            return False\n    if \"zeronet\" in data and isinstance(data[\"zeronet\"], string_types):\n    # {\n    #    \"zeronet\":\"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9\"\n    # } is valid\n        return processNameOp(domain, json.dumps({\"zeronet\": { \"\": data[\"zeronet\"]}}), test)\n    if not isinstance(data[\"zeronet\"], dict):\n        print(\"Not dict: \", data[\"zeronet\"])\n        return False\n    if not re.match(\"^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$\", domain):\n        print(\"Invalid domain: \", domain)\n        return False\n\n    if test:\n        return True\n\n    if \"slave\" in sys.argv:\n        print(\"Waiting for master update arrive\")\n        time.sleep(30)  # Wait 30 sec to allow master updater\n\n    # Note: Requires the file data/names.json to exist and contain \"{}\" to work\n    names_raw = open(names_path, \"rb\").read()\n    names = json.loads(names_raw)\n    for subdomain, address in data[\"zeronet\"].items():\n        subdomain = subdomain.lower()\n        address = re.sub(\"[^A-Za-z0-9]\", \"\", address)\n        print(subdomain, domain, \"->\", address)\n        if subdomain:\n            if re.match(\"^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$\", subdomain):\n                names[\"%s.%s.bit\" % (subdomain, domain)] = address\n            else:\n                print(\"Invalid subdomain:\", domain, subdomain)\n        else:\n            names[\"%s.bit\" % domain] = address\n\n    new_names_raw = json.dumps(names, indent=2, sort_keys=True)\n    if new_names_raw != names_raw:\n        open(names_path, \"wb\").write(new_names_raw)\n        print(\"-\", domain, \"Changed\")\n        return True\n    else:\n        print(\"-\", domain, \"Not changed\")\n        return False\n\n\ndef processBlock(block_id, test=False):\n    print(\"Processing block #%s...\" % block_id)\n    s = time.time()\n    block_hash = rpc.getblockhash(block_id)\n    block = rpc.getblock(block_hash)\n\n    print(\"Checking %s tx\" % len(block[\"tx\"]))\n    updated = 0\n    for tx in block[\"tx\"]:\n        try:\n            transaction = rpc.getrawtransaction(tx, 1)\n            for vout in transaction.get(\"vout\", []):\n                if \"scriptPubKey\" in vout and \"nameOp\" in vout[\"scriptPubKey\"] and \"name\" in vout[\"scriptPubKey\"][\"nameOp\"]:\n                    name_op = vout[\"scriptPubKey\"][\"nameOp\"]\n                    updated += processNameOp(name_op[\"name\"].replace(\"d/\", \"\"), name_op[\"value\"], test)\n        except Exception as err:\n            print(\"Error processing tx #%s %s\" % (tx, err))\n    print(\"Done in %.3fs (updated %s).\" % (time.time() - s, updated))\n    return updated\n\n# Connecting to RPC\ndef initRpc(config):\n    \"\"\"Initialize Namecoin RPC\"\"\"\n    rpc_data = {\n        'connect': '127.0.0.1',\n        'port': '8336',\n        'user': 'PLACEHOLDER',\n        'password': 'PLACEHOLDER',\n        'clienttimeout': '900'\n    }\n    try:\n        fptr = open(config, 'r')\n        lines = fptr.readlines()\n        fptr.close()\n    except:\n        return None  # Or take some other appropriate action\n\n    for line in lines:\n        if not line.startswith('rpc'):\n            continue\n        key_val = line.split(None, 1)[0]\n        (key, val) = key_val.split('=', 1)\n        if not key or not val:\n            continue\n        rpc_data[key[3:]] = val\n\n    url = 'http://%(user)s:%(password)s@%(connect)s:%(port)s' % rpc_data\n\n    return url, int(rpc_data['clienttimeout'])\n\n# Loading config...\n\n# Check whether platform is on windows or linux\n# On linux namecoin is installed under ~/.namecoin, while on on windows it is in %appdata%/Namecoin\n\nif sys.platform == \"win32\":\n    namecoin_location = os.getenv('APPDATA') + \"/Namecoin/\"\nelse:\n    namecoin_location = os.path.expanduser(\"~/.namecoin/\")\n\nconfig_path = namecoin_location + 'zeroname_config.json'\nif not os.path.isfile(config_path):  # Create sample config\n    open(config_path, \"w\").write(\n        json.dumps({'site': 'site', 'zeronet_path': '/home/zeronet', 'privatekey': '', 'lastprocessed': 223910}, indent=2)\n    )\n    print(\"* Example config written to %s\" % config_path)\n    sys.exit(0)\n\nconfig = json.load(open(config_path))\nnames_path = \"%s/data/%s/data/names.json\" % (config[\"zeronet_path\"], config[\"site\"])\nos.chdir(config[\"zeronet_path\"])  # Change working dir - tells script where Zeronet install is.\n\n# Parameters to sign and publish\ncommand_sign_publish = [sys.executable, \"zeronet.py\", \"siteSign\", config[\"site\"], config[\"privatekey\"], \"--publish\"]\nif sys.platform == 'win32':\n    command_sign_publish = ['\"%s\"' % param for param in command_sign_publish]\n\n# Initialize rpc connection\nrpc_auth, rpc_timeout = initRpc(namecoin_location + \"namecoin.conf\")\nrpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)\n\nnode_version = rpc.getnetworkinfo()['version']\n\nwhile 1:\n    try:\n        time.sleep(1)\n        if node_version < 160000 :\n            last_block = int(rpc.getinfo()[\"blocks\"])\n        else:\n            last_block = int(rpc.getblockchaininfo()[\"blocks\"])\n        break # Connection succeeded\n    except socket.timeout:  # Timeout\n        print(\".\", end=' ')\n        sys.stdout.flush()\n    except Exception as err:\n        print(\"Exception\", err.__class__, err)\n        time.sleep(5)\n        rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)\n\nif not config[\"lastprocessed\"]:  # First startup: Start processing from last block\n    config[\"lastprocessed\"] = last_block\n\n\nprint(\"- Testing domain parsing...\")\nassert processBlock(223911, test=True) # Testing zeronetwork.bit\nassert processBlock(227052, test=True) # Testing brainwallets.bit\nassert not processBlock(236824, test=True) # Utf8 domain name (invalid should skip)\nassert not processBlock(236752, test=True) # Uppercase domain (invalid should skip)\nassert processBlock(236870, test=True) # Encoded domain (should pass)\nassert processBlock(438317, test=True) # Testing namecoin standard artifaxradio.bit (should pass)\n# sys.exit(0)\n\nprint(\"- Parsing skipped blocks...\")\nshould_publish = False\nfor block_id in range(config[\"lastprocessed\"], last_block + 1):\n    if processBlock(block_id):\n        should_publish = True\nconfig[\"lastprocessed\"] = last_block\n\nif should_publish:\n    publish()\n\nwhile 1:\n    print(\"- Waiting for new block\")\n    sys.stdout.flush()\n    while 1:\n        try:\n            time.sleep(1)\n            if node_version < 160000 :\n                rpc.waitforblock()\n            else:\n                rpc.waitfornewblock()\n            print(\"Found\")\n            break  # Block found\n        except socket.timeout:  # Timeout\n            print(\".\", end=' ')\n            sys.stdout.flush()\n        except Exception as err:\n            print(\"Exception\", err.__class__, err)\n            time.sleep(5)\n            rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)\n\n    if node_version < 160000 :\n        last_block = int(rpc.getinfo()[\"blocks\"])\n    else:\n        last_block = int(rpc.getblockchaininfo()[\"blocks\"])\n    should_publish = False\n    for block_id in range(config[\"lastprocessed\"] + 1, last_block + 1):\n        if processBlock(block_id):\n            should_publish = True\n\n    config[\"lastprocessed\"] = last_block\n    open(config_path, \"w\").write(json.dumps(config, indent=2))\n\n    if should_publish:\n        publish()\n"
  },
  {
    "path": "plugins/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/disabled-Bootstrapper/BootstrapperDb.py",
    "content": "import time\nimport re\n\nimport gevent\n\nfrom Config import config\nfrom Db import Db\nfrom util import helper\n\n\nclass BootstrapperDb(Db.Db):\n    def __init__(self):\n        self.version = 7\n        self.hash_ids = {}  # hash -> id cache\n        super(BootstrapperDb, self).__init__({\"db_name\": \"Bootstrapper\"}, \"%s/bootstrapper.db\" % config.data_dir)\n        self.foreign_keys = True\n        self.checkTables()\n        self.updateHashCache()\n        gevent.spawn(self.cleanup)\n\n    def cleanup(self):\n        while 1:\n            time.sleep(4 * 60)\n            timeout = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime(time.time() - 60 * 40))\n            self.execute(\"DELETE FROM peer WHERE date_announced < ?\", [timeout])\n\n    def updateHashCache(self):\n        res = self.execute(\"SELECT * FROM hash\")\n        self.hash_ids = {row[\"hash\"]: row[\"hash_id\"] for row in res}\n        self.log.debug(\"Loaded %s hash_ids\" % len(self.hash_ids))\n\n    def checkTables(self):\n        version = int(self.execute(\"PRAGMA user_version\").fetchone()[0])\n        self.log.debug(\"Db version: %s, needed: %s\" % (version, self.version))\n        if version < self.version:\n            self.createTables()\n        else:\n            self.execute(\"VACUUM\")\n\n    def createTables(self):\n        # Delete all tables\n        self.execute(\"PRAGMA writable_schema = 1\")\n        self.execute(\"DELETE FROM sqlite_master WHERE type IN ('table', 'index', 'trigger')\")\n        self.execute(\"PRAGMA writable_schema = 0\")\n        self.execute(\"VACUUM\")\n        self.execute(\"PRAGMA INTEGRITY_CHECK\")\n        # Create new tables\n        self.execute(\"\"\"\n            CREATE TABLE peer (\n                peer_id        INTEGER PRIMARY KEY ASC AUTOINCREMENT NOT NULL UNIQUE,\n                type           TEXT,\n                address        TEXT,\n                port           INTEGER NOT NULL,\n                date_added     DATETIME DEFAULT (CURRENT_TIMESTAMP),\n                date_announced DATETIME DEFAULT (CURRENT_TIMESTAMP)\n            );\n        \"\"\")\n        self.execute(\"CREATE UNIQUE INDEX peer_key ON peer (address, port);\")\n\n        self.execute(\"\"\"\n            CREATE TABLE peer_to_hash (\n                peer_to_hash_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,\n                peer_id         INTEGER REFERENCES peer (peer_id) ON DELETE CASCADE,\n                hash_id         INTEGER REFERENCES hash (hash_id)\n            );\n        \"\"\")\n        self.execute(\"CREATE INDEX peer_id ON peer_to_hash (peer_id);\")\n        self.execute(\"CREATE INDEX hash_id ON peer_to_hash (hash_id);\")\n\n        self.execute(\"\"\"\n            CREATE TABLE hash (\n                hash_id    INTEGER  PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,\n                hash       BLOB     UNIQUE NOT NULL,\n                date_added DATETIME DEFAULT (CURRENT_TIMESTAMP)\n            );\n        \"\"\")\n        self.execute(\"PRAGMA user_version = %s\" % self.version)\n\n    def getHashId(self, hash):\n        if hash not in self.hash_ids:\n            self.log.debug(\"New hash: %s\" % repr(hash))\n            res = self.execute(\"INSERT OR IGNORE INTO hash ?\", {\"hash\": hash})\n            self.hash_ids[hash] = res.lastrowid\n        return self.hash_ids[hash]\n\n    def peerAnnounce(self, ip_type, address, port=None, hashes=[], onion_signed=False, delete_missing_hashes=False):\n        hashes_ids_announced = []\n        for hash in hashes:\n            hashes_ids_announced.append(self.getHashId(hash))\n\n        # Check user\n        res = self.execute(\"SELECT peer_id FROM peer WHERE ? LIMIT 1\", {\"address\": address, \"port\": port})\n\n        user_row = res.fetchone()\n        now = time.strftime(\"%Y-%m-%d %H:%M:%S\")\n        if user_row:\n            peer_id = user_row[\"peer_id\"]\n            self.execute(\"UPDATE peer SET date_announced = ? WHERE peer_id = ?\", (now, peer_id))\n        else:\n            self.log.debug(\"New peer: %s signed: %s\" % (address, onion_signed))\n            if ip_type == \"onion\" and not onion_signed:\n                return len(hashes)\n            res = self.execute(\"INSERT INTO peer ?\", {\"type\": ip_type, \"address\": address, \"port\": port, \"date_announced\": now})\n            peer_id = res.lastrowid\n\n        # Check user's hashes\n        res = self.execute(\"SELECT * FROM peer_to_hash WHERE ?\", {\"peer_id\": peer_id})\n        hash_ids_db = [row[\"hash_id\"] for row in res]\n        if hash_ids_db != hashes_ids_announced:\n            hash_ids_added = set(hashes_ids_announced) - set(hash_ids_db)\n            hash_ids_removed = set(hash_ids_db) - set(hashes_ids_announced)\n            if ip_type != \"onion\" or onion_signed:\n                for hash_id in hash_ids_added:\n                    self.execute(\"INSERT INTO peer_to_hash ?\", {\"peer_id\": peer_id, \"hash_id\": hash_id})\n                if hash_ids_removed and delete_missing_hashes:\n                    self.execute(\"DELETE FROM peer_to_hash WHERE ?\", {\"peer_id\": peer_id, \"hash_id\": list(hash_ids_removed)})\n\n            return len(hash_ids_added) + len(hash_ids_removed)\n        else:\n            return 0\n\n    def peerList(self, hash, address=None, onions=[], port=None, limit=30, need_types=[\"ipv4\", \"onion\"], order=True):\n        back = {\"ipv4\": [], \"ipv6\": [], \"onion\": []}\n        if limit == 0:\n            return back\n        hashid = self.getHashId(hash)\n\n        if order:\n            order_sql = \"ORDER BY date_announced DESC\"\n        else:\n            order_sql = \"\"\n        where_sql = \"hash_id = :hashid\"\n        if onions:\n            onions_escaped = [\"'%s'\" % re.sub(\"[^a-z0-9,]\", \"\", onion) for onion in onions if type(onion) is str]\n            where_sql += \" AND address NOT IN (%s)\" % \",\".join(onions_escaped)\n        elif address:\n            where_sql += \" AND NOT (address = :address AND port = :port)\"\n\n        query = \"\"\"\n            SELECT type, address, port\n            FROM peer_to_hash\n            LEFT JOIN peer USING (peer_id)\n            WHERE %s\n            %s\n            LIMIT :limit\n        \"\"\" % (where_sql, order_sql)\n        res = self.execute(query, {\"hashid\": hashid, \"address\": address, \"port\": port, \"limit\": limit})\n\n        for row in res:\n            if row[\"type\"] in need_types:\n                if row[\"type\"] == \"onion\":\n                    packed = helper.packOnionAddress(row[\"address\"], row[\"port\"])\n                else:\n                    packed = helper.packAddress(str(row[\"address\"]), row[\"port\"])\n                back[row[\"type\"]].append(packed)\n        return back\n"
  },
  {
    "path": "plugins/disabled-Bootstrapper/BootstrapperPlugin.py",
    "content": "import time\n\nfrom util import helper\n\nfrom Plugin import PluginManager\nfrom .BootstrapperDb import BootstrapperDb\nfrom Crypt import CryptRsa\nfrom Config import config\n\nif \"db\" not in locals().keys():  # Share during reloads\n    db = BootstrapperDb()\n\n\n@PluginManager.registerTo(\"FileRequest\")\nclass FileRequestPlugin(object):\n    def checkOnionSigns(self, onions, onion_signs, onion_sign_this):\n        if not onion_signs or len(onion_signs) != len(set(onions)):\n            return False\n\n        if time.time() - float(onion_sign_this) > 3 * 60:\n            return False  # Signed out of allowed 3 minutes\n\n        onions_signed = []\n        # Check onion signs\n        for onion_publickey, onion_sign in onion_signs.items():\n            if CryptRsa.verify(onion_sign_this.encode(), onion_publickey, onion_sign):\n                onions_signed.append(CryptRsa.publickeyToOnion(onion_publickey))\n            else:\n                break\n\n        # Check if the same onion addresses signed as the announced onces\n        if sorted(onions_signed) == sorted(set(onions)):\n            return True\n        else:\n            return False\n\n    def actionAnnounce(self, params):\n        time_started = time.time()\n        s = time.time()\n        # Backward compatibility\n        if \"ip4\" in params[\"add\"]:\n            params[\"add\"].append(\"ipv4\")\n        if \"ip4\" in params[\"need_types\"]:\n            params[\"need_types\"].append(\"ipv4\")\n\n        hashes = params[\"hashes\"]\n\n        all_onions_signed = self.checkOnionSigns(params.get(\"onions\", []), params.get(\"onion_signs\"), params.get(\"onion_sign_this\"))\n\n        time_onion_check = time.time() - s\n\n        ip_type = helper.getIpType(self.connection.ip)\n\n        if ip_type == \"onion\" or self.connection.ip in config.ip_local:\n            is_port_open = False\n        elif ip_type in params[\"add\"]:\n            is_port_open = True\n        else:\n            is_port_open = False\n\n        s = time.time()\n        # Separatley add onions to sites or at once if no onions present\n        i = 0\n        onion_to_hash = {}\n        for onion in params.get(\"onions\", []):\n            if onion not in onion_to_hash:\n                onion_to_hash[onion] = []\n            onion_to_hash[onion].append(hashes[i])\n            i += 1\n\n        hashes_changed = 0\n        for onion, onion_hashes in onion_to_hash.items():\n            hashes_changed += db.peerAnnounce(\n                ip_type=\"onion\",\n                address=onion,\n                port=params[\"port\"],\n                hashes=onion_hashes,\n                onion_signed=all_onions_signed\n            )\n        time_db_onion = time.time() - s\n\n        s = time.time()\n\n        if is_port_open:\n            hashes_changed += db.peerAnnounce(\n                ip_type=ip_type,\n                address=self.connection.ip,\n                port=params[\"port\"],\n                hashes=hashes,\n                delete_missing_hashes=params.get(\"delete\")\n            )\n        time_db_ip = time.time() - s\n\n        s = time.time()\n        # Query sites\n        back = {}\n        peers = []\n        if params.get(\"onions\") and not all_onions_signed and hashes_changed:\n            back[\"onion_sign_this\"] = \"%.0f\" % time.time()  # Send back nonce for signing\n\n        if len(hashes) > 500 or not hashes_changed:\n            limit = 5\n            order = False\n        else:\n            limit = 30\n            order = True\n        for hash in hashes:\n            if time.time() - time_started > 1:  # 1 sec limit on request\n                self.connection.log(\"Announce time limit exceeded after %s/%s sites\" % (len(peers), len(hashes)))\n                break\n\n            hash_peers = db.peerList(\n                hash,\n                address=self.connection.ip, onions=list(onion_to_hash.keys()), port=params[\"port\"],\n                limit=min(limit, params[\"need_num\"]), need_types=params[\"need_types\"], order=order\n            )\n            if \"ip4\" in params[\"need_types\"]:  # Backward compatibility\n                hash_peers[\"ip4\"] = hash_peers[\"ipv4\"]\n                del(hash_peers[\"ipv4\"])\n            peers.append(hash_peers)\n        time_peerlist = time.time() - s\n\n        back[\"peers\"] = peers\n        self.connection.log(\n            \"Announce %s sites (onions: %s, onion_check: %.3fs, db_onion: %.3fs, db_ip: %.3fs, peerlist: %.3fs, limit: %s)\" %\n            (len(hashes), len(onion_to_hash), time_onion_check, time_db_onion, time_db_ip, time_peerlist, limit)\n        )\n        self.response(back)\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    @helper.encodeResponse\n    def actionStatsBootstrapper(self):\n        self.sendHeader()\n\n        # Style\n        yield \"\"\"\n        <style>\n         * { font-family: monospace; white-space: pre }\n         table td, table th { text-align: right; padding: 0px 10px }\n        </style>\n        \"\"\"\n\n        hash_rows = db.execute(\"SELECT * FROM hash\").fetchall()\n        for hash_row in hash_rows:\n            peer_rows = db.execute(\n                \"SELECT * FROM peer LEFT JOIN peer_to_hash USING (peer_id) WHERE hash_id = :hash_id\",\n                {\"hash_id\": hash_row[\"hash_id\"]}\n            ).fetchall()\n\n            yield \"<br>%s (added: %s, peers: %s)<br>\" % (\n                str(hash_row[\"hash\"]).encode().hex(), hash_row[\"date_added\"], len(peer_rows)\n            )\n            for peer_row in peer_rows:\n                yield \" - {type} {address}:{port} added: {date_added}, announced: {date_announced}<br>\".format(**dict(peer_row))\n"
  },
  {
    "path": "plugins/disabled-Bootstrapper/Test/TestBootstrapper.py",
    "content": "import hashlib\nimport os\n\nimport pytest\n\nfrom Bootstrapper import BootstrapperPlugin\nfrom Bootstrapper.BootstrapperDb import BootstrapperDb\nfrom Peer import Peer\nfrom Crypt import CryptRsa\nfrom util import helper\n\n\n@pytest.fixture()\ndef bootstrapper_db(request):\n    BootstrapperPlugin.db.close()\n    BootstrapperPlugin.db = BootstrapperDb()\n    BootstrapperPlugin.db.createTables()  # Reset db\n    BootstrapperPlugin.db.cur.logging = True\n\n    def cleanup():\n        BootstrapperPlugin.db.close()\n        os.unlink(BootstrapperPlugin.db.db_path)\n\n    request.addfinalizer(cleanup)\n    return BootstrapperPlugin.db\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestBootstrapper:\n    def testHashCache(self, file_server, bootstrapper_db):\n        ip_type = helper.getIpType(file_server.ip)\n        peer = Peer(file_server.ip, 1544, connection_server=file_server)\n        hash1 = hashlib.sha256(b\"site1\").digest()\n        hash2 = hashlib.sha256(b\"site2\").digest()\n        hash3 = hashlib.sha256(b\"site3\").digest()\n\n        # Verify empty result\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1, hash2],\n            \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": [ip_type]\n        })\n\n        assert len(res[\"peers\"][0][ip_type]) == 0  # Empty result\n\n        hash_ids_before = bootstrapper_db.hash_ids.copy()\n\n        bootstrapper_db.updateHashCache()\n\n        assert hash_ids_before == bootstrapper_db.hash_ids\n\n\n    def testBootstrapperDb(self, file_server, bootstrapper_db):\n        ip_type = helper.getIpType(file_server.ip)\n        peer = Peer(file_server.ip, 1544, connection_server=file_server)\n        hash1 = hashlib.sha256(b\"site1\").digest()\n        hash2 = hashlib.sha256(b\"site2\").digest()\n        hash3 = hashlib.sha256(b\"site3\").digest()\n\n        # Verify empty result\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1, hash2],\n            \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": [ip_type]\n        })\n\n        assert len(res[\"peers\"][0][ip_type]) == 0  # Empty result\n\n        # Verify added peer on previous request\n        bootstrapper_db.peerAnnounce(ip_type, file_server.ip_external, port=15441, hashes=[hash1, hash2], delete_missing_hashes=True)\n\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1, hash2],\n            \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": [ip_type]\n        })\n        assert len(res[\"peers\"][0][ip_type]) == 1\n        assert len(res[\"peers\"][1][ip_type]) == 1\n\n        # hash2 deleted from 1.2.3.4\n        bootstrapper_db.peerAnnounce(ip_type, file_server.ip_external, port=15441, hashes=[hash1], delete_missing_hashes=True)\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1, hash2],\n            \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": [ip_type]\n        })\n        assert len(res[\"peers\"][0][ip_type]) == 1\n        assert len(res[\"peers\"][1][ip_type]) == 0\n\n        # Announce 3 hash again\n        bootstrapper_db.peerAnnounce(ip_type, file_server.ip_external, port=15441, hashes=[hash1, hash2, hash3], delete_missing_hashes=True)\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1, hash2, hash3],\n            \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": [ip_type]\n        })\n        assert len(res[\"peers\"][0][ip_type]) == 1\n        assert len(res[\"peers\"][1][ip_type]) == 1\n        assert len(res[\"peers\"][2][ip_type]) == 1\n\n        # Single hash announce\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1], \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": [ip_type]\n        })\n        assert len(res[\"peers\"][0][ip_type]) == 1\n\n        # Test DB cleanup\n        assert [row[0] for row in bootstrapper_db.execute(\"SELECT address FROM peer\").fetchall()] == [file_server.ip_external]  # 127.0.0.1 never get added to db\n\n        # Delete peers\n        bootstrapper_db.execute(\"DELETE FROM peer WHERE address = ?\", [file_server.ip_external])\n        assert bootstrapper_db.execute(\"SELECT COUNT(*) AS num FROM peer_to_hash\").fetchone()[\"num\"] == 0\n\n        assert bootstrapper_db.execute(\"SELECT COUNT(*) AS num FROM hash\").fetchone()[\"num\"] == 3  # 3 sites\n        assert bootstrapper_db.execute(\"SELECT COUNT(*) AS num FROM peer\").fetchone()[\"num\"] == 0  # 0 peer\n\n    def testPassive(self, file_server, bootstrapper_db):\n        peer = Peer(file_server.ip, 1544, connection_server=file_server)\n        ip_type = helper.getIpType(file_server.ip)\n        hash1 = hashlib.sha256(b\"hash1\").digest()\n\n        bootstrapper_db.peerAnnounce(ip_type, address=None, port=15441, hashes=[hash1])\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1], \"port\": 15441, \"need_types\": [ip_type], \"need_num\": 10, \"add\": []\n        })\n\n        assert len(res[\"peers\"][0][\"ipv4\"]) == 0  # Empty result\n\n    def testAddOnion(self, file_server, site, bootstrapper_db, tor_manager):\n        onion1 = tor_manager.addOnion()\n        onion2 = tor_manager.addOnion()\n        peer = Peer(file_server.ip, 1544, connection_server=file_server)\n        hash1 = hashlib.sha256(b\"site1\").digest()\n        hash2 = hashlib.sha256(b\"site2\").digest()\n        hash3 = hashlib.sha256(b\"site3\").digest()\n\n        bootstrapper_db.peerAnnounce(ip_type=\"ipv4\", address=\"1.2.3.4\", port=1234, hashes=[hash1, hash2, hash3])\n        res = peer.request(\"announce\", {\n            \"onions\": [onion1, onion1, onion2],\n            \"hashes\": [hash1, hash2, hash3], \"port\": 15441, \"need_types\": [\"ipv4\", \"onion\"], \"need_num\": 10, \"add\": [\"onion\"]\n        })\n        assert len(res[\"peers\"][0][\"ipv4\"]) == 1\n\n        # Onion address not added yet\n        site_peers = bootstrapper_db.peerList(address=\"1.2.3.4\", port=1234, hash=hash1)\n        assert len(site_peers[\"onion\"]) == 0\n        assert \"onion_sign_this\" in res\n\n        # Sign the nonces\n        sign1 = CryptRsa.sign(res[\"onion_sign_this\"].encode(), tor_manager.getPrivatekey(onion1))\n        sign2 = CryptRsa.sign(res[\"onion_sign_this\"].encode(), tor_manager.getPrivatekey(onion2))\n\n        # Bad sign (different address)\n        res = peer.request(\"announce\", {\n            \"onions\": [onion1], \"onion_sign_this\": res[\"onion_sign_this\"],\n            \"onion_signs\": {tor_manager.getPublickey(onion2): sign2},\n            \"hashes\": [hash1], \"port\": 15441, \"need_types\": [\"ipv4\", \"onion\"], \"need_num\": 10, \"add\": [\"onion\"]\n        })\n        assert \"onion_sign_this\" in res\n        site_peers1 = bootstrapper_db.peerList(address=\"1.2.3.4\", port=1234, hash=hash1)\n        assert len(site_peers1[\"onion\"]) == 0  # Not added\n\n        # Bad sign (missing one)\n        res = peer.request(\"announce\", {\n            \"onions\": [onion1, onion1, onion2], \"onion_sign_this\": res[\"onion_sign_this\"],\n            \"onion_signs\": {tor_manager.getPublickey(onion1): sign1},\n            \"hashes\": [hash1, hash2, hash3], \"port\": 15441, \"need_types\": [\"ipv4\", \"onion\"], \"need_num\": 10, \"add\": [\"onion\"]\n        })\n        assert \"onion_sign_this\" in res\n        site_peers1 = bootstrapper_db.peerList(address=\"1.2.3.4\", port=1234, hash=hash1)\n        assert len(site_peers1[\"onion\"]) == 0  # Not added\n\n        # Good sign\n        res = peer.request(\"announce\", {\n            \"onions\": [onion1, onion1, onion2], \"onion_sign_this\": res[\"onion_sign_this\"],\n            \"onion_signs\": {tor_manager.getPublickey(onion1): sign1, tor_manager.getPublickey(onion2): sign2},\n            \"hashes\": [hash1, hash2, hash3], \"port\": 15441, \"need_types\": [\"ipv4\", \"onion\"], \"need_num\": 10, \"add\": [\"onion\"]\n        })\n        assert \"onion_sign_this\" not in res\n\n        # Onion addresses added\n        site_peers1 = bootstrapper_db.peerList(address=\"1.2.3.4\", port=1234, hash=hash1)\n        assert len(site_peers1[\"onion\"]) == 1\n        site_peers2 = bootstrapper_db.peerList(address=\"1.2.3.4\", port=1234, hash=hash2)\n        assert len(site_peers2[\"onion\"]) == 1\n        site_peers3 = bootstrapper_db.peerList(address=\"1.2.3.4\", port=1234, hash=hash3)\n        assert len(site_peers3[\"onion\"]) == 1\n\n        assert site_peers1[\"onion\"][0] == site_peers2[\"onion\"][0]\n        assert site_peers2[\"onion\"][0] != site_peers3[\"onion\"][0]\n        assert helper.unpackOnionAddress(site_peers1[\"onion\"][0])[0] == onion1 + \".onion\"\n        assert helper.unpackOnionAddress(site_peers2[\"onion\"][0])[0] == onion1 + \".onion\"\n        assert helper.unpackOnionAddress(site_peers3[\"onion\"][0])[0] == onion2 + \".onion\"\n\n        tor_manager.delOnion(onion1)\n        tor_manager.delOnion(onion2)\n\n    def testRequestPeers(self, file_server, site, bootstrapper_db, tor_manager):\n        site.connection_server = file_server\n        file_server.tor_manager = tor_manager\n        hash = hashlib.sha256(site.address.encode()).digest()\n\n        # Request peers from tracker\n        assert len(site.peers) == 0\n        bootstrapper_db.peerAnnounce(ip_type=\"ipv4\", address=\"1.2.3.4\", port=1234, hashes=[hash])\n        site.announcer.announceTracker(\"zero://%s:%s\" % (file_server.ip, file_server.port))\n        assert len(site.peers) == 1\n\n        # Test onion address store\n        bootstrapper_db.peerAnnounce(ip_type=\"onion\", address=\"bka4ht2bzxchy44r\", port=1234, hashes=[hash], onion_signed=True)\n        site.announcer.announceTracker(\"zero://%s:%s\" % (file_server.ip, file_server.port))\n        assert len(site.peers) == 2\n        assert \"bka4ht2bzxchy44r.onion:1234\" in site.peers\n\n    @pytest.mark.slow\n    def testAnnounce(self, file_server, tor_manager):\n        file_server.tor_manager = tor_manager\n        hash1 = hashlib.sha256(b\"1Nekos4fiBqfcazyG1bAxdBT5oBvA76Z\").digest()\n        hash2 = hashlib.sha256(b\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\").digest()\n        peer = Peer(\"zero.booth.moe\", 443, connection_server=file_server)\n        assert peer.request(\"ping\")\n        peer = Peer(\"boot3rdez4rzn36x.onion\", 15441, connection_server=file_server)\n        assert peer.request(\"ping\")\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1, hash2],\n            \"port\": 15441, \"need_types\": [\"ip4\", \"onion\"], \"need_num\": 100, \"add\": [\"\"]\n        })\n\n        assert res\n\n    def testBackwardCompatibility(self, file_server, bootstrapper_db):\n        peer = Peer(file_server.ip, 1544, connection_server=file_server)\n        hash1 = hashlib.sha256(b\"site1\").digest()\n\n        bootstrapper_db.peerAnnounce(\"ipv4\", file_server.ip_external, port=15441, hashes=[hash1], delete_missing_hashes=True)\n\n        # Test with ipv4 need type\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1],\n            \"port\": 15441, \"need_types\": [\"ipv4\"], \"need_num\": 10, \"add\": []\n        })\n\n        assert len(res[\"peers\"][0][\"ipv4\"]) == 1\n\n        # Test with ip4 need type\n        res = peer.request(\"announce\", {\n            \"hashes\": [hash1],\n            \"port\": 15441, \"need_types\": [\"ip4\"], \"need_num\": 10, \"add\": []\n        })\n\n        assert len(res[\"peers\"][0][\"ip4\"]) == 1\n"
  },
  {
    "path": "plugins/disabled-Bootstrapper/Test/conftest.py",
    "content": "from src.Test.conftest import *"
  },
  {
    "path": "plugins/disabled-Bootstrapper/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    slow: mark a tests as slow.\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/disabled-Bootstrapper/__init__.py",
    "content": "from . import BootstrapperPlugin"
  },
  {
    "path": "plugins/disabled-Bootstrapper/plugin_info.json",
    "content": "{\n\t\"name\": \"Bootstrapper\",\n\t\"description\": \"Add BitTorrent tracker server like features to your ZeroNet client.\",\n\t\"default\": \"disabled\"\n}"
  },
  {
    "path": "plugins/disabled-Dnschain/SiteManagerPlugin.py",
    "content": "import logging, json, os, re, sys, time\nimport gevent\nfrom Plugin import PluginManager\nfrom Config import config\nfrom util import Http\nfrom Debug import Debug\n\nallow_reload = False # No reload supported\n\nlog = logging.getLogger(\"DnschainPlugin\")\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n\tdns_cache_path = \"%s/dns_cache.json\" % config.data_dir\n\tdns_cache = None\n\n\t# Checks if its a valid address\n\tdef isAddress(self, address):\n\t\tif self.isDomain(address): \n\t\t\treturn True\n\t\telse:\n\t\t\treturn super(SiteManagerPlugin, self).isAddress(address)\n\n\n\t# Return: True if the address is domain\n\tdef isDomain(self, address):\n\t\treturn re.match(r\"(.*?)([A-Za-z0-9_-]+\\.[A-Za-z0-9]+)$\", address)\n\n\n\t# Load dns entries from data/dns_cache.json\n\tdef loadDnsCache(self):\n\t\tif os.path.isfile(self.dns_cache_path):\n\t\t\tself.dns_cache = json.load(open(self.dns_cache_path))\n\t\telse:\n\t\t\tself.dns_cache = {}\n\t\tlog.debug(\"Loaded dns cache, entries: %s\" % len(self.dns_cache))\n\n\n\t# Save dns entries to data/dns_cache.json\n\tdef saveDnsCache(self):\n\t\tjson.dump(self.dns_cache, open(self.dns_cache_path, \"wb\"), indent=2)\n\n\n\t# Resolve domain using dnschain.net\n\t# Return: The address or None\n\tdef resolveDomainDnschainNet(self, domain):\n\t\ttry:\n\t\t\tmatch = self.isDomain(domain)\n\t\t\tsub_domain = match.group(1).strip(\".\")\n\t\t\ttop_domain = match.group(2)\n\t\t\tif not sub_domain: sub_domain = \"@\"\n\t\t\taddress = None\n\t\t\twith gevent.Timeout(5, Exception(\"Timeout: 5s\")):\n\t\t\t\tres = Http.get(\"https://api.dnschain.net/v1/namecoin/key/%s\" % top_domain).read()\n\t\t\t\tdata = json.loads(res)[\"data\"][\"value\"]\n\t\t\t\tif \"zeronet\" in data:\n\t\t\t\t\tfor key, val in data[\"zeronet\"].items():\n\t\t\t\t\t\tself.dns_cache[key+\".\"+top_domain] = [val, time.time()+60*60*5] # Cache for 5 hours\n\t\t\t\t\tself.saveDnsCache()\n\t\t\t\t\treturn data[\"zeronet\"].get(sub_domain)\n\t\t\t# Not found\n\t\t\treturn address\n\t\texcept Exception as err:\n\t\t\tlog.debug(\"Dnschain.net %s resolve error: %s\" % (domain, Debug.formatException(err)))\n\n\n\t# Resolve domain using dnschain.info\n\t# Return: The address or None\n\tdef resolveDomainDnschainInfo(self, domain):\n\t\ttry:\n\t\t\tmatch = self.isDomain(domain)\n\t\t\tsub_domain = match.group(1).strip(\".\")\n\t\t\ttop_domain = match.group(2)\n\t\t\tif not sub_domain: sub_domain = \"@\"\n\t\t\taddress = None\n\t\t\twith gevent.Timeout(5, Exception(\"Timeout: 5s\")):\n\t\t\t\tres = Http.get(\"https://dnschain.info/bit/d/%s\" % re.sub(r\"\\.bit$\", \"\", top_domain)).read()\n\t\t\t\tdata = json.loads(res)[\"value\"]\n\t\t\t\tfor key, val in data[\"zeronet\"].items():\n\t\t\t\t\tself.dns_cache[key+\".\"+top_domain] = [val, time.time()+60*60*5] # Cache for 5 hours\n\t\t\t\tself.saveDnsCache()\n\t\t\t\treturn data[\"zeronet\"].get(sub_domain)\n\t\t\t# Not found\n\t\t\treturn address\n\t\texcept Exception as err:\n\t\t\tlog.debug(\"Dnschain.info %s resolve error: %s\" % (domain, Debug.formatException(err)))\n\n\n\t# Resolve domain\n\t# Return: The address or None\n\tdef resolveDomain(self, domain):\n\t\tdomain = domain.lower()\n\t\tif self.dns_cache == None:\n\t\t\tself.loadDnsCache()\n\t\tif domain.count(\".\") < 2: # Its a topleved request, prepend @. to it\n\t\t\tdomain = \"@.\"+domain\n\n\t\tdomain_details = self.dns_cache.get(domain)\n\t\tif domain_details and time.time() < domain_details[1]: # Found in cache and its not expired\n\t\t\treturn domain_details[0]\n\t\telse:\n\t\t\t# Resovle dns using dnschain\n\t\t\tthread_dnschain_info = gevent.spawn(self.resolveDomainDnschainInfo, domain)\n\t\t\tthread_dnschain_net = gevent.spawn(self.resolveDomainDnschainNet, domain)\n\t\t\tgevent.joinall([thread_dnschain_net, thread_dnschain_info]) # Wait for finish\n\n\t\t\tif thread_dnschain_info.value and thread_dnschain_net.value: # Booth successfull\n\t\t\t\tif thread_dnschain_info.value == thread_dnschain_net.value: # Same returned value\n\t\t\t\t\treturn thread_dnschain_info.value \n\t\t\t\telse:\n\t\t\t\t\tlog.error(\"Dns %s missmatch: %s != %s\" % (domain, thread_dnschain_info.value, thread_dnschain_net.value))\n\n\t\t\t# Problem during resolve\n\t\t\tif domain_details: # Resolve failed, but we have it in the cache\n\t\t\t\tdomain_details[1] = time.time()+60*60 # Dont try again for 1 hour\n\t\t\t\treturn domain_details[0]\n\t\t\telse: # Not found in cache\n\t\t\t\tself.dns_cache[domain] = [None, time.time()+60] # Don't check again for 1 min\n\t\t\t\treturn None\n\n\n\t# Return or create site and start download site files\n\t# Return: Site or None if dns resolve failed\n\tdef need(self, address, all_file=True):\n\t\tif self.isDomain(address): # Its looks like a domain\n\t\t\taddress_resolved = self.resolveDomain(address)\n\t\t\tif address_resolved:\n\t\t\t\taddress = address_resolved\n\t\t\telse:\n\t\t\t\treturn None\n\t\t\n\t\treturn super(SiteManagerPlugin, self).need(address, all_file)\n\n\n\t# Return: Site object or None if not found\n\tdef get(self, address):\n\t\tif self.sites == None: # Not loaded yet\n\t\t\tself.load()\n\t\tif self.isDomain(address): # Its looks like a domain\n\t\t\taddress_resolved = self.resolveDomain(address)\n\t\t\tif address_resolved: # Domain found\n\t\t\t\tsite = self.sites.get(address_resolved)\n\t\t\t\tif site:\n\t\t\t\t\tsite_domain = site.settings.get(\"domain\")\n\t\t\t\t\tif site_domain != address:\n\t\t\t\t\t\tsite.settings[\"domain\"] = address\n\t\t\telse: # Domain not found\n\t\t\t\tsite = self.sites.get(address)\n\n\t\telse: # Access by site address\n\t\t\tsite = self.sites.get(address)\n\t\treturn site\n\n"
  },
  {
    "path": "plugins/disabled-Dnschain/UiRequestPlugin.py",
    "content": "import re\nfrom Plugin import PluginManager\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n\tdef __init__(self, server = None):\n\t\tfrom Site import SiteManager\n\t\tself.site_manager = SiteManager.site_manager\n\t\tsuper(UiRequestPlugin, self).__init__(server)\n\n\n\t# Media request\n\tdef actionSiteMedia(self, path):\n\t\tmatch = re.match(r\"/media/(?P<address>[A-Za-z0-9-]+\\.[A-Za-z0-9\\.-]+)(?P<inner_path>/.*|$)\", path)\n\t\tif match: # Its a valid domain, resolve first\n\t\t\tdomain = match.group(\"address\")\n\t\t\taddress = self.site_manager.resolveDomain(domain)\n\t\t\tif address:\n\t\t\t\tpath = \"/media/\"+address+match.group(\"inner_path\")\n\t\treturn super(UiRequestPlugin, self).actionSiteMedia(path) # Get the wrapper frame output\n\n\n\t# Is mediarequest allowed from that referer\n\tdef isMediaRequestAllowed(self, site_address, referer):\n\t\treferer_path = re.sub(\"http[s]{0,1}://.*?/\", \"/\", referer).replace(\"/media\", \"\") # Remove site address\n\t\treferer_site_address = re.match(r\"/(?P<address>[A-Za-z0-9\\.-]+)(?P<inner_path>/.*|$)\", referer_path).group(\"address\")\n\n\t\tif referer_site_address == site_address: # Referer site address as simple address\n\t\t\treturn True\n\t\telif self.site_manager.resolveDomain(referer_site_address) == site_address: # Referer site address as dns\n\t\t\treturn True\n\t\telse: # Invalid referer\n\t\t\treturn False\n\n"
  },
  {
    "path": "plugins/disabled-Dnschain/__init__.py",
    "content": "# This plugin is experimental, if you really want to enable uncomment the following lines:\n# import DnschainPlugin\n# import SiteManagerPlugin"
  },
  {
    "path": "plugins/disabled-DonationMessage/DonationMessagePlugin.py",
    "content": "import re\nfrom Plugin import PluginManager\n\n# Warning: If you modify the donation address then renmae the plugin's directory to \"MyDonationMessage\" to prevent the update script overwrite\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    # Inject a donation message to every page top right corner\n    def renderWrapper(self, *args, **kwargs):\n        body = super(UiRequestPlugin, self).renderWrapper(*args, **kwargs)  # Get the wrapper frame output\n\n        inject_html = \"\"\"\n            <style>\n             #donation_message { position: absolute; bottom: 0px; right: 20px; padding: 7px; font-family: Arial; font-size: 11px }\n            </style>\n            <a id='donation_message' href='https://blockchain.info/address/1QDhxQ6PraUZa21ET5fYUCPgdrwBomnFgX' target='_blank'>Please donate to help to keep this ZeroProxy alive</a>\n            </body>\n            </html>\n        \"\"\"\n\n        return re.sub(r\"</body>\\s*</html>\\s*$\", inject_html, body)\n"
  },
  {
    "path": "plugins/disabled-DonationMessage/__init__.py",
    "content": "from . import DonationMessagePlugin\n"
  },
  {
    "path": "plugins/disabled-Multiuser/MultiuserPlugin.py",
    "content": "import re\nimport sys\nimport json\n\nfrom Config import config\nfrom Plugin import PluginManager\nfrom Crypt import CryptBitcoin\nfrom . import UserPlugin\nfrom util.Flag import flag\nfrom Translate import translate as _\n\n# We can only import plugin host clases after the plugins are loaded\n@PluginManager.afterLoad\ndef importPluginnedClasses():\n    global UserManager\n    from User import UserManager\n\ntry:\n    local_master_addresses = set(json.load(open(\"%s/users.json\" % config.data_dir)).keys())  # Users in users.json\nexcept Exception as err:\n    local_master_addresses = set()\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def __init__(self, *args, **kwargs):\n        self.user_manager = UserManager.user_manager\n        super(UiRequestPlugin, self).__init__(*args, **kwargs)\n\n    # Create new user and inject user welcome message if necessary\n    # Return: Html body also containing the injection\n    def actionWrapper(self, path, extra_headers=None):\n\n        match = re.match(\"/(?P<address>[A-Za-z0-9\\._-]+)(?P<inner_path>/.*|$)\", path)\n        if not match:\n            return False\n\n        inner_path = match.group(\"inner_path\").lstrip(\"/\")\n        html_request = \".\" not in inner_path or inner_path.endswith(\".html\")  # Only inject html to html requests\n\n        user_created = False\n        if html_request:\n            user = self.getCurrentUser()  # Get user from cookie\n            if not user:  # No user found by cookie\n                user = self.user_manager.create()\n                user_created = True\n        else:\n            user = None\n\n        # Disable new site creation if --multiuser_no_new_sites enabled\n        if config.multiuser_no_new_sites:\n            path_parts = self.parsePath(path)\n            if not self.server.site_manager.get(match.group(\"address\")) and (not user or user.master_address not in local_master_addresses):\n                self.sendHeader(404)\n                return self.formatError(\"Not Found\", \"Adding new sites disabled on this proxy\", details=False)\n\n        if user_created:\n            if not extra_headers:\n                extra_headers = {}\n            extra_headers['Set-Cookie'] = \"master_address=%s;path=/;max-age=2592000;\" % user.master_address  # = 30 days\n\n        loggedin = self.get.get(\"login\") == \"done\"\n\n        back_generator = super(UiRequestPlugin, self).actionWrapper(path, extra_headers)  # Get the wrapper frame output\n\n        if not back_generator:  # Wrapper error or not string returned, injection not possible\n            return False\n\n        elif loggedin:\n            back = next(back_generator)\n            inject_html = \"\"\"\n                <!-- Multiser plugin -->\n                <script nonce=\"{script_nonce}\">\n                 setTimeout(function() {\n                    zeroframe.cmd(\"wrapperNotification\", [\"done\", \"{message}<br><small>You have been logged in successfully</small>\", 5000])\n                 }, 1000)\n                </script>\n                </body>\n                </html>\n            \"\"\".replace(\"\\t\", \"\")\n            if user.master_address in local_master_addresses:\n                message = \"Hello master!\"\n            else:\n                message = \"Hello again!\"\n            inject_html = inject_html.replace(\"{message}\", message)\n            inject_html = inject_html.replace(\"{script_nonce}\", self.getScriptNonce())\n            return iter([re.sub(b\"</body>\\s*</html>\\s*$\", inject_html.encode(), back)])  # Replace the </body></html> tags with the injection\n\n        else:  # No injection necessary\n            return back_generator\n\n    # Get the current user based on request's cookies\n    # Return: User object or None if no match\n    def getCurrentUser(self):\n        cookies = self.getCookies()\n        user = None\n        if \"master_address\" in cookies:\n            users = self.user_manager.list()\n            user = users.get(cookies[\"master_address\"])\n        return user\n\n\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def __init__(self, *args, **kwargs):\n        if config.multiuser_no_new_sites:\n            flag.no_multiuser(self.actionMergerSiteAdd)\n\n        super(UiWebsocketPlugin, self).__init__(*args, **kwargs)\n\n    # Let the page know we running in multiuser mode\n    def formatServerInfo(self):\n        server_info = super(UiWebsocketPlugin, self).formatServerInfo()\n        server_info[\"multiuser\"] = True\n        if \"ADMIN\" in self.site.settings[\"permissions\"]:\n            server_info[\"master_address\"] = self.user.master_address\n            is_multiuser_admin = config.multiuser_local or self.user.master_address in local_master_addresses\n            server_info[\"multiuser_admin\"] = is_multiuser_admin\n        return server_info\n\n    # Show current user's master seed\n    @flag.admin\n    def actionUserShowMasterSeed(self, to):\n        message = \"<b style='padding-top: 5px; display: inline-block'>Your unique private key:</b>\"\n        message += \"<div style='font-size: 84%%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px'>%s</div>\" % self.user.master_seed\n        message += \"<small>(Save it, you can access your account using this information)</small>\"\n        self.cmd(\"notification\", [\"info\", message])\n\n    # Logout user\n    @flag.admin\n    def actionUserLogout(self, to):\n        message = \"<b>You have been logged out.</b> <a href='#Login' class='button' id='button_notification'>Login to another account</a>\"\n        self.cmd(\"notification\", [\"done\", message, 1000000])  # 1000000 = Show ~forever :)\n\n        script = \"document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';\"\n        script += \"$('#button_notification').on('click', function() { zeroframe.cmd(\\\"userLoginForm\\\", []); });\"\n        self.cmd(\"injectScript\", script)\n        # Delete from user_manager\n        user_manager = UserManager.user_manager\n        if self.user.master_address in user_manager.users:\n            if not config.multiuser_local:\n                del user_manager.users[self.user.master_address]\n            self.response(to, \"Successful logout\")\n        else:\n            self.response(to, \"User not found\")\n\n    @flag.admin\n    def actionUserSet(self, to, master_address):\n        user_manager = UserManager.user_manager\n        user = user_manager.get(master_address)\n        if not user:\n            raise Exception(\"No user found\")\n\n        script = \"document.cookie = 'master_address=%s;path=/;max-age=2592000;';\" % master_address\n        script += \"zeroframe.cmd('wrapperReload', ['login=done']);\"\n        self.cmd(\"notification\", [\"done\", \"Successful login, reloading page...\"])\n        self.cmd(\"injectScript\", script)\n\n        self.response(to, \"ok\")\n\n    @flag.admin\n    def actionUserSelectForm(self, to):\n        if not config.multiuser_local:\n            raise Exception(\"Only allowed in multiuser local mode\")\n        user_manager = UserManager.user_manager\n        body = \"<span style='padding-bottom: 5px; display: inline-block'>\" + \"Change account:\" + \"</span>\"\n        for master_address, user in user_manager.list().items():\n            is_active = self.user.master_address == master_address\n            if user.certs:\n                first_cert = next(iter(user.certs.keys()))\n                title = \"%s@%s\" % (user.certs[first_cert][\"auth_user_name\"], first_cert)\n            else:\n                title = user.master_address\n                if len(user.sites) < 2 and not is_active:  # Avoid listing ad-hoc created users\n                    continue\n            if is_active:\n                css_class = \"active\"\n            else:\n                css_class = \"noclass\"\n            body += \"<a href='#Select+user' class='select select-close user %s' title='%s'>%s</a>\" % (css_class, user.master_address, title)\n\n        script = \"\"\"\n             $(\".notification .select.user\").on(\"click\", function() {\n                $(\".notification .select\").removeClass('active')\n                zeroframe.response(%s, this.title)\n                return false\n             })\n        \"\"\" % self.next_message_id\n\n        self.cmd(\"notification\", [\"ask\", body], lambda master_address: self.actionUserSet(to, master_address))\n        self.cmd(\"injectScript\", script)\n\n    # Show login form\n    def actionUserLoginForm(self, to):\n        self.cmd(\"prompt\", [\"<b>Login</b><br>Your private key:\", \"password\", \"Login\"], self.responseUserLogin)\n\n    # Login form submit\n    def responseUserLogin(self, master_seed):\n        user_manager = UserManager.user_manager\n        user = user_manager.get(CryptBitcoin.privatekeyToAddress(master_seed))\n        if not user:\n            user = user_manager.create(master_seed=master_seed)\n        if user.master_address:\n            script = \"document.cookie = 'master_address=%s;path=/;max-age=2592000;';\" % user.master_address\n            script += \"zeroframe.cmd('wrapperReload', ['login=done']);\"\n            self.cmd(\"notification\", [\"done\", \"Successful login, reloading page...\"])\n            self.cmd(\"injectScript\", script)\n        else:\n            self.cmd(\"notification\", [\"error\", \"Error: Invalid master seed\"])\n            self.actionUserLoginForm(0)\n\n    def hasCmdPermission(self, cmd):\n        flags = flag.db.get(self.getCmdFuncName(cmd), ())\n        is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses\n        if is_public_proxy_user and \"no_multiuser\" in flags:\n            self.cmd(\"notification\", [\"info\", _(\"This function ({cmd}) is disabled on this proxy!\")])\n            return False\n        else:\n            return super(UiWebsocketPlugin, self).hasCmdPermission(cmd)\n\n    def actionCertAdd(self, *args, **kwargs):\n        super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs)\n        master_seed = self.user.master_seed\n        message = \"\"\"\n            <style>\n            .masterseed {\n                font-size: 85%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px; width: 100%;\n                box-sizing: border-box; border: 0px; text-align: center; cursor: pointer;\n            }\n            </style>\n            <b>Hello, welcome to ZeroProxy!</b><div style='margin-top: 8px'>A new, unique account created for you:</div>\n            <input type='text' class='masterseed' id='button_notification_masterseed' value='Click here to show' readonly/>\n            <div style='text-align: center; font-size: 85%; margin-bottom: 10px;'>\n             or <a href='#Download' id='button_notification_download'\n             class='masterseed_download' download='zeronet_private_key.backup'>Download backup as text file</a>\n            </div>\n            <div>\n             This is your private key, <b>save it</b>, so you can login next time.<br>\n             <b>Warning: Without this key, your account will be lost forever!</b>\n            </div><br>\n            <a href='#' class='button' style='margin-left: 0px'>Ok, Saved it!</a><br><br>\n            <small>This site allows you to browse ZeroNet content, but if you want to secure your account <br>\n            and help to keep the network alive, then please run your own <a href='https://zeronet.io' target='_blank'>ZeroNet client</a>.</small>\n        \"\"\"\n\n        self.cmd(\"notification\", [\"info\", message])\n\n        script = \"\"\"\n            $(\"#button_notification_masterseed\").on(\"click\", function() {\n                this.value = \"{master_seed}\"; this.setSelectionRange(0,100);\n            })\n            $(\"#button_notification_download\").on(\"mousedown\", function() {\n                this.href = window.URL.createObjectURL(new Blob([\"ZeroNet user master seed:\\\\r\\\\n{master_seed}\"]))\n            })\n        \"\"\".replace(\"{master_seed}\", master_seed)\n        self.cmd(\"injectScript\", script)\n\n    def actionPermissionAdd(self, to, permission):\n        is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses\n        if permission == \"NOSANDBOX\" and is_public_proxy_user:\n            self.cmd(\"notification\", [\"info\", \"You can't disable sandbox on this proxy!\"])\n            self.response(to, {\"error\": \"Denied by proxy\"})\n            return False\n        else:\n            return super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"Multiuser plugin\")\n        group.add_argument('--multiuser_local', help=\"Enable unsafe Ui functions and write users to disk\", action='store_true')\n        group.add_argument('--multiuser_no_new_sites', help=\"Denies adding new sites by normal users\", action='store_true')\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/disabled-Multiuser/Test/TestMultiuser.py",
    "content": "import pytest\nimport json\nfrom Config import config\nfrom User import UserManager\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestMultiuser:\n    def testMemorySave(self, user):\n        # It should not write users to disk\n        users_before = open(\"%s/users.json\" % config.data_dir).read()\n        user = UserManager.user_manager.create()\n        user.save()\n        assert open(\"%s/users.json\" % config.data_dir).read() == users_before\n"
  },
  {
    "path": "plugins/disabled-Multiuser/Test/conftest.py",
    "content": "from src.Test.conftest import *\n"
  },
  {
    "path": "plugins/disabled-Multiuser/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6\nmarkers =\n    webtest: mark a test as a webtest."
  },
  {
    "path": "plugins/disabled-Multiuser/UserPlugin.py",
    "content": "from Config import config\nfrom Plugin import PluginManager\n\nallow_reload = False\n\n@PluginManager.registerTo(\"UserManager\")\nclass UserManagerPlugin(object):\n    def load(self):\n        if not config.multiuser_local:\n            # In multiuser mode do not load the users\n            if not self.users:\n                self.users = {}\n            return self.users\n        else:\n            return super(UserManagerPlugin, self).load()\n\n    # Find user by master address\n    # Return: User or None\n    def get(self, master_address=None):\n        users = self.list()\n        if master_address in users:\n            user = users[master_address]\n        else:\n            user = None\n        return user\n\n\n@PluginManager.registerTo(\"User\")\nclass UserPlugin(object):\n    # In multiuser mode users data only exits in memory, dont write to data/user.json\n    def save(self):\n        if not config.multiuser_local:\n            return False\n        else:\n            return super(UserPlugin, self).save()\n"
  },
  {
    "path": "plugins/disabled-Multiuser/__init__.py",
    "content": "from . import MultiuserPlugin\n"
  },
  {
    "path": "plugins/disabled-Multiuser/plugin_info.json",
    "content": "{\n\t\"name\": \"MultiUser\",\n\t\"description\": \"Cookie based multi-users support on your ZeroNet web interface.\",\n\t\"default\": \"disabled\"\n}"
  },
  {
    "path": "plugins/disabled-StemPort/StemPortPlugin.py",
    "content": "import logging\nimport traceback\n\nimport socket\nimport stem\nfrom stem import Signal\nfrom stem.control import Controller\nfrom stem.socket import ControlPort\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\n\nif config.tor != \"disable\":\n    from gevent import monkey\n    monkey.patch_time()\n    monkey.patch_socket(dns=False)\n    monkey.patch_thread()\n    print(\"Stem Port Plugin: modules are patched.\")\nelse:\n    print(\"Stem Port Plugin: Tor mode disabled. Module patching skipped.\")\n\n\nclass PatchedControlPort(ControlPort):\n    def _make_socket(self):\n        try:\n            if \"socket_noproxy\" in dir(socket):  # Socket proxy-patched, use non-proxy one\n                control_socket = socket.socket_noproxy(socket.AF_INET, socket.SOCK_STREAM)\n            else:\n                control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n\n            # TODO: repeated code - consider making a separate method\n\n            control_socket.connect((self._control_addr, self._control_port))\n            return control_socket\n        except socket.error as exc:\n            raise stem.SocketError(exc)\n\ndef from_port(address = '127.0.0.1', port = 'default'):\n    import stem.connection\n\n    if not stem.util.connection.is_valid_ipv4_address(address):\n        raise ValueError('Invalid IP address: %s' % address)\n    elif port != 'default' and not stem.util.connection.is_valid_port(port):\n        raise ValueError('Invalid port: %s' % port)\n\n    if port == 'default':\n        raise ValueError('Must specify a port')\n    else:\n        control_port = PatchedControlPort(address, port)\n\n    return Controller(control_port)\n\n\n@PluginManager.registerTo(\"TorManager\")\nclass TorManagerPlugin(object):\n\n    def connectController(self):\n        self.log.info(\"Authenticate using Stem... %s:%s\" % (self.ip, self.port))\n\n        try:\n            with self.lock:\n                if config.tor_password:\n                    controller = from_port(port=self.port, password=config.tor_password)\n                else:\n                    controller = from_port(port=self.port)\n                controller.authenticate()\n                self.controller = controller\n                self.status = \"Connected (via Stem)\"\n        except Exception as err:\n            print(\"\\n\")\n            traceback.print_exc()\n            print(\"\\n\")\n\n            self.controller = None\n            self.status = \"Error (%s)\" % err\n            self.log.error(\"Tor stem connect error: %s\" % Debug.formatException(err))\n\n        return self.controller\n\n\n    def disconnect(self):\n        self.controller.close()\n        self.controller = None\n\n\n    def resetCircuits(self):\n        try:\n            self.controller.signal(Signal.NEWNYM)\n        except Exception as err:\n            self.status = \"Stem reset circuits error (%s)\" % err\n            self.log.error(\"Stem reset circuits error: %s\" % err)\n\n\n    def makeOnionAndKey(self):\n        try:\n            service = self.controller.create_ephemeral_hidden_service(\n                {self.fileserver_port: self.fileserver_port},\n                await_publication = False\n            )\n            if service.private_key_type != \"RSA1024\":\n                raise Exception(\"ZeroNet doesn't support crypto \" + service.private_key_type)\n\n            self.log.debug(\"Stem created %s.onion (async descriptor publication)\" % service.service_id)\n\n            return (service.service_id, service.private_key)\n\n        except Exception as err:\n            self.status = \"AddOnion error (Stem: %s)\" % err\n            self.log.error(\"Failed to create hidden service with Stem: \" + err)\n            return False\n\n\n    def delOnion(self, address):\n        try:\n            self.controller.remove_ephemeral_hidden_service(address)\n            return True\n        except Exception as err:\n            self.status = \"DelOnion error (Stem: %s)\" % err\n            self.log.error(\"Stem failed to delete %s.onion: %s\" % (address, err))\n            self.disconnect() # Why?\n            return False\n\n\n    def request(self, cmd):\n        with self.lock:\n            if not self.enabled:\n                return False\n            else:\n                self.log.error(\"[WARNING] StemPort self.request should not be called\")\n                return \"\"\n\n    def send(self, cmd, conn=None):\n        self.log.error(\"[WARNING] StemPort self.send should not be called\")\n        return \"\"\n"
  },
  {
    "path": "plugins/disabled-StemPort/__init__.py",
    "content": "try:\r\n    from stem.control import Controller\r\n    stem_found = True\r\nexcept Exception as err:\r\n    print((\"STEM NOT FOUND! %s\" % err))\r\n    stem_found = False\r\n\r\nif stem_found:\r\n    print(\"Starting Stem plugin...\")\r\n    from . import StemPortPlugin\r\n"
  },
  {
    "path": "plugins/disabled-UiPassword/UiPasswordPlugin.py",
    "content": "import string\nimport random\nimport time\nimport json\nimport re\nimport os\n\nfrom Config import config\nfrom Plugin import PluginManager\nfrom util import helper\n\n\nplugin_dir = os.path.dirname(__file__)\n\nif \"sessions\" not in locals().keys():  # To keep sessions between module reloads\n    sessions = {}\n    whitelisted_client_ids = {}\n\n\ndef showPasswordAdvice(password):\n    error_msgs = []\n    if not password or not isinstance(password, str):\n        error_msgs.append(\"You have enabled <b>UiPassword</b> plugin, but you forgot to set a password!\")\n    elif len(password) < 8:\n        error_msgs.append(\"You are using a very short UI password!\")\n    return error_msgs\n\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    sessions = sessions\n    whitelisted_client_ids = whitelisted_client_ids\n    last_cleanup = time.time()\n\n    def getClientId(self):\n        return self.env[\"REMOTE_ADDR\"] + \" - \" + self.env[\"HTTP_USER_AGENT\"]\n\n    def whitelistClientId(self, session_id=None):\n        if not session_id:\n            session_id = self.getCookies().get(\"session_id\")\n        client_id = self.getClientId()\n        if client_id in self.whitelisted_client_ids:\n            self.whitelisted_client_ids[client_id][\"updated\"] = time.time()\n            return False\n\n        self.whitelisted_client_ids[client_id] = {\n            \"added\": time.time(),\n            \"updated\": time.time(),\n            \"session_id\": session_id\n        }\n\n    def route(self, path):\n        # Restict Ui access by ip\n        if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict:\n            return self.error403(details=False)\n        if path.endswith(\"favicon.ico\"):\n            return self.actionFile(\"src/Ui/media/img/favicon.ico\")\n        else:\n            if config.ui_password:\n                if time.time() - self.last_cleanup > 60 * 60:  # Cleanup expired sessions every hour\n                    self.sessionCleanup()\n                # Validate session\n                session_id = self.getCookies().get(\"session_id\")\n                if session_id not in self.sessions and self.getClientId() not in self.whitelisted_client_ids:\n                    # Invalid session id and not whitelisted ip: display login\n                    return self.actionLogin()\n            return super(UiRequestPlugin, self).route(path)\n\n    def actionWrapper(self, path, *args, **kwargs):\n        if config.ui_password and self.isWrapperNecessary(path):\n            session_id = self.getCookies().get(\"session_id\")\n            if session_id not in self.sessions:\n                # We only accept cookie based auth on wrapper\n                return self.actionLogin()\n            else:\n                self.whitelistClientId()\n\n        return super().actionWrapper(path, *args, **kwargs)\n\n    # Action: Login\n    @helper.encodeResponse\n    def actionLogin(self):\n        template = open(plugin_dir + \"/login.html\").read()\n        self.sendHeader()\n        posted = self.getPosted()\n        if posted:  # Validate http posted data\n            if self.sessionCheckPassword(posted.get(\"password\")):\n                # Valid password, create session\n                session_id = self.randomString(26)\n                self.sessions[session_id] = {\n                    \"added\": time.time(),\n                    \"keep\": posted.get(\"keep\")\n                }\n                self.whitelistClientId(session_id)\n\n                # Redirect to homepage or referer\n                url = self.env.get(\"HTTP_REFERER\", \"\")\n                if not url or re.sub(r\"\\?.*\", \"\", url).endswith(\"/Login\"):\n                    url = \"/\" + config.homepage\n                cookie_header = ('Set-Cookie', \"session_id=%s;path=/;max-age=2592000;\" % session_id)  # Max age = 30 days\n                self.start_response('301 Redirect', [('Location', url), cookie_header])\n                yield \"Redirecting...\"\n\n            else:\n                # Invalid password, show login form again\n                template = template.replace(\"{result}\", \"bad_password\")\n        yield template\n\n    def randomString(self, nchars):\n        return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(nchars))\n\n    def sessionCheckPassword(self, password):\n        return password == config.ui_password\n\n    def sessionDelete(self, session_id):\n       del self.sessions[session_id]\n\n       for client_id in list(self.whitelisted_client_ids):\n            if self.whitelisted_client_ids[client_id][\"session_id\"] == session_id:\n                del self.whitelisted_client_ids[client_id]\n\n    def sessionCleanup(self):\n        self.last_cleanup = time.time()\n        for session_id, session in list(self.sessions.items()):\n            if session[\"keep\"] and time.time() - session[\"added\"] > 60 * 60 * 24 * 60:  # Max 60days for keep sessions\n                self.sessionDelete(session_id)\n            elif not session[\"keep\"] and time.time() - session[\"added\"] > 60 * 60 * 24:  # Max 24h for non-keep sessions\n                self.sessionDelete(session_id)\n\n    # Action: Display sessions\n    @helper.encodeResponse\n    def actionSessions(self):\n        self.sendHeader()\n        yield \"<pre>\"\n        yield json.dumps(self.sessions, indent=4)\n        yield \"\\r\\n\"\n        yield json.dumps(self.whitelisted_client_ids, indent=4)\n\n    # Action: Logout\n    @helper.encodeResponse\n    def actionLogout(self):\n        # Session id has to passed as get parameter or called without referer to avoid remote logout\n        session_id = self.getCookies().get(\"session_id\")\n        if not self.env.get(\"HTTP_REFERER\") or session_id == self.get.get(\"session_id\"):\n            if session_id in self.sessions:\n                self.sessionDelete(session_id)\n\n            self.start_response('301 Redirect', [\n                ('Location', \"/\"),\n                ('Set-Cookie', \"session_id=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT\")\n            ])\n            yield \"Redirecting...\"\n        else:\n            self.sendHeader()\n            yield \"Error: Invalid session id\"\n\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"UiPassword plugin\")\n        group.add_argument('--ui_password', help='Password to access UiServer', default=None, metavar=\"password\")\n\n        return super(ConfigPlugin, self).createArguments()\n\n\nfrom Translate import translate as lang\n@PluginManager.registerTo(\"UiWebsocket\")\nclass UiWebsocketPlugin(object):\n    def actionUiLogout(self, to):\n        permissions = self.getPermissions(to)\n        if \"ADMIN\" not in permissions:\n            return self.response(to, \"You don't have permission to run this command\")\n\n        session_id = self.request.getCookies().get(\"session_id\", \"\")\n        self.cmd(\"redirect\", '/Logout?session_id=%s' % session_id)\n\n    def addHomepageNotifications(self):\n        error_msgs = showPasswordAdvice(config.ui_password)\n        for msg in error_msgs:\n            self.site.notifications.append([\"error\", lang[msg]])\n\n        return super(UiWebsocketPlugin, self).addHomepageNotifications()\n"
  },
  {
    "path": "plugins/disabled-UiPassword/__init__.py",
    "content": "from . import UiPasswordPlugin"
  },
  {
    "path": "plugins/disabled-UiPassword/login.html",
    "content": "<html>\n<head>\n <title>Log In</title>\n <meta name=\"viewport\" id=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n\n<style>\nbody {\n\tbackground-color: #323C4D; font-family: \"Segoe UI\", Helvetica, Arial; font-weight: lighter;\n    font-size: 22px; color: #333; letter-spacing: 1px; color: white; overflow: hidden;\n}\n.login { left: 50%; position: absolute; top: 50%; transform: translateX(-50%) translateY(-50%); -webkit-transform: translateX(-50%) translateY(-50%); width: 100%; max-width: 370px; text-align: center; }\n\n*:focus { outline: 0; }\ninput[type=text], input[type=password] {\n\tpadding: 10px 0px; border: 0px; display: block; margin: 15px 0px; width: 100%; border-radius: 30px; transition: 0.3s ease-out; background-color: #DDD;\n\ttext-align: center; font-family: \"Segoe UI\", Helvetica, Arial; font-weight: lighter; font-size: 28px; border: 2px solid #323C4D;\n}\ninput[type=text]:focus, input[type=password]:focus {\n\tborder: 2px solid #FFF; background-color: #FFF;\n}\ninput[type=checkbox] { opacity: 0; }\ninput[type=checkbox]:checked + label { color: white; }\ninput[type=checkbox]:focus + label::before { background-color: #435065; }\ninput[type=checkbox]:checked + label::before { box-shadow: inset 0px 0px 0px 5px white; background-color: #4DCC6E; }\ninput.error { border: 2px solid #F44336 !important; animation: shake 1s }\nlabel::before {\n\tcontent: \"\"; width: 20px; height: 20px; background-color: #323C4D;\n\tdisplay: inline-block; margin-left: -20px; border-radius: 15px; box-shadow: inset 0px 0px 0px 2px #9EA5B3;\n\ttransition: all 0.1s; margin-right: 7px; position: relative; top: 2px;\n}\nlabel { vertical-align: -1px; color: #9EA5B3; transition: all 0.3s; }\n\n.button {\n\tpadding: 13px; display: inline-block; margin: 15px 0px; width: 100%; border-radius: 30px; text-align: center; white-space: nowrap;\n\tfont-size: 28px; color: #333; background: linear-gradient(45deg, #6B14D3 0, #7A26E2 25%, #4962DD 90%);\n    box-sizing: border-box; margin-top: 50px; color: white; text-decoration: none; transition: 0.3s ease-out;\n}\n.button:hover, .button:focus { box-shadow: 0px 5px 30px rgba(0,0,0,0.3); }\n.button:active { transform: translateY(1px); box-shadow: 0px 0px 20px rgba(0,0,0,0.5); transition: none; }\n\n#login_form_submit { display: none; }\n\n.login-anim { animation: login 1s cubic-bezier(0.785, 0.135, 0.15, 0.86) forwards; }\n\n@keyframes login {\n    0%   { width: 100%; }\n    60%  { width: 63px; transform: scale(1); color: rgba(255,255,255,0); }\n    70%  { width: 63px; transform: scale(1); color: rgba(255,255,255,0); }\n    100% { transform: scale(80); width: 63px; color: rgba(255,255,255,0); }\n}\n\n@keyframes shake {\n    0%, 100% { transform: translateX(0); }\n    10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }\n    20%, 40%, 60%, 80% { transform: translateX(10px); }\n}\n</style>\n\n<body>\n\n\n<div class=\"login\">\n <form action=\"\" method=\"post\" id=\"login_form\" onkeypress=\"return onFormKeypress(event)\">\n  <!--<input type=\"text\" name=\"username\" placeholder=\"Username\" required/>-->\n  <input type=\"password\" name=\"password\" placeholder=\"Password\" required/>\n  <input type=\"checkbox\" name=\"keep\" id=\"keep\"><label for=\"keep\">Keep me logged in</label>\n  <div style=\"clear: both\"></div>\n  <a href=\"#\" class=\"button\" onclick=\"return submit()\" id=\"login_button\"><span>Log In</span></a>\n  <input type=\"submit\" id=\"login_form_submit\"/>\n </form>\n</div>\n\n\n<script>\n\nfunction onFormKeypress(event) {\n\tif (event.keyCode == 13) {\n\t\tsubmit()\n\t\treturn false\n\t}\n}\n\nfunction submit() {\n\tvar form = document.getElementById(\"login_form\")\n\tif (form.checkValidity()) {\n\t\tdocument.getElementById(\"login_button\").className = \"button login-anim\"\n\t\tsetTimeout(function() {\n\t\t\tform.submit()\n\t\t}, 1000)\n\t} else {\n\t\tform.submit()\n\t}\n\treturn false\n}\n\nfunction badPassword() {\n\tvar elem = document.getElementsByName(\"password\")[0]\n\telem.className = \"error\"\n\telem.placeholder = \"Wrong Password\"\n\telem.focus()\n\telem.addEventListener('input', function() {\n\t\telem.className = \"\"\n\t\telem.placeholder = \"Password\"\n\t})\n}\n\nresult = \"{result}\"\n\nif (result == \"bad_password\")\n\tbadPassword()\n\n</script>\n\n</body>\n</html>\n"
  },
  {
    "path": "plugins/disabled-UiPassword/plugin_info.json",
    "content": "{\n\t\"name\": \"UiPassword\",\n\t\"description\": \"Password based autentication on the web interface.\",\n\t\"default\": \"disabled\"\n}"
  },
  {
    "path": "plugins/disabled-ZeronameLocal/SiteManagerPlugin.py",
    "content": "import logging, json, os, re, sys, time, socket\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\nfrom http.client import HTTPSConnection, HTTPConnection, HTTPException\nfrom base64 import b64encode\n\nallow_reload = False # No reload supported\n\n@PluginManager.registerTo(\"SiteManager\")\nclass SiteManagerPlugin(object):\n    def load(self, *args, **kwargs):\n        super(SiteManagerPlugin, self).load(*args, **kwargs)\n        self.log = logging.getLogger(\"ZeronetLocal Plugin\")\n        self.error_message = None\n        if not config.namecoin_host or not config.namecoin_rpcport or not config.namecoin_rpcuser or not config.namecoin_rpcpassword:\n            self.error_message = \"Missing parameters\"\n            self.log.error(\"Missing parameters to connect to namecoin node. Please check all the arguments needed with '--help'. Zeronet will continue working without it.\")\n            return\n\n        url = \"%(host)s:%(port)s\" % {\"host\": config.namecoin_host, \"port\": config.namecoin_rpcport}\n        self.c = HTTPConnection(url, timeout=3)\n        user_pass = \"%(user)s:%(password)s\" % {\"user\": config.namecoin_rpcuser, \"password\": config.namecoin_rpcpassword}\n        userAndPass = b64encode(bytes(user_pass, \"utf-8\")).decode(\"ascii\")\n        self.headers = {\"Authorization\" : \"Basic %s\" %  userAndPass, \"Content-Type\": \" application/json \" }\n\n        payload = json.dumps({\n            \"jsonrpc\": \"2.0\",\n            \"id\": \"zeronet\",\n            \"method\": \"ping\",\n            \"params\": []\n        })\n\n        try:\n            self.c.request(\"POST\", \"/\", payload, headers=self.headers)\n            response = self.c.getresponse()\n            data = response.read()\n            self.c.close()\n            if response.status == 200:\n                result = json.loads(data.decode())[\"result\"]\n            else:\n                raise Exception(response.reason)\n        except Exception as err:\n            self.log.error(\"The Namecoin node is unreachable. Please check the configuration value are correct. Zeronet will continue working without it.\")\n            self.error_message = err\n        self.cache = dict()\n\n    # Checks if it's a valid address\n    def isAddress(self, address):\n        return self.isBitDomain(address) or super(SiteManagerPlugin, self).isAddress(address)\n\n    # Return: True if the address is domain\n    def isDomain(self, address):\n        return self.isBitDomain(address) or super(SiteManagerPlugin, self).isDomain(address)\n\n    # Return: True if the address is .bit domain\n    def isBitDomain(self, address):\n        return re.match(r\"(.*?)([A-Za-z0-9_-]+\\.bit)$\", address)\n\n    # Return: Site object or None if not found\n    def get(self, address):\n        if self.isBitDomain(address):  # Its looks like a domain\n            address_resolved = self.resolveDomain(address)\n            if address_resolved:  # Domain found\n                site = self.sites.get(address_resolved)\n                if site:\n                    site_domain = site.settings.get(\"domain\")\n                    if site_domain != address:\n                        site.settings[\"domain\"] = address\n            else:  # Domain not found\n                site = self.sites.get(address)\n\n        else:  # Access by site address\n            site = super(SiteManagerPlugin, self).get(address)\n        return site\n\n    # Return or create site and start download site files\n    # Return: Site or None if dns resolve failed\n    def need(self, address, *args, **kwargs):\n        if self.isBitDomain(address):  # Its looks like a domain\n            address_resolved = self.resolveDomain(address)\n            if address_resolved:\n                address = address_resolved\n            else:\n                return None\n\n        return super(SiteManagerPlugin, self).need(address, *args, **kwargs)\n\n    # Resolve domain\n    # Return: The address or None\n    def resolveDomain(self, domain):\n        domain = domain.lower()\n\n        #remove .bit on end\n        if domain[-4:] == \".bit\":\n            domain = domain[0:-4]\n\n        domain_array = domain.split(\".\")\n\n        if self.error_message:\n            self.log.error(\"Not able to connect to Namecoin node : {!s}\".format(self.error_message))\n            return None\n\n        if len(domain_array) > 2:\n            self.log.error(\"Too many subdomains! Can only handle one level (eg. staging.mixtape.bit)\")\n            return None\n\n        subdomain = \"\"\n        if len(domain_array) == 1:\n            domain = domain_array[0]\n        else:\n            subdomain = domain_array[0]\n            domain = domain_array[1]\n\n        if domain in self.cache:\n            delta = time.time() - self.cache[domain][\"time\"]\n            if delta < 3600:\n                # Must have been less than 1hour\n                return self.cache[domain][\"addresses_resolved\"][subdomain]\n\n        payload = json.dumps({\n            \"jsonrpc\": \"2.0\",\n            \"id\": \"zeronet\",\n            \"method\": \"name_show\",\n            \"params\": [\"d/\"+domain]\n        })\n\n        try:\n            self.c.request(\"POST\", \"/\", payload, headers=self.headers)\n            response = self.c.getresponse()\n            data = response.read()\n            self.c.close()\n            domain_object = json.loads(data.decode())[\"result\"]\n        except Exception as err:\n            #domain doesn't exist\n            return None\n\n        if \"zeronet\" in domain_object[\"value\"]:\n            zeronet_domains = json.loads(domain_object[\"value\"])[\"zeronet\"]\n\n            if isinstance(zeronet_domains, str):\n                # {\n                #    \"zeronet\":\"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9\"\n                # } is valid\n                zeronet_domains = {\"\": zeronet_domains}\n\n            self.cache[domain] = {\"addresses_resolved\": zeronet_domains, \"time\": time.time()}\n\n        elif \"map\" in domain_object[\"value\"]:\n            # Namecoin standard use {\"map\": { \"blog\": {\"zeronet\": \"1D...\"} }}\n            data_map = json.loads(domain_object[\"value\"])[\"map\"]\n\n            zeronet_domains = dict()\n            for subdomain in data_map:\n                if \"zeronet\" in data_map[subdomain]:\n                    zeronet_domains[subdomain] = data_map[subdomain][\"zeronet\"]\n            if \"zeronet\" in data_map and isinstance(data_map[\"zeronet\"], str):\n                # {\"map\":{\n                #    \"zeronet\":\"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9\",\n                # }}\n                zeronet_domains[\"\"] = data_map[\"zeronet\"]\n\n            self.cache[domain] = {\"addresses_resolved\": zeronet_domains, \"time\": time.time()}\n\n        else:\n            # No Zeronet address registered\n            return None\n\n        return self.cache[domain][\"addresses_resolved\"][subdomain]\n\n@PluginManager.registerTo(\"ConfigPlugin\")\nclass ConfigPlugin(object):\n    def createArguments(self):\n        group = self.parser.add_argument_group(\"Zeroname Local plugin\")\n        group.add_argument('--namecoin_host', help=\"Host to namecoin node (eg. 127.0.0.1)\")\n        group.add_argument('--namecoin_rpcport', help=\"Port to connect (eg. 8336)\")\n        group.add_argument('--namecoin_rpcuser', help=\"RPC user to connect to the namecoin node (eg. nofish)\")\n        group.add_argument('--namecoin_rpcpassword', help=\"RPC password to connect to namecoin node\")\n\n        return super(ConfigPlugin, self).createArguments()\n"
  },
  {
    "path": "plugins/disabled-ZeronameLocal/UiRequestPlugin.py",
    "content": "import re\nfrom Plugin import PluginManager\n\n@PluginManager.registerTo(\"UiRequest\")\nclass UiRequestPlugin(object):\n    def __init__(self, *args, **kwargs):\n        from Site import SiteManager\n        self.site_manager = SiteManager.site_manager\n        super(UiRequestPlugin, self).__init__(*args, **kwargs)\n\n\n    # Media request\n    def actionSiteMedia(self, path):\n        match = re.match(r\"/media/(?P<address>[A-Za-z0-9-]+\\.[A-Za-z0-9\\.-]+)(?P<inner_path>/.*|$)\", path)\n        if match: # Its a valid domain, resolve first\n            domain = match.group(\"address\")\n            address = self.site_manager.resolveDomain(domain)\n            if address:\n                path = \"/media/\"+address+match.group(\"inner_path\")\n        return super(UiRequestPlugin, self).actionSiteMedia(path) # Get the wrapper frame output\n\n\n    # Is mediarequest allowed from that referer\n    def isMediaRequestAllowed(self, site_address, referer):\n        referer_path = re.sub(\"http[s]{0,1}://.*?/\", \"/\", referer).replace(\"/media\", \"\") # Remove site address\n        referer_path = re.sub(r\"\\?.*\", \"\", referer_path) # Remove http params\n\n        if self.isProxyRequest(): # Match to site domain\n            referer = re.sub(\"^http://zero[/]+\", \"http://\", referer) # Allow /zero access\n            referer_site_address = re.match(\"http[s]{0,1}://(.*?)(/|$)\", referer).group(1)\n        else: # Match to request path\n            referer_site_address = re.match(r\"/(?P<address>[A-Za-z0-9\\.-]+)(?P<inner_path>/.*|$)\", referer_path).group(\"address\")\n\n        if referer_site_address == site_address: # Referer site address as simple address\n            return True\n        elif self.site_manager.resolveDomain(referer_site_address) == site_address: # Referer site address as dns\n            return True\n        else: # Invalid referer\n            return False\n"
  },
  {
    "path": "plugins/disabled-ZeronameLocal/__init__.py",
    "content": "from . import UiRequestPlugin\nfrom . import SiteManagerPlugin"
  },
  {
    "path": "requirements.txt",
    "content": "gevent==1.4.0; python_version <= \"3.6\"\ngreenlet==0.4.16; python_version <= \"3.6\"\ngevent>=20.9.0; python_version >= \"3.7\"\nmsgpack>=0.4.4\nbase58\nmerkletools\nrsa\nPySocks>=1.6.8\npyasn1\nwebsocket_client\ngevent-ws\ncoincurve\nmaxminddb\n"
  },
  {
    "path": "src/Config.py",
    "content": "import argparse\nimport sys\nimport os\nimport locale\nimport re\nimport configparser\nimport logging\nimport logging.handlers\nimport stat\nimport time\n\n\nclass Config(object):\n\n    def __init__(self, argv):\n        self.version = \"0.7.2\"\n        self.rev = 4555\n        self.argv = argv\n        self.action = None\n        self.test_parser = None\n        self.pending_changes = {}\n        self.need_restart = False\n        self.keys_api_change_allowed = set([\n            \"tor\", \"fileserver_port\", \"language\", \"tor_use_bridges\", \"trackers_proxy\", \"trackers\",\n            \"trackers_file\", \"open_browser\", \"log_level\", \"fileserver_ip_type\", \"ip_external\", \"offline\",\n            \"threads_fs_read\", \"threads_fs_write\", \"threads_crypt\", \"threads_db\"\n        ])\n        self.keys_restart_need = set([\n            \"tor\", \"fileserver_port\", \"fileserver_ip_type\", \"threads_fs_read\", \"threads_fs_write\", \"threads_crypt\", \"threads_db\"\n        ])\n        self.start_dir = self.getStartDir()\n\n        self.config_file = self.start_dir + \"/zeronet.conf\"\n        self.data_dir = self.start_dir + \"/data\"\n        self.log_dir = self.start_dir + \"/log\"\n        self.openssl_lib_file = None\n        self.openssl_bin_file = None\n\n        self.trackers_file = False\n        self.createParser()\n        self.createArguments()\n\n    def createParser(self):\n        # Create parser\n        self.parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n        self.parser.register('type', 'bool', self.strToBool)\n        self.subparsers = self.parser.add_subparsers(title=\"Action to perform\", dest=\"action\")\n\n    def __str__(self):\n        return str(self.arguments).replace(\"Namespace\", \"Config\")  # Using argparse str output\n\n    # Convert string to bool\n    def strToBool(self, v):\n        return v.lower() in (\"yes\", \"true\", \"t\", \"1\")\n\n    def getStartDir(self):\n        this_file = os.path.abspath(__file__).replace(\"\\\\\", \"/\").rstrip(\"cd\")\n\n        if \"--start_dir\" in self.argv:\n            start_dir = self.argv[self.argv.index(\"--start_dir\") + 1]\n        elif this_file.endswith(\"/Contents/Resources/core/src/Config.py\"):\n            # Running as ZeroNet.app\n            if this_file.startswith(\"/Application\") or this_file.startswith(\"/private\") or this_file.startswith(os.path.expanduser(\"~/Library\")):\n                # Runnig from non-writeable directory, put data to Application Support\n                start_dir = os.path.expanduser(\"~/Library/Application Support/ZeroNet\")\n            else:\n                # Running from writeable directory put data next to .app\n                start_dir = re.sub(\"/[^/]+/Contents/Resources/core/src/Config.py\", \"\", this_file)\n        elif this_file.endswith(\"/core/src/Config.py\"):\n            # Running as exe or source is at Application Support directory, put var files to outside of core dir\n            start_dir = this_file.replace(\"/core/src/Config.py\", \"\")\n        elif this_file.endswith(\"usr/share/zeronet/src/Config.py\"):\n            # Running from non-writeable location, e.g., AppImage\n            start_dir = os.path.expanduser(\"~/ZeroNet\")\n        else:\n            start_dir = \".\"\n\n        return start_dir\n\n    # Create command line arguments\n    def createArguments(self):\n        trackers = [\n            \"zero://boot3rdez4rzn36x.onion:15441\",\n            \"zero://zero.booth.moe#f36ca555bee6ba216b14d10f38c16f7769ff064e0e37d887603548cc2e64191d:443\",  # US/NY\n            \"udp://tracker.coppersurfer.tk:6969\",  # DE\n            \"udp://104.238.198.186:8000\",  # US/LA\n            \"udp://retracker.akado-ural.ru:80\",  # RU\n            \"http://h4.trakx.nibba.trade:80/announce\",  # US/VA\n            \"http://open.acgnxtracker.com:80/announce\",  # DE\n            \"http://tracker.bt4g.com:2095/announce\",  # Cloudflare\n            \"zero://2602:ffc5::c5b2:5360:26312\"  # US/ATL\n        ]\n        # Platform specific\n        if sys.platform.startswith(\"win\"):\n            coffeescript = \"type %s | tools\\\\coffee\\\\coffee.cmd\"\n        else:\n            coffeescript = None\n\n        try:\n            language, enc = locale.getdefaultlocale()\n            language = language.lower().replace(\"_\", \"-\")\n            if language not in [\"pt-br\", \"zh-tw\"]:\n                language = language.split(\"-\")[0]\n        except Exception:\n            language = \"en\"\n\n        use_openssl = True\n\n        if repr(1483108852.565) != \"1483108852.565\":  # Fix for weird Android issue\n            fix_float_decimals = True\n        else:\n            fix_float_decimals = False\n\n        config_file = self.start_dir + \"/zeronet.conf\"\n        data_dir = self.start_dir + \"/data\"\n        log_dir = self.start_dir + \"/log\"\n\n        ip_local = [\"127.0.0.1\", \"::1\"]\n\n        # Main\n        action = self.subparsers.add_parser(\"main\", help='Start UiServer and FileServer (default)')\n\n        # SiteCreate\n        action = self.subparsers.add_parser(\"siteCreate\", help='Create a new site')\n        action.register('type', 'bool', self.strToBool)\n        action.add_argument('--use_master_seed', help=\"Allow created site's private key to be recovered using the master seed in users.json (default: True)\", type=\"bool\", choices=[True, False], default=True)\n\n        # SiteNeedFile\n        action = self.subparsers.add_parser(\"siteNeedFile\", help='Get a file from site')\n        action.add_argument('address', help='Site address')\n        action.add_argument('inner_path', help='File inner path')\n\n        # SiteDownload\n        action = self.subparsers.add_parser(\"siteDownload\", help='Download a new site')\n        action.add_argument('address', help='Site address')\n\n        # SiteSign\n        action = self.subparsers.add_parser(\"siteSign\", help='Update and sign content.json: address [privatekey]')\n        action.add_argument('address', help='Site to sign')\n        action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')\n        action.add_argument('--inner_path', help='File you want to sign (default: content.json)',\n                            default=\"content.json\", metavar=\"inner_path\")\n        action.add_argument('--remove_missing_optional', help='Remove optional files that is not present in the directory', action='store_true')\n        action.add_argument('--publish', help='Publish site after the signing', action='store_true')\n\n        # SitePublish\n        action = self.subparsers.add_parser(\"sitePublish\", help='Publish site to other peers: address')\n        action.add_argument('address', help='Site to publish')\n        action.add_argument('peer_ip', help='Peer ip to publish (default: random peers ip from tracker)',\n                            default=None, nargs='?')\n        action.add_argument('peer_port', help='Peer port to publish (default: random peer port from tracker)',\n                            default=15441, nargs='?')\n        action.add_argument('--inner_path', help='Content.json you want to publish (default: content.json)',\n                            default=\"content.json\", metavar=\"inner_path\")\n\n        # SiteVerify\n        action = self.subparsers.add_parser(\"siteVerify\", help='Verify site files using sha512: address')\n        action.add_argument('address', help='Site to verify')\n\n        # SiteCmd\n        action = self.subparsers.add_parser(\"siteCmd\", help='Execute a ZeroFrame API command on a site')\n        action.add_argument('address', help='Site address')\n        action.add_argument('cmd', help='API command name')\n        action.add_argument('parameters', help='Parameters of the command', nargs='?')\n\n        # dbRebuild\n        action = self.subparsers.add_parser(\"dbRebuild\", help='Rebuild site database cache')\n        action.add_argument('address', help='Site to rebuild')\n\n        # dbQuery\n        action = self.subparsers.add_parser(\"dbQuery\", help='Query site sql cache')\n        action.add_argument('address', help='Site to query')\n        action.add_argument('query', help='Sql query')\n\n        # PeerPing\n        action = self.subparsers.add_parser(\"peerPing\", help='Send Ping command to peer')\n        action.add_argument('peer_ip', help='Peer ip')\n        action.add_argument('peer_port', help='Peer port', nargs='?')\n\n        # PeerGetFile\n        action = self.subparsers.add_parser(\"peerGetFile\", help='Request and print a file content from peer')\n        action.add_argument('peer_ip', help='Peer ip')\n        action.add_argument('peer_port', help='Peer port')\n        action.add_argument('site', help='Site address')\n        action.add_argument('filename', help='File name to request')\n        action.add_argument('--benchmark', help='Request file 10x then displays the total time', action='store_true')\n\n        # PeerCmd\n        action = self.subparsers.add_parser(\"peerCmd\", help='Request and print a file content from peer')\n        action.add_argument('peer_ip', help='Peer ip')\n        action.add_argument('peer_port', help='Peer port')\n        action.add_argument('cmd', help='Command to execute')\n        action.add_argument('parameters', help='Parameters to command', nargs='?')\n\n        # CryptSign\n        action = self.subparsers.add_parser(\"cryptSign\", help='Sign message using Bitcoin private key')\n        action.add_argument('message', help='Message to sign')\n        action.add_argument('privatekey', help='Private key')\n\n        # Crypt Verify\n        action = self.subparsers.add_parser(\"cryptVerify\", help='Verify message using Bitcoin public address')\n        action.add_argument('message', help='Message to verify')\n        action.add_argument('sign', help='Signiture for message')\n        action.add_argument('address', help='Signer\\'s address')\n\n        # Crypt GetPrivatekey\n        action = self.subparsers.add_parser(\"cryptGetPrivatekey\", help='Generate a privatekey from master seed')\n        action.add_argument('master_seed', help='Source master seed')\n        action.add_argument('site_address_index', help='Site address index', type=int)\n\n        action = self.subparsers.add_parser(\"getConfig\", help='Return json-encoded info')\n        action = self.subparsers.add_parser(\"testConnection\", help='Testing')\n        action = self.subparsers.add_parser(\"testAnnounce\", help='Testing')\n\n        self.test_parser = self.subparsers.add_parser(\"test\", help='Run a test')\n        self.test_parser.add_argument('test_name', help='Test name', nargs=\"?\")\n        # self.test_parser.add_argument('--benchmark', help='Run the tests multiple times to measure the performance', action='store_true')\n\n        # Config parameters\n        self.parser.add_argument('--verbose', help='More detailed logging', action='store_true')\n        self.parser.add_argument('--debug', help='Debug mode', action='store_true')\n        self.parser.add_argument('--silent', help='Only log errors to terminal output', action='store_true')\n        self.parser.add_argument('--debug_socket', help='Debug socket connections', action='store_true')\n        self.parser.add_argument('--merge_media', help='Merge all.js and all.css', action='store_true')\n\n        self.parser.add_argument('--batch', help=\"Batch mode (No interactive input for commands)\", action='store_true')\n\n        self.parser.add_argument('--start_dir', help='Path of working dir for variable content (data, log, .conf)', default=self.start_dir, metavar=\"path\")\n        self.parser.add_argument('--config_file', help='Path of config file', default=config_file, metavar=\"path\")\n        self.parser.add_argument('--data_dir', help='Path of data directory', default=data_dir, metavar=\"path\")\n\n        self.parser.add_argument('--console_log_level', help='Level of logging to console', default=\"default\", choices=[\"default\", \"DEBUG\", \"INFO\", \"ERROR\", \"off\"])\n\n        self.parser.add_argument('--log_dir', help='Path of logging directory', default=log_dir, metavar=\"path\")\n        self.parser.add_argument('--log_level', help='Level of logging to file', default=\"DEBUG\", choices=[\"DEBUG\", \"INFO\", \"ERROR\", \"off\"])\n        self.parser.add_argument('--log_rotate', help='Log rotate interval', default=\"daily\", choices=[\"hourly\", \"daily\", \"weekly\", \"off\"])\n        self.parser.add_argument('--log_rotate_backup_count', help='Log rotate backup count', default=5, type=int)\n\n        self.parser.add_argument('--language', help='Web interface language', default=language, metavar='language')\n        self.parser.add_argument('--ui_ip', help='Web interface bind address', default=\"127.0.0.1\", metavar='ip')\n        self.parser.add_argument('--ui_port', help='Web interface bind port', default=43110, type=int, metavar='port')\n        self.parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip', nargs='*')\n        self.parser.add_argument('--ui_host', help='Allow access using this hosts', metavar='host', nargs='*')\n        self.parser.add_argument('--ui_trans_proxy', help='Allow access using a transparent proxy', action='store_true')\n\n        self.parser.add_argument('--open_browser', help='Open homepage in web browser automatically',\n                                 nargs='?', const=\"default_browser\", metavar='browser_name')\n        self.parser.add_argument('--homepage', help='Web interface Homepage', default='1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D',\n                                 metavar='address')\n        self.parser.add_argument('--updatesite', help='Source code update site', default='1uPDaT3uSyWAPdCv1WkMb5hBQjWSNNACf',\n                                 metavar='address')\n        self.parser.add_argument('--dist_type', help='Type of installed distribution', default='source')\n\n        self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='limit')\n        self.parser.add_argument('--file_size_limit', help='Maximum per file size limit in MB', default=10, type=int, metavar='limit')\n        self.parser.add_argument('--connected_limit', help='Max connected peer per site', default=8, type=int, metavar='connected_limit')\n        self.parser.add_argument('--global_connected_limit', help='Max connections', default=512, type=int, metavar='global_connected_limit')\n        self.parser.add_argument('--workers', help='Download workers per site', default=5, type=int, metavar='workers')\n\n        self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default=\"*\", metavar='ip')\n        self.parser.add_argument('--fileserver_port', help='FileServer bind port (0: randomize)', default=0, type=int, metavar='port')\n        self.parser.add_argument('--fileserver_port_range', help='FileServer randomization range', default=\"10000-40000\", metavar='port')\n        self.parser.add_argument('--fileserver_ip_type', help='FileServer ip type', default=\"dual\", choices=[\"ipv4\", \"ipv6\", \"dual\"])\n        self.parser.add_argument('--ip_local', help='My local ips', default=ip_local, type=int, metavar='ip', nargs='*')\n        self.parser.add_argument('--ip_external', help='Set reported external ip (tested on start if None)', metavar='ip', nargs='*')\n        self.parser.add_argument('--offline', help='Disable network communication', action='store_true')\n\n        self.parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true')\n        self.parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port')\n        self.parser.add_argument('--bind', help='Bind outgoing sockets to this address', metavar='ip')\n        self.parser.add_argument('--trackers', help='Bootstraping torrent trackers', default=trackers, metavar='protocol://address', nargs='*')\n        self.parser.add_argument('--trackers_file', help='Load torrent trackers dynamically from a file', metavar='path', nargs='*')\n        self.parser.add_argument('--trackers_proxy', help='Force use proxy to connect to trackers (disable, tor, ip:port)', default=\"disable\")\n        self.parser.add_argument('--use_libsecp256k1', help='Use Libsecp256k1 liblary for speedup', type='bool', choices=[True, False], default=True)\n        self.parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup', type='bool', choices=[True, False], default=True)\n        self.parser.add_argument('--openssl_lib_file', help='Path for OpenSSL library file (default: detect)', default=argparse.SUPPRESS, metavar=\"path\")\n        self.parser.add_argument('--openssl_bin_file', help='Path for OpenSSL binary file (default: detect)', default=argparse.SUPPRESS, metavar=\"path\")\n        self.parser.add_argument('--disable_db', help='Disable database updating', action='store_true')\n        self.parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true')\n        self.parser.add_argument('--force_encryption', help=\"Enforce encryption to all peer connections\", action='store_true')\n        self.parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory',\n                                 type='bool', choices=[True, False], default=True)\n        self.parser.add_argument('--keep_ssl_cert', help='Disable new SSL cert generation on startup', action='store_true')\n        self.parser.add_argument('--max_files_opened', help='Change maximum opened files allowed by OS to this value on startup',\n                                 default=2048, type=int, metavar='limit')\n        self.parser.add_argument('--stack_size', help='Change thread stack size', default=None, type=int, metavar='thread_stack_size')\n        self.parser.add_argument('--use_tempfiles', help='Use temporary files when downloading (experimental)',\n                                 type='bool', choices=[True, False], default=False)\n        self.parser.add_argument('--stream_downloads', help='Stream download directly to files (experimental)',\n                                 type='bool', choices=[True, False], default=False)\n        self.parser.add_argument(\"--msgpack_purepython\", help='Use less memory, but a bit more CPU power',\n                                 type='bool', choices=[True, False], default=False)\n        self.parser.add_argument(\"--fix_float_decimals\", help='Fix content.json modification date float precision on verification',\n                                 type='bool', choices=[True, False], default=fix_float_decimals)\n        self.parser.add_argument(\"--db_mode\", choices=[\"speed\", \"security\"], default=\"speed\")\n\n        self.parser.add_argument('--threads_fs_read', help='Number of threads for file read operations', default=1, type=int)\n        self.parser.add_argument('--threads_fs_write', help='Number of threads for file write operations', default=1, type=int)\n        self.parser.add_argument('--threads_crypt', help='Number of threads for cryptographic operations', default=2, type=int)\n        self.parser.add_argument('--threads_db', help='Number of threads for database operations', default=1, type=int)\n\n        self.parser.add_argument(\"--download_optional\", choices=[\"manual\", \"auto\"], default=\"manual\")\n\n        self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,\n                                 metavar='executable_path')\n\n        self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=[\"disable\", \"enable\", \"always\"], default='enable')\n        self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051')\n        self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050')\n        self.parser.add_argument('--tor_password', help='Tor controller password', metavar='password')\n        self.parser.add_argument('--tor_use_bridges', help='Use obfuscated bridge relays to avoid Tor block', action='store_true')\n        self.parser.add_argument('--tor_hs_limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10)\n        self.parser.add_argument('--tor_hs_port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441)\n\n        self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))\n        self.parser.add_argument('--end', help='Stop multi value argument parsing', action='store_true')\n\n        return self.parser\n\n    def loadTrackersFile(self):\n        if not self.trackers_file:\n            return None\n\n        self.trackers = self.arguments.trackers[:]\n\n        for trackers_file in self.trackers_file:\n            try:\n                if trackers_file.startswith(\"/\"):  # Absolute\n                    trackers_file_path = trackers_file\n                elif trackers_file.startswith(\"{data_dir}\"):  # Relative to data_dir\n                    trackers_file_path = trackers_file.replace(\"{data_dir}\", self.data_dir)\n                else:  # Relative to zeronet.py\n                    trackers_file_path = self.start_dir + \"/\" + trackers_file\n\n                for line in open(trackers_file_path):\n                    tracker = line.strip()\n                    if \"://\" in tracker and tracker not in self.trackers:\n                        self.trackers.append(tracker)\n            except Exception as err:\n                print(\"Error loading trackers file: %s\" % err)\n\n    # Find arguments specified for current action\n    def getActionArguments(self):\n        back = {}\n        arguments = self.parser._subparsers._group_actions[0].choices[self.action]._actions[1:]  # First is --version\n        for argument in arguments:\n            back[argument.dest] = getattr(self, argument.dest)\n        return back\n\n    # Try to find action from argv\n    def getAction(self, argv):\n        actions = [list(action.choices.keys()) for action in self.parser._actions if action.dest == \"action\"][0]  # Valid actions\n        found_action = False\n        for action in actions:  # See if any in argv\n            if action in argv:\n                found_action = action\n                break\n        return found_action\n\n    # Move plugin parameters to end of argument list\n    def moveUnknownToEnd(self, argv, default_action):\n        valid_actions = sum([action.option_strings for action in self.parser._actions], [])\n        valid_parameters = []\n        plugin_parameters = []\n        plugin = False\n        for arg in argv:\n            if arg.startswith(\"--\"):\n                if arg not in valid_actions:\n                    plugin = True\n                else:\n                    plugin = False\n            elif arg == default_action:\n                plugin = False\n\n            if plugin:\n                plugin_parameters.append(arg)\n            else:\n                valid_parameters.append(arg)\n        return valid_parameters + plugin_parameters\n\n    def getParser(self, argv):\n        action = self.getAction(argv)\n        if not action:\n            return self.parser\n        else:\n            return self.subparsers.choices[action]\n\n    # Parse arguments from config file and command line\n    def parse(self, silent=False, parse_config=True):\n        argv = self.argv[:]  # Copy command line arguments\n        current_parser = self.getParser(argv)\n        if silent:  # Don't display messages or quit on unknown parameter\n            original_print_message = self.parser._print_message\n            original_exit = self.parser.exit\n\n            def silencer(parser, function_name):\n                parser.exited = True\n                return None\n            current_parser.exited = False\n            current_parser._print_message = lambda *args, **kwargs: silencer(current_parser, \"_print_message\")\n            current_parser.exit = lambda *args, **kwargs: silencer(current_parser, \"exit\")\n\n        self.parseCommandline(argv, silent)  # Parse argv\n        self.setAttributes()\n        if parse_config:\n            argv = self.parseConfig(argv)  # Add arguments from config file\n\n        self.parseCommandline(argv, silent)  # Parse argv\n        self.setAttributes()\n\n        if not silent:\n            if self.fileserver_ip != \"*\" and self.fileserver_ip not in self.ip_local:\n                self.ip_local.append(self.fileserver_ip)\n\n        if silent:  # Restore original functions\n            if current_parser.exited and self.action == \"main\":  # Argument parsing halted, don't start ZeroNet with main action\n                self.action = None\n            current_parser._print_message = original_print_message\n            current_parser.exit = original_exit\n\n        self.loadTrackersFile()\n\n    # Parse command line arguments\n    def parseCommandline(self, argv, silent=False):\n        # Find out if action is specificed on start\n        action = self.getAction(argv)\n        if not action:\n            argv.append(\"--end\")\n            argv.append(\"main\")\n            action = \"main\"\n        argv = self.moveUnknownToEnd(argv, action)\n        if silent:\n            res = self.parser.parse_known_args(argv[1:])\n            if res:\n                self.arguments = res[0]\n            else:\n                self.arguments = {}\n        else:\n            self.arguments = self.parser.parse_args(argv[1:])\n\n    # Parse config file\n    def parseConfig(self, argv):\n        # Find config file path from parameters\n        if \"--config_file\" in argv:\n            self.config_file = argv[argv.index(\"--config_file\") + 1]\n        # Load config file\n        if os.path.isfile(self.config_file):\n            config = configparser.RawConfigParser(allow_no_value=True, strict=False)\n            config.read(self.config_file)\n            for section in config.sections():\n                for key, val in config.items(section):\n                    if val == \"True\":\n                        val = None\n                    if section != \"global\":  # If not global prefix key with section\n                        key = section + \"_\" + key\n\n                    if key == \"open_browser\":  # Prefer config file value over cli argument\n                        while \"--%s\" % key in argv:\n                            pos = argv.index(\"--open_browser\")\n                            del argv[pos:pos + 2]\n\n                    argv_extend = [\"--%s\" % key]\n                    if val:\n                        for line in val.strip().split(\"\\n\"):  # Allow multi-line values\n                            argv_extend.append(line)\n                        if \"\\n\" in val:\n                            argv_extend.append(\"--end\")\n\n                    argv = argv[:1] + argv_extend + argv[1:]\n        return argv\n\n    # Return command line value of given argument\n    def getCmdlineValue(self, key):\n        if key not in self.argv:\n            return None\n        argv_index = self.argv.index(key)\n        if argv_index == len(self.argv) - 1:  # last arg, test not specified\n            return None\n\n        return self.argv[argv_index + 1]\n\n    # Expose arguments as class attributes\n    def setAttributes(self):\n        # Set attributes from arguments\n        if self.arguments:\n            args = vars(self.arguments)\n            for key, val in args.items():\n                if type(val) is list:\n                    val = val[:]\n                if key in (\"data_dir\", \"log_dir\", \"start_dir\", \"openssl_bin_file\", \"openssl_lib_file\"):\n                    if val:\n                        val = val.replace(\"\\\\\", \"/\")\n                setattr(self, key, val)\n\n    def loadPlugins(self):\n        from Plugin import PluginManager\n\n        @PluginManager.acceptPlugins\n        class ConfigPlugin(object):\n            def __init__(self, config):\n                self.argv = config.argv\n                self.parser = config.parser\n                self.subparsers = config.subparsers\n                self.test_parser = config.test_parser\n                self.getCmdlineValue = config.getCmdlineValue\n                self.createArguments()\n\n            def createArguments(self):\n                pass\n\n        ConfigPlugin(self)\n\n    def saveValue(self, key, value):\n        if not os.path.isfile(self.config_file):\n            content = \"\"\n        else:\n            content = open(self.config_file).read()\n        lines = content.splitlines()\n\n        global_line_i = None\n        key_line_i = None\n        i = 0\n        for line in lines:\n            if line.strip() == \"[global]\":\n                global_line_i = i\n            if line.startswith(key + \" =\") or line == key:\n                key_line_i = i\n            i += 1\n\n        if key_line_i and len(lines) > key_line_i + 1:\n            while True:  # Delete previous multiline values\n                is_value_line = lines[key_line_i + 1].startswith(\" \") or lines[key_line_i + 1].startswith(\"\\t\")\n                if not is_value_line:\n                    break\n                del lines[key_line_i + 1]\n\n        if value is None:  # Delete line\n            if key_line_i:\n                del lines[key_line_i]\n\n        else:  # Add / update\n            if type(value) is list:\n                value_lines = [\"\"] + [str(line).replace(\"\\n\", \"\").replace(\"\\r\", \"\") for line in value]\n            else:\n                value_lines = [str(value).replace(\"\\n\", \"\").replace(\"\\r\", \"\")]\n            new_line = \"%s = %s\" % (key, \"\\n \".join(value_lines))\n            if key_line_i:  # Already in the config, change the line\n                lines[key_line_i] = new_line\n            elif global_line_i is None:  # No global section yet, append to end of file\n                lines.append(\"[global]\")\n                lines.append(new_line)\n            else:  # Has global section, append the line after it\n                lines.insert(global_line_i + 1, new_line)\n\n        open(self.config_file, \"w\").write(\"\\n\".join(lines))\n\n    def getServerInfo(self):\n        from Plugin import PluginManager\n        import main\n\n        info = {\n            \"platform\": sys.platform,\n            \"fileserver_ip\": self.fileserver_ip,\n            \"fileserver_port\": self.fileserver_port,\n            \"ui_ip\": self.ui_ip,\n            \"ui_port\": self.ui_port,\n            \"version\": self.version,\n            \"rev\": self.rev,\n            \"language\": self.language,\n            \"debug\": self.debug,\n            \"plugins\": PluginManager.plugin_manager.plugin_names,\n\n            \"log_dir\": os.path.abspath(self.log_dir),\n            \"data_dir\": os.path.abspath(self.data_dir),\n            \"src_dir\": os.path.dirname(os.path.abspath(__file__))\n        }\n\n        try:\n            info[\"ip_external\"] = main.file_server.port_opened\n            info[\"tor_enabled\"] = main.file_server.tor_manager.enabled\n            info[\"tor_status\"] = main.file_server.tor_manager.status\n        except Exception:\n            pass\n\n        return info\n\n    def initConsoleLogger(self):\n        if self.action == \"main\":\n            format = '[%(asctime)s] %(name)s %(message)s'\n        else:\n            format = '%(name)s %(message)s'\n\n        if self.console_log_level == \"default\":\n            if self.silent:\n                level = logging.ERROR\n            elif self.debug:\n                level = logging.DEBUG\n            else:\n                level = logging.INFO\n        else:\n            level = logging.getLevelName(self.console_log_level)\n\n        console_logger = logging.StreamHandler()\n        console_logger.setFormatter(logging.Formatter(format, \"%H:%M:%S\"))\n        console_logger.setLevel(level)\n        logging.getLogger('').addHandler(console_logger)\n\n    def initFileLogger(self):\n        if self.action == \"main\":\n            log_file_path = \"%s/debug.log\" % self.log_dir\n        else:\n            log_file_path = \"%s/cmd.log\" % self.log_dir\n\n        if self.log_rotate == \"off\":\n            file_logger = logging.FileHandler(log_file_path, \"w\", \"utf-8\")\n        else:\n            when_names = {\"weekly\": \"w\", \"daily\": \"d\", \"hourly\": \"h\"}\n            file_logger = logging.handlers.TimedRotatingFileHandler(\n                log_file_path, when=when_names[self.log_rotate], interval=1, backupCount=self.log_rotate_backup_count,\n                encoding=\"utf8\"\n            )\n\n            if os.path.isfile(log_file_path):\n                file_logger.doRollover()  # Always start with empty log file\n        file_logger.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)-8s %(name)s %(message)s'))\n        file_logger.setLevel(logging.getLevelName(self.log_level))\n        logging.getLogger('').setLevel(logging.getLevelName(self.log_level))\n        logging.getLogger('').addHandler(file_logger)\n\n    def initLogging(self, console_logging=None, file_logging=None):\n        if console_logging == None:\n            console_logging = self.console_log_level != \"off\"\n\n        if file_logging == None:\n            file_logging = self.log_level != \"off\"\n\n        # Create necessary files and dirs\n        if not os.path.isdir(self.log_dir):\n            os.mkdir(self.log_dir)\n            try:\n                os.chmod(self.log_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)\n            except Exception as err:\n                print(\"Can't change permission of %s: %s\" % (self.log_dir, err))\n\n        # Make warning hidden from console\n        logging.WARNING = 15  # Don't display warnings if not in debug mode\n        logging.addLevelName(15, \"WARNING\")\n\n        logging.getLogger('').name = \"-\"  # Remove root prefix\n\n        self.error_logger = ErrorLogHandler()\n        self.error_logger.setLevel(logging.getLevelName(\"ERROR\"))\n        logging.getLogger('').addHandler(self.error_logger)\n\n        if console_logging:\n            self.initConsoleLogger()\n        if file_logging:\n            self.initFileLogger()\n\n\nclass ErrorLogHandler(logging.StreamHandler):\n    def __init__(self):\n        self.lines = []\n        return super().__init__()\n\n    def emit(self, record):\n        self.lines.append([time.time(), record.levelname, self.format(record)])\n\n    def onNewRecord(self, record):\n        pass\n\n\nconfig = Config(sys.argv)\n"
  },
  {
    "path": "src/Connection/Connection.py",
    "content": "import socket\nimport time\n\nimport gevent\ntry:\n    from gevent.coros import RLock\nexcept:\n    from gevent.lock import RLock\n\nfrom Config import config\nfrom Debug import Debug\nfrom util import Msgpack\nfrom Crypt import CryptConnection\nfrom util import helper\n\n\nclass Connection(object):\n    __slots__ = (\n        \"sock\", \"sock_wrapped\", \"ip\", \"port\", \"cert_pin\", \"target_onion\", \"id\", \"protocol\", \"type\", \"server\", \"unpacker\", \"unpacker_bytes\", \"req_id\", \"ip_type\",\n        \"handshake\", \"crypt\", \"connected\", \"event_connected\", \"closed\", \"start_time\", \"handshake_time\", \"last_recv_time\", \"is_private_ip\", \"is_tracker_connection\",\n        \"last_message_time\", \"last_send_time\", \"last_sent_time\", \"incomplete_buff_recv\", \"bytes_recv\", \"bytes_sent\", \"cpu_time\", \"send_lock\",\n        \"last_ping_delay\", \"last_req_time\", \"last_cmd_sent\", \"last_cmd_recv\", \"bad_actions\", \"sites\", \"name\", \"waiting_requests\", \"waiting_streams\"\n    )\n\n    def __init__(self, server, ip, port, sock=None, target_onion=None, is_tracker_connection=False):\n        self.sock = sock\n        self.cert_pin = None\n        if \"#\" in ip:\n            ip, self.cert_pin = ip.split(\"#\")\n        self.target_onion = target_onion  # Requested onion adress\n        self.id = server.last_connection_id\n        server.last_connection_id += 1\n        self.protocol = \"?\"\n        self.type = \"?\"\n        self.ip_type = \"?\"\n        self.port = int(port)\n        self.setIp(ip)\n\n        if helper.isPrivateIp(self.ip) and self.ip not in config.ip_local:\n            self.is_private_ip = True\n        else:\n            self.is_private_ip = False\n        self.is_tracker_connection = is_tracker_connection\n\n        self.server = server\n        self.unpacker = None  # Stream incoming socket messages here\n        self.unpacker_bytes = 0  # How many bytes the unpacker received\n        self.req_id = 0  # Last request id\n        self.handshake = {}  # Handshake info got from peer\n        self.crypt = None  # Connection encryption method\n        self.sock_wrapped = False  # Socket wrapped to encryption\n\n        self.connected = False\n        self.event_connected = gevent.event.AsyncResult()  # Solves on handshake received\n        self.closed = False\n\n        # Stats\n        self.start_time = time.time()\n        self.handshake_time = 0\n        self.last_recv_time = 0\n        self.last_message_time = 0\n        self.last_send_time = 0\n        self.last_sent_time = 0\n        self.incomplete_buff_recv = 0\n        self.bytes_recv = 0\n        self.bytes_sent = 0\n        self.last_ping_delay = None\n        self.last_req_time = 0\n        self.last_cmd_sent = None\n        self.last_cmd_recv = None\n        self.bad_actions = 0\n        self.sites = 0\n        self.cpu_time = 0.0\n        self.send_lock = RLock()\n\n        self.name = None\n        self.updateName()\n\n        self.waiting_requests = {}  # Waiting sent requests\n        self.waiting_streams = {}  # Waiting response file streams\n\n    def setIp(self, ip):\n        self.ip = ip\n        self.ip_type = helper.getIpType(ip)\n        self.updateName()\n\n    def createSocket(self):\n        if helper.getIpType(self.ip) == \"ipv6\" and not hasattr(socket, \"socket_noproxy\"):\n            # Create IPv6 connection as IPv4 when using proxy\n            return socket.socket(socket.AF_INET6, socket.SOCK_STREAM)\n        else:\n            return socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n\n    def updateName(self):\n        self.name = \"Conn#%2s %-12s [%s]\" % (self.id, self.ip, self.protocol)\n\n    def __str__(self):\n        return self.name\n\n    def __repr__(self):\n        return \"<%s>\" % self.__str__()\n\n    def log(self, text):\n        self.server.log.debug(\"%s > %s\" % (self.name, text))\n\n    def getValidSites(self):\n        return [key for key, val in self.server.tor_manager.site_onions.items() if val == self.target_onion]\n\n    def badAction(self, weight=1):\n        self.bad_actions += weight\n        if self.bad_actions > 40:\n            self.close(\"Too many bad actions\")\n        elif self.bad_actions > 20:\n            time.sleep(5)\n\n    def goodAction(self):\n        self.bad_actions = 0\n\n    # Open connection to peer and wait for handshake\n    def connect(self):\n        self.type = \"out\"\n        if self.ip_type == \"onion\":\n            if not self.server.tor_manager or not self.server.tor_manager.enabled:\n                raise Exception(\"Can't connect to onion addresses, no Tor controller present\")\n            self.sock = self.server.tor_manager.createSocket(self.ip, self.port)\n        elif config.tor == \"always\" and helper.isPrivateIp(self.ip) and self.ip not in config.ip_local:\n            raise Exception(\"Can't connect to local IPs in Tor: always mode\")\n        elif config.trackers_proxy != \"disable\" and config.tor != \"always\" and self.is_tracker_connection:\n            if config.trackers_proxy == \"tor\":\n                self.sock = self.server.tor_manager.createSocket(self.ip, self.port)\n            else:\n                import socks\n                self.sock = socks.socksocket()\n                proxy_ip, proxy_port = config.trackers_proxy.split(\":\")\n                self.sock.set_proxy(socks.PROXY_TYPE_SOCKS5, proxy_ip, int(proxy_port))\n        else:\n            self.sock = self.createSocket()\n\n        if \"TCP_NODELAY\" in dir(socket):\n            self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)\n\n        timeout_before = self.sock.gettimeout()\n        self.sock.settimeout(30)\n        if self.ip_type == \"ipv6\" and not hasattr(self.sock, \"proxy\"):\n            sock_address = (self.ip, self.port, 1, 1)\n        else:\n            sock_address = (self.ip, self.port)\n\n        self.sock.connect(sock_address)\n\n        # Implicit SSL\n        should_encrypt = not self.ip_type == \"onion\" and self.ip not in self.server.broken_ssl_ips and self.ip not in config.ip_local\n        if self.cert_pin:\n            self.sock = CryptConnection.manager.wrapSocket(self.sock, \"tls-rsa\", cert_pin=self.cert_pin)\n            self.sock.do_handshake()\n            self.crypt = \"tls-rsa\"\n            self.sock_wrapped = True\n        elif should_encrypt and \"tls-rsa\" in CryptConnection.manager.crypt_supported:\n            try:\n                self.sock = CryptConnection.manager.wrapSocket(self.sock, \"tls-rsa\")\n                self.sock.do_handshake()\n                self.crypt = \"tls-rsa\"\n                self.sock_wrapped = True\n            except Exception as err:\n                if not config.force_encryption:\n                    self.log(\"Crypt connection error, adding %s:%s as broken ssl. %s\" % (self.ip, self.port, Debug.formatException(err)))\n                    self.server.broken_ssl_ips[self.ip] = True\n                self.sock.close()\n                self.crypt = None\n                self.sock = self.createSocket()\n                self.sock.settimeout(30)\n                self.sock.connect(sock_address)\n\n        # Detect protocol\n        self.send({\"cmd\": \"handshake\", \"req_id\": 0, \"params\": self.getHandshakeInfo()})\n        event_connected = self.event_connected\n        gevent.spawn(self.messageLoop)\n        connect_res = event_connected.get()  # Wait for handshake\n        self.sock.settimeout(timeout_before)\n        return connect_res\n\n    # Handle incoming connection\n    def handleIncomingConnection(self, sock):\n        self.log(\"Incoming connection...\")\n\n        if \"TCP_NODELAY\" in dir(socket):\n            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)\n\n        self.type = \"in\"\n        if self.ip not in config.ip_local:   # Clearnet: Check implicit SSL\n            try:\n                first_byte = sock.recv(1, gevent.socket.MSG_PEEK)\n                if first_byte == b\"\\x16\":\n                    self.log(\"Crypt in connection using implicit SSL\")\n                    self.sock = CryptConnection.manager.wrapSocket(self.sock, \"tls-rsa\", True)\n                    self.sock_wrapped = True\n                    self.crypt = \"tls-rsa\"\n            except Exception as err:\n                self.log(\"Socket peek error: %s\" % Debug.formatException(err))\n        self.messageLoop()\n\n    def getMsgpackUnpacker(self):\n        if self.handshake and self.handshake.get(\"use_bin_type\"):\n            return Msgpack.getUnpacker(fallback=True, decode=False)\n        else:  # Backward compatibility for <0.7.0\n            return Msgpack.getUnpacker(fallback=True, decode=True)\n\n    # Message loop for connection\n    def messageLoop(self):\n        if not self.sock:\n            self.log(\"Socket error: No socket found\")\n            return False\n        self.protocol = \"v2\"\n        self.updateName()\n        self.connected = True\n        buff_len = 0\n        req_len = 0\n        self.unpacker_bytes = 0\n\n        try:\n            while not self.closed:\n                buff = self.sock.recv(64 * 1024)\n                if not buff:\n                    break  # Connection closed\n                buff_len = len(buff)\n\n                # Statistics\n                self.last_recv_time = time.time()\n                self.incomplete_buff_recv += 1\n                self.bytes_recv += buff_len\n                self.server.bytes_recv += buff_len\n                req_len += buff_len\n\n                if not self.unpacker:\n                    self.unpacker = self.getMsgpackUnpacker()\n                    self.unpacker_bytes = 0\n\n                self.unpacker.feed(buff)\n                self.unpacker_bytes += buff_len\n\n                while True:\n                    try:\n                        message = next(self.unpacker)\n                    except StopIteration:\n                        break\n                    if not type(message) is dict:\n                        if config.debug_socket:\n                            self.log(\"Invalid message type: %s, content: %r, buffer: %r\" % (type(message), message, buff[0:16]))\n                        raise Exception(\"Invalid message type: %s\" % type(message))\n\n                    # Stats\n                    self.incomplete_buff_recv = 0\n                    stat_key = message.get(\"cmd\", \"unknown\")\n                    if stat_key == \"response\" and \"to\" in message:\n                        cmd_sent = self.waiting_requests.get(message[\"to\"], {\"cmd\": \"unknown\"})[\"cmd\"]\n                        stat_key = \"response: %s\" % cmd_sent\n                    if stat_key == \"update\":\n                        stat_key = \"update: %s\" % message[\"params\"][\"site\"]\n                    self.server.stat_recv[stat_key][\"bytes\"] += req_len\n                    self.server.stat_recv[stat_key][\"num\"] += 1\n                    if \"stream_bytes\" in message:\n                        self.server.stat_recv[stat_key][\"bytes\"] += message[\"stream_bytes\"]\n                    req_len = 0\n\n                    # Handle message\n                    if \"stream_bytes\" in message:\n                        buff_left = self.handleStream(message, buff)\n                        self.unpacker = self.getMsgpackUnpacker()\n                        self.unpacker.feed(buff_left)\n                        self.unpacker_bytes = len(buff_left)\n                        if config.debug_socket:\n                            self.log(\"Start new unpacker with buff_left: %r\" % buff_left)\n                    else:\n                        self.handleMessage(message)\n\n                message = None\n        except Exception as err:\n            if not self.closed:\n                self.log(\"Socket error: %s\" % Debug.formatException(err))\n                self.server.stat_recv[\"error: %s\" % err][\"bytes\"] += req_len\n                self.server.stat_recv[\"error: %s\" % err][\"num\"] += 1\n        self.close(\"MessageLoop ended (closed: %s)\" % self.closed)  # MessageLoop ended, close connection\n\n    def getUnpackerUnprocessedBytesNum(self):\n        if \"tell\" in dir(self.unpacker):\n            bytes_num = self.unpacker_bytes - self.unpacker.tell()\n        else:\n            bytes_num = self.unpacker._fb_buf_n - self.unpacker._fb_buf_o\n        return bytes_num\n\n    # Stream socket directly to a file\n    def handleStream(self, message, buff):\n        stream_bytes_left = message[\"stream_bytes\"]\n        file = self.waiting_streams[message[\"to\"]]\n\n        unprocessed_bytes_num = self.getUnpackerUnprocessedBytesNum()\n\n        if unprocessed_bytes_num:  # Found stream bytes in unpacker\n            unpacker_stream_bytes = min(unprocessed_bytes_num, stream_bytes_left)\n            buff_stream_start = len(buff) - unprocessed_bytes_num\n            file.write(buff[buff_stream_start:buff_stream_start + unpacker_stream_bytes])\n            stream_bytes_left -= unpacker_stream_bytes\n        else:\n            unpacker_stream_bytes = 0\n\n        if config.debug_socket:\n            self.log(\n                \"Starting stream %s: %s bytes (%s from unpacker, buff size: %s, unprocessed: %s)\" %\n                (message[\"to\"], message[\"stream_bytes\"], unpacker_stream_bytes, len(buff), unprocessed_bytes_num)\n            )\n\n        try:\n            while 1:\n                if stream_bytes_left <= 0:\n                    break\n                stream_buff = self.sock.recv(min(64 * 1024, stream_bytes_left))\n                if not stream_buff:\n                    break\n                buff_len = len(stream_buff)\n                stream_bytes_left -= buff_len\n                file.write(stream_buff)\n\n                # Statistics\n                self.last_recv_time = time.time()\n                self.incomplete_buff_recv += 1\n                self.bytes_recv += buff_len\n                self.server.bytes_recv += buff_len\n        except Exception as err:\n            self.log(\"Stream read error: %s\" % Debug.formatException(err))\n\n        if config.debug_socket:\n            self.log(\"End stream %s, file pos: %s\" % (message[\"to\"], file.tell()))\n\n        self.incomplete_buff_recv = 0\n        self.waiting_requests[message[\"to\"]][\"evt\"].set(message)  # Set the response to event\n        del self.waiting_streams[message[\"to\"]]\n        del self.waiting_requests[message[\"to\"]]\n\n        if unpacker_stream_bytes:\n            return buff[buff_stream_start + unpacker_stream_bytes:]\n        else:\n            return b\"\"\n\n    # My handshake info\n    def getHandshakeInfo(self):\n        # No TLS for onion connections\n        if self.ip_type == \"onion\":\n            crypt_supported = []\n        elif self.ip in self.server.broken_ssl_ips:\n            crypt_supported = []\n        else:\n            crypt_supported = CryptConnection.manager.crypt_supported\n        # No peer id for onion connections\n        if self.ip_type == \"onion\" or self.ip in config.ip_local:\n            peer_id = \"\"\n        else:\n            peer_id = self.server.peer_id\n        # Setup peer lock from requested onion address\n        if self.handshake and self.handshake.get(\"target_ip\", \"\").endswith(\".onion\") and self.server.tor_manager.start_onions:\n            self.target_onion = self.handshake.get(\"target_ip\").replace(\".onion\", \"\")  # My onion address\n            if not self.server.tor_manager.site_onions.values():\n                self.server.log.warning(\"Unknown target onion address: %s\" % self.target_onion)\n\n        handshake = {\n            \"version\": config.version,\n            \"protocol\": \"v2\",\n            \"use_bin_type\": True,\n            \"peer_id\": peer_id,\n            \"fileserver_port\": self.server.port,\n            \"port_opened\": self.server.port_opened.get(self.ip_type, None),\n            \"target_ip\": self.ip,\n            \"rev\": config.rev,\n            \"crypt_supported\": crypt_supported,\n            \"crypt\": self.crypt,\n            \"time\": int(time.time())\n        }\n        if self.target_onion:\n            handshake[\"onion\"] = self.target_onion\n        elif self.ip_type == \"onion\":\n            handshake[\"onion\"] = self.server.tor_manager.getOnion(\"global\")\n\n        if self.is_tracker_connection:\n            handshake[\"tracker_connection\"] = True\n\n        if config.debug_socket:\n            self.log(\"My Handshake: %s\" % handshake)\n\n        return handshake\n\n    def setHandshake(self, handshake):\n        if config.debug_socket:\n            self.log(\"Remote Handshake: %s\" % handshake)\n\n        if handshake.get(\"peer_id\") == self.server.peer_id and not handshake.get(\"tracker_connection\") and not self.is_tracker_connection:\n            self.close(\"Same peer id, can't connect to myself\")\n            self.server.peer_blacklist.append((handshake[\"target_ip\"], handshake[\"fileserver_port\"]))\n            return False\n\n        self.handshake = handshake\n        if handshake.get(\"port_opened\", None) is False and \"onion\" not in handshake and not self.is_private_ip:  # Not connectable\n            self.port = 0\n        else:\n            self.port = int(handshake[\"fileserver_port\"])  # Set peer fileserver port\n\n        if handshake.get(\"use_bin_type\") and self.unpacker:\n            unprocessed_bytes_num = self.getUnpackerUnprocessedBytesNum()\n            self.log(\"Changing unpacker to bin type (unprocessed bytes: %s)\" % unprocessed_bytes_num)\n            unprocessed_bytes = self.unpacker.read_bytes(unprocessed_bytes_num)\n            self.unpacker = self.getMsgpackUnpacker()  # Create new unpacker for different msgpack type\n            self.unpacker_bytes = 0\n            if unprocessed_bytes:\n                self.unpacker.feed(unprocessed_bytes)\n\n        # Check if we can encrypt the connection\n        if handshake.get(\"crypt_supported\") and self.ip not in self.server.broken_ssl_ips:\n            if type(handshake[\"crypt_supported\"][0]) is bytes:\n                handshake[\"crypt_supported\"] = [item.decode() for item in handshake[\"crypt_supported\"]]  # Backward compatibility\n\n            if self.ip_type == \"onion\" or self.ip in config.ip_local:\n                crypt = None\n            elif handshake.get(\"crypt\"):  # Recommended crypt by server\n                crypt = handshake[\"crypt\"]\n            else:  # Select the best supported on both sides\n                crypt = CryptConnection.manager.selectCrypt(handshake[\"crypt_supported\"])\n\n            if crypt:\n                self.crypt = crypt\n\n        if self.type == \"in\" and handshake.get(\"onion\") and not self.ip_type == \"onion\":  # Set incoming connection's onion address\n            if self.server.ips.get(self.ip) == self:\n                del self.server.ips[self.ip]\n            self.setIp(handshake[\"onion\"] + \".onion\")\n            self.log(\"Changing ip to %s\" % self.ip)\n            self.server.ips[self.ip] = self\n            self.updateName()\n\n        self.event_connected.set(True)  # Mark handshake as done\n        self.event_connected = None\n        self.handshake_time = time.time()\n\n    # Handle incoming message\n    def handleMessage(self, message):\n        cmd = message[\"cmd\"]\n\n        self.last_message_time = time.time()\n        self.last_cmd_recv = cmd\n        if cmd == \"response\":  # New style response\n            if message[\"to\"] in self.waiting_requests:\n                if self.last_send_time and len(self.waiting_requests) == 1:\n                    ping = time.time() - self.last_send_time\n                    self.last_ping_delay = ping\n                self.waiting_requests[message[\"to\"]][\"evt\"].set(message)  # Set the response to event\n                del self.waiting_requests[message[\"to\"]]\n            elif message[\"to\"] == 0:  # Other peers handshake\n                ping = time.time() - self.start_time\n                if config.debug_socket:\n                    self.log(\"Handshake response: %s, ping: %s\" % (message, ping))\n                self.last_ping_delay = ping\n                # Server switched to crypt, lets do it also if not crypted already\n                if message.get(\"crypt\") and not self.sock_wrapped:\n                    self.crypt = message[\"crypt\"]\n                    server = (self.type == \"in\")\n                    self.log(\"Crypt out connection using: %s (server side: %s, ping: %.3fs)...\" % (self.crypt, server, ping))\n                    self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server, cert_pin=self.cert_pin)\n                    self.sock.do_handshake()\n                    self.sock_wrapped = True\n\n                if not self.sock_wrapped and self.cert_pin:\n                    self.close(\"Crypt connection error: Socket not encrypted, but certificate pin present\")\n                    return\n\n                self.setHandshake(message)\n            else:\n                self.log(\"Unknown response: %s\" % message)\n        elif cmd:\n            self.server.num_recv += 1\n            if cmd == \"handshake\":\n                self.handleHandshake(message)\n            else:\n                self.server.handleRequest(self, message)\n\n    # Incoming handshake set request\n    def handleHandshake(self, message):\n        self.setHandshake(message[\"params\"])\n        data = self.getHandshakeInfo()\n        data[\"cmd\"] = \"response\"\n        data[\"to\"] = message[\"req_id\"]\n        self.send(data)  # Send response to handshake\n        # Sent crypt request to client\n        if self.crypt and not self.sock_wrapped:\n            server = (self.type == \"in\")\n            self.log(\"Crypt in connection using: %s (server side: %s)...\" % (self.crypt, server))\n            try:\n                self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server, cert_pin=self.cert_pin)\n                self.sock_wrapped = True\n            except Exception as err:\n                if not config.force_encryption:\n                    self.log(\"Crypt connection error, adding %s:%s as broken ssl. %s\" % (self.ip, self.port, Debug.formatException(err)))\n                    self.server.broken_ssl_ips[self.ip] = True\n                self.close(\"Broken ssl\")\n\n        if not self.sock_wrapped and self.cert_pin:\n            self.close(\"Crypt connection error: Socket not encrypted, but certificate pin present\")\n\n    # Send data to connection\n    def send(self, message, streaming=False):\n        self.last_send_time = time.time()\n        if config.debug_socket:\n            self.log(\"Send: %s, to: %s, streaming: %s, site: %s, inner_path: %s, req_id: %s\" % (\n                message.get(\"cmd\"), message.get(\"to\"), streaming,\n                message.get(\"params\", {}).get(\"site\"), message.get(\"params\", {}).get(\"inner_path\"),\n                message.get(\"req_id\"))\n            )\n\n        if not self.sock:\n            self.log(\"Send error: missing socket\")\n            return False\n\n        if not self.connected and message.get(\"cmd\") != \"handshake\":\n            self.log(\"Wait for handshake before send request\")\n            self.event_connected.get()\n\n        try:\n            stat_key = message.get(\"cmd\", \"unknown\")\n            if stat_key == \"response\":\n                stat_key = \"response: %s\" % self.last_cmd_recv\n            else:\n                self.server.num_sent += 1\n\n            self.server.stat_sent[stat_key][\"num\"] += 1\n            if streaming:\n                with self.send_lock:\n                    bytes_sent = Msgpack.stream(message, self.sock.sendall)\n                self.bytes_sent += bytes_sent\n                self.server.bytes_sent += bytes_sent\n                self.server.stat_sent[stat_key][\"bytes\"] += bytes_sent\n                message = None\n            else:\n                data = Msgpack.pack(message)\n                self.bytes_sent += len(data)\n                self.server.bytes_sent += len(data)\n                self.server.stat_sent[stat_key][\"bytes\"] += len(data)\n                message = None\n                with self.send_lock:\n                    self.sock.sendall(data)\n        except Exception as err:\n            self.close(\"Send error: %s (cmd: %s)\" % (err, stat_key))\n            return False\n        self.last_sent_time = time.time()\n        return True\n\n    # Stream file to connection without msgpacking\n    def sendRawfile(self, file, read_bytes):\n        buff = 64 * 1024\n        bytes_left = read_bytes\n        bytes_sent = 0\n        while True:\n            self.last_send_time = time.time()\n            data = file.read(min(bytes_left, buff))\n            bytes_sent += len(data)\n            with self.send_lock:\n                self.sock.sendall(data)\n            bytes_left -= buff\n            if bytes_left <= 0:\n                break\n        self.bytes_sent += bytes_sent\n        self.server.bytes_sent += bytes_sent\n        self.server.stat_sent[\"raw_file\"][\"num\"] += 1\n        self.server.stat_sent[\"raw_file\"][\"bytes\"] += bytes_sent\n        return True\n\n    # Create and send a request to peer\n    def request(self, cmd, params={}, stream_to=None):\n        # Last command sent more than 10 sec ago, timeout\n        if self.waiting_requests and self.protocol == \"v2\" and time.time() - max(self.last_req_time, self.last_recv_time) > 10:\n            self.close(\"Request %s timeout: %.3fs\" % (self.last_cmd_sent, time.time() - self.last_send_time))\n            return False\n\n        self.last_req_time = time.time()\n        self.last_cmd_sent = cmd\n        self.req_id += 1\n        data = {\"cmd\": cmd, \"req_id\": self.req_id, \"params\": params}\n        event = gevent.event.AsyncResult()  # Create new event for response\n        self.waiting_requests[self.req_id] = {\"evt\": event, \"cmd\": cmd}\n        if stream_to:\n            self.waiting_streams[self.req_id] = stream_to\n        self.send(data)  # Send request\n        res = event.get()  # Wait until event solves\n        return res\n\n    def ping(self):\n        s = time.time()\n        response = None\n        with gevent.Timeout(10.0, False):\n            try:\n                response = self.request(\"ping\")\n            except Exception as err:\n                self.log(\"Ping error: %s\" % Debug.formatException(err))\n        if response and \"body\" in response and response[\"body\"] == b\"Pong!\":\n            self.last_ping_delay = time.time() - s\n            return True\n        else:\n            return False\n\n    # Close connection\n    def close(self, reason=\"Unknown\"):\n        if self.closed:\n            return False  # Already closed\n        self.closed = True\n        self.connected = False\n        if self.event_connected:\n            self.event_connected.set(False)\n\n        self.log(\n            \"Closing connection: %s, waiting_requests: %s, sites: %s, buff: %s...\" %\n            (reason, len(self.waiting_requests), self.sites, self.incomplete_buff_recv)\n        )\n        for request in self.waiting_requests.values():  # Mark pending requests failed\n            request[\"evt\"].set(False)\n        self.waiting_requests = {}\n        self.waiting_streams = {}\n        self.sites = 0\n        self.server.removeConnection(self)  # Remove connection from server registry\n        try:\n            if self.sock:\n                self.sock.shutdown(gevent.socket.SHUT_WR)\n                self.sock.close()\n        except Exception as err:\n            if config.debug_socket:\n                self.log(\"Close error: %s\" % err)\n\n        # Little cleanup\n        self.sock = None\n        self.unpacker = None\n        self.event_connected = None\n"
  },
  {
    "path": "src/Connection/ConnectionServer.py",
    "content": "import logging\nimport time\nimport sys\nimport socket\nfrom collections import defaultdict\n\nimport gevent\nimport msgpack\nfrom gevent.server import StreamServer\nfrom gevent.pool import Pool\n\nimport util\nfrom util import helper\nfrom Debug import Debug\nfrom .Connection import Connection\nfrom Config import config\nfrom Crypt import CryptConnection\nfrom Crypt import CryptHash\nfrom Tor import TorManager\nfrom Site import SiteManager\n\n\nclass ConnectionServer(object):\n    def __init__(self, ip=None, port=None, request_handler=None):\n        if not ip:\n            if config.fileserver_ip_type == \"ipv6\":\n                ip = \"::1\"\n            else:\n                ip = \"127.0.0.1\"\n            port = 15441\n        self.ip = ip\n        self.port = port\n        self.last_connection_id = 1  # Connection id incrementer\n        self.log = logging.getLogger(\"ConnServer\")\n        self.port_opened = {}\n        self.peer_blacklist = SiteManager.peer_blacklist\n\n        self.tor_manager = TorManager(self.ip, self.port)\n        self.connections = []  # Connections\n        self.whitelist = config.ip_local  # No flood protection on this ips\n        self.ip_incoming = {}  # Incoming connections from ip in the last minute to avoid connection flood\n        self.broken_ssl_ips = {}  # Peerids of broken ssl connections\n        self.ips = {}  # Connection by ip\n        self.has_internet = True  # Internet outage detection\n\n        self.stream_server = None\n        self.stream_server_proxy = None\n        self.running = False\n        self.stopping = False\n        self.thread_checker = None\n\n        self.stat_recv = defaultdict(lambda: defaultdict(int))\n        self.stat_sent = defaultdict(lambda: defaultdict(int))\n        self.bytes_recv = 0\n        self.bytes_sent = 0\n        self.num_recv = 0\n        self.num_sent = 0\n\n        self.num_incoming = 0\n        self.num_outgoing = 0\n        self.had_external_incoming = False\n\n        self.timecorrection = 0.0\n        self.pool = Pool(500)  # do not accept more than 500 connections\n\n        # Bittorrent style peerid\n        self.peer_id = \"-UT3530-%s\" % CryptHash.random(12, \"base64\")\n\n        # Check msgpack version\n        if msgpack.version[0] == 0 and msgpack.version[1] < 4:\n            self.log.error(\n                \"Error: Unsupported msgpack version: %s (<0.4.0), please run `sudo apt-get install python-pip; sudo pip install msgpack --upgrade`\" %\n                str(msgpack.version)\n            )\n            sys.exit(0)\n\n        if request_handler:\n            self.handleRequest = request_handler\n\n    def start(self, check_connections=True):\n        if self.stopping:\n            return False\n        self.running = True\n        if check_connections:\n            self.thread_checker = gevent.spawn(self.checkConnections)\n        CryptConnection.manager.loadCerts()\n        if config.tor != \"disable\":\n            self.tor_manager.start()\n        if not self.port:\n            self.log.info(\"No port found, not binding\")\n            return False\n\n        self.log.debug(\"Binding to: %s:%s, (msgpack: %s), supported crypt: %s\" % (\n            self.ip, self.port, \".\".join(map(str, msgpack.version)),\n            CryptConnection.manager.crypt_supported\n        ))\n        try:\n            self.stream_server = StreamServer(\n                (self.ip, self.port), self.handleIncomingConnection, spawn=self.pool, backlog=100\n            )\n        except Exception as err:\n            self.log.info(\"StreamServer create error: %s\" % Debug.formatException(err))\n\n    def listen(self):\n        if not self.running:\n            return None\n\n        if self.stream_server_proxy:\n            gevent.spawn(self.listenProxy)\n        try:\n            self.stream_server.serve_forever()\n        except Exception as err:\n            self.log.info(\"StreamServer listen error: %s\" % err)\n            return False\n        self.log.debug(\"Stopped.\")\n\n    def stop(self):\n        self.log.debug(\"Stopping %s\" % self.stream_server)\n        self.stopping = True\n        self.running = False\n        if self.thread_checker:\n            gevent.kill(self.thread_checker)\n        if self.stream_server:\n            self.stream_server.stop()\n\n    def closeConnections(self):\n        self.log.debug(\"Closing all connection: %s\" % len(self.connections))\n        for connection in self.connections[:]:\n            connection.close(\"Close all connections\")\n\n    def handleIncomingConnection(self, sock, addr):\n        if config.offline:\n            sock.close()\n            return False\n\n        ip, port = addr[0:2]\n        ip = ip.lower()\n        if ip.startswith(\"::ffff:\"):  # IPv6 to IPv4 mapping\n            ip = ip.replace(\"::ffff:\", \"\", 1)\n        self.num_incoming += 1\n\n        if not self.had_external_incoming and not helper.isPrivateIp(ip):\n            self.had_external_incoming = True\n\n        # Connection flood protection\n        if ip in self.ip_incoming and ip not in self.whitelist:\n            self.ip_incoming[ip] += 1\n            if self.ip_incoming[ip] > 6:  # Allow 6 in 1 minute from same ip\n                self.log.debug(\"Connection flood detected from %s\" % ip)\n                time.sleep(30)\n                sock.close()\n                return False\n        else:\n            self.ip_incoming[ip] = 1\n\n        connection = Connection(self, ip, port, sock)\n        self.connections.append(connection)\n        if ip not in config.ip_local:\n            self.ips[ip] = connection\n        connection.handleIncomingConnection(sock)\n\n    def handleMessage(self, *args, **kwargs):\n        pass\n\n    def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None, is_tracker_connection=False):\n        ip_type = helper.getIpType(ip)\n        has_per_site_onion = (ip.endswith(\".onion\") or self.port_opened.get(ip_type, None) == False) and self.tor_manager.start_onions and site\n        if has_per_site_onion:  # Site-unique connection for Tor\n            if ip.endswith(\".onion\"):\n                site_onion = self.tor_manager.getOnion(site.address)\n            else:\n                site_onion = self.tor_manager.getOnion(\"global\")\n            key = ip + site_onion\n        else:\n            key = ip\n\n        # Find connection by ip\n        if key in self.ips:\n            connection = self.ips[key]\n            if not peer_id or connection.handshake.get(\"peer_id\") == peer_id:  # Filter by peer_id\n                if not connection.connected and create:\n                    succ = connection.event_connected.get()  # Wait for connection\n                    if not succ:\n                        raise Exception(\"Connection event return error\")\n                return connection\n\n            # Recover from connection pool\n            for connection in self.connections:\n                if connection.ip == ip:\n                    if peer_id and connection.handshake.get(\"peer_id\") != peer_id:  # Does not match\n                        continue\n                    if ip.endswith(\".onion\") and self.tor_manager.start_onions and ip.replace(\".onion\", \"\") != connection.target_onion:\n                        # For different site\n                        continue\n                    if not connection.connected and create:\n                        succ = connection.event_connected.get()  # Wait for connection\n                        if not succ:\n                            raise Exception(\"Connection event return error\")\n                    return connection\n\n        # No connection found\n        if create and not config.offline:  # Allow to create new connection if not found\n            if port == 0:\n                raise Exception(\"This peer is not connectable\")\n\n            if (ip, port) in self.peer_blacklist and not is_tracker_connection:\n                raise Exception(\"This peer is blacklisted\")\n\n            try:\n                if has_per_site_onion:  # Lock connection to site\n                    connection = Connection(self, ip, port, target_onion=site_onion, is_tracker_connection=is_tracker_connection)\n                else:\n                    connection = Connection(self, ip, port, is_tracker_connection=is_tracker_connection)\n                self.num_outgoing += 1\n                self.ips[key] = connection\n                self.connections.append(connection)\n                connection.log(\"Connecting... (site: %s)\" % site)\n                succ = connection.connect()\n                if not succ:\n                    connection.close(\"Connection event return error\")\n                    raise Exception(\"Connection event return error\")\n\n            except Exception as err:\n                connection.close(\"%s Connect error: %s\" % (ip, Debug.formatException(err)))\n                raise err\n\n            if len(self.connections) > config.global_connected_limit:\n                gevent.spawn(self.checkMaxConnections)\n\n            return connection\n        else:\n            return None\n\n    def removeConnection(self, connection):\n        # Delete if same as in registry\n        if self.ips.get(connection.ip) == connection:\n            del self.ips[connection.ip]\n        # Site locked connection\n        if connection.target_onion:\n            if self.ips.get(connection.ip + connection.target_onion) == connection:\n                del self.ips[connection.ip + connection.target_onion]\n        # Cert pinned connection\n        if connection.cert_pin and self.ips.get(connection.ip + \"#\" + connection.cert_pin) == connection:\n            del self.ips[connection.ip + \"#\" + connection.cert_pin]\n\n        if connection in self.connections:\n            self.connections.remove(connection)\n\n    def checkConnections(self):\n        run_i = 0\n        time.sleep(15)\n        while self.running:\n            run_i += 1\n            self.ip_incoming = {}  # Reset connected ips counter\n            last_message_time = 0\n            s = time.time()\n            for connection in self.connections[:]:  # Make a copy\n                if connection.ip.endswith(\".onion\") or config.tor == \"always\":\n                    timeout_multipler = 2\n                else:\n                    timeout_multipler = 1\n\n                idle = time.time() - max(connection.last_recv_time, connection.start_time, connection.last_message_time)\n                if connection.last_message_time > last_message_time and not connection.is_private_ip:\n                    # Message from local IPs does not means internet connection\n                    last_message_time = connection.last_message_time\n\n                if connection.unpacker and idle > 30:\n                    # Delete the unpacker if not needed\n                    del connection.unpacker\n                    connection.unpacker = None\n\n                elif connection.last_cmd_sent == \"announce\" and idle > 20:  # Bootstrapper connection close after 20 sec\n                    connection.close(\"[Cleanup] Tracker connection, idle: %.3fs\" % idle)\n\n                if idle > 60 * 60:\n                    # Wake up after 1h\n                    connection.close(\"[Cleanup] After wakeup, idle: %.3fs\" % idle)\n\n                elif idle > 20 * 60 and connection.last_send_time < time.time() - 10:\n                    # Idle more than 20 min and we have not sent request in last 10 sec\n                    if not connection.ping():\n                        connection.close(\"[Cleanup] Ping timeout\")\n\n                elif idle > 10 * timeout_multipler and connection.incomplete_buff_recv > 0:\n                    # Incomplete data with more than 10 sec idle\n                    connection.close(\"[Cleanup] Connection buff stalled\")\n\n                elif idle > 10 * timeout_multipler and connection.protocol == \"?\":  # No connection after 10 sec\n                    connection.close(\n                        \"[Cleanup] Connect timeout: %.3fs\" % idle\n                    )\n\n                elif idle > 10 * timeout_multipler and connection.waiting_requests and time.time() - connection.last_send_time > 10 * timeout_multipler:\n                    # Sent command and no response in 10 sec\n                    connection.close(\n                        \"[Cleanup] Command %s timeout: %.3fs\" % (connection.last_cmd_sent, time.time() - connection.last_send_time)\n                    )\n\n                elif idle < 60 and connection.bad_actions > 40:\n                    connection.close(\n                        \"[Cleanup] Too many bad actions: %s\" % connection.bad_actions\n                    )\n\n                elif idle > 5 * 60 and connection.sites == 0:\n                    connection.close(\n                        \"[Cleanup] No site for connection\"\n                    )\n\n                elif run_i % 90 == 0:\n                    # Reset bad action counter every 30 min\n                    connection.bad_actions = 0\n\n            # Internet outage detection\n            if time.time() - last_message_time > max(60, 60 * 10 / max(1, float(len(self.connections)) / 50)):\n                # Offline: Last message more than 60-600sec depending on connection number\n                if self.has_internet and last_message_time:\n                    self.has_internet = False\n                    self.onInternetOffline()\n            else:\n                # Online\n                if not self.has_internet:\n                    self.has_internet = True\n                    self.onInternetOnline()\n\n            self.timecorrection = self.getTimecorrection()\n\n            if time.time() - s > 0.01:\n                self.log.debug(\"Connection cleanup in %.3fs\" % (time.time() - s))\n\n            time.sleep(15)\n        self.log.debug(\"Checkconnections ended\")\n\n    @util.Noparallel(blocking=False)\n    def checkMaxConnections(self):\n        if len(self.connections) < config.global_connected_limit:\n            return 0\n\n        s = time.time()\n        num_connected_before = len(self.connections)\n        self.connections.sort(key=lambda connection: connection.sites)\n        num_closed = 0\n        for connection in self.connections:\n            idle = time.time() - max(connection.last_recv_time, connection.start_time, connection.last_message_time)\n            if idle > 60:\n                connection.close(\"Connection limit reached\")\n                num_closed += 1\n            if num_closed > config.global_connected_limit * 0.1:\n                break\n\n        self.log.debug(\"Closed %s connections of %s after reached limit %s in %.3fs\" % (\n            num_closed, num_connected_before, config.global_connected_limit, time.time() - s\n        ))\n        return num_closed\n\n    def onInternetOnline(self):\n        self.log.info(\"Internet online\")\n\n    def onInternetOffline(self):\n        self.had_external_incoming = False\n        self.log.info(\"Internet offline\")\n\n    def getTimecorrection(self):\n        corrections = sorted([\n            connection.handshake.get(\"time\") - connection.handshake_time + connection.last_ping_delay\n            for connection in self.connections\n            if connection.handshake.get(\"time\") and connection.last_ping_delay\n        ])\n        if len(corrections) < 9:\n            return 0.0\n        mid = int(len(corrections) / 2 - 1)\n        median = (corrections[mid - 1] + corrections[mid] + corrections[mid + 1]) / 3\n        return median\n"
  },
  {
    "path": "src/Connection/__init__.py",
    "content": "from .ConnectionServer import ConnectionServer\nfrom .Connection import Connection\n"
  },
  {
    "path": "src/Content/ContentDb.py",
    "content": "import os\n\nfrom Db.Db import Db, DbTableError\nfrom Config import config\nfrom Plugin import PluginManager\nfrom Debug import Debug\n\n\n@PluginManager.acceptPlugins\nclass ContentDb(Db):\n    def __init__(self, path):\n        Db.__init__(self, {\"db_name\": \"ContentDb\", \"tables\": {}}, path)\n        self.foreign_keys = True\n\n    def init(self):\n        try:\n            self.schema = self.getSchema()\n            try:\n                self.checkTables()\n            except DbTableError:\n                pass\n            self.log.debug(\"Checking foreign keys...\")\n            foreign_key_error = self.execute(\"PRAGMA foreign_key_check\").fetchone()\n            if foreign_key_error:\n                raise Exception(\"Database foreign key error: %s\" % foreign_key_error)\n        except Exception as err:\n            self.log.error(\"Error loading content.db: %s, rebuilding...\" % Debug.formatException(err))\n            self.close()\n            os.unlink(self.db_path)  # Remove and try again\n            Db.__init__(self, {\"db_name\": \"ContentDb\", \"tables\": {}}, self.db_path)\n            self.foreign_keys = True\n            self.schema = self.getSchema()\n            try:\n                self.checkTables()\n            except DbTableError:\n                pass\n        self.site_ids = {}\n        self.sites = {}\n\n    def getSchema(self):\n        schema = {}\n        schema[\"db_name\"] = \"ContentDb\"\n        schema[\"version\"] = 3\n        schema[\"tables\"] = {}\n\n        if not self.getTableVersion(\"site\"):\n            self.log.debug(\"Migrating from table version-less content.db\")\n            version = int(self.execute(\"PRAGMA user_version\").fetchone()[0])\n            if version > 0:\n                self.checkTables()\n                self.execute(\"INSERT INTO keyvalue ?\", {\"json_id\": 0, \"key\": \"table.site.version\", \"value\": 1})\n                self.execute(\"INSERT INTO keyvalue ?\", {\"json_id\": 0, \"key\": \"table.content.version\", \"value\": 1})\n\n        schema[\"tables\"][\"site\"] = {\n            \"cols\": [\n                [\"site_id\", \"INTEGER  PRIMARY KEY ASC NOT NULL UNIQUE\"],\n                [\"address\", \"TEXT NOT NULL\"]\n            ],\n            \"indexes\": [\n                \"CREATE UNIQUE INDEX site_address ON site (address)\"\n            ],\n            \"schema_changed\": 1\n        }\n\n        schema[\"tables\"][\"content\"] = {\n            \"cols\": [\n                [\"content_id\", \"INTEGER PRIMARY KEY UNIQUE NOT NULL\"],\n                [\"site_id\", \"INTEGER REFERENCES site (site_id) ON DELETE CASCADE\"],\n                [\"inner_path\", \"TEXT\"],\n                [\"size\", \"INTEGER\"],\n                [\"size_files\", \"INTEGER\"],\n                [\"size_files_optional\", \"INTEGER\"],\n                [\"modified\", \"INTEGER\"]\n            ],\n            \"indexes\": [\n                \"CREATE UNIQUE INDEX content_key ON content (site_id, inner_path)\",\n                \"CREATE INDEX content_modified ON content (site_id, modified)\"\n            ],\n            \"schema_changed\": 1\n        }\n\n        return schema\n\n    def initSite(self, site):\n        self.sites[site.address] = site\n\n    def needSite(self, site):\n        if site.address not in self.site_ids:\n            self.execute(\"INSERT OR IGNORE INTO site ?\", {\"address\": site.address})\n            self.site_ids = {}\n            for row in self.execute(\"SELECT * FROM site\"):\n                self.site_ids[row[\"address\"]] = row[\"site_id\"]\n        return self.site_ids[site.address]\n\n    def deleteSite(self, site):\n        site_id = self.site_ids.get(site.address, 0)\n        if site_id:\n            self.execute(\"DELETE FROM site WHERE site_id = :site_id\", {\"site_id\": site_id})\n            del self.site_ids[site.address]\n            del self.sites[site.address]\n\n    def setContent(self, site, inner_path, content, size=0):\n        self.insertOrUpdate(\"content\", {\n            \"size\": size,\n            \"size_files\": sum([val[\"size\"] for key, val in content.get(\"files\", {}).items()]),\n            \"size_files_optional\": sum([val[\"size\"] for key, val in content.get(\"files_optional\", {}).items()]),\n            \"modified\": int(content.get(\"modified\", 0))\n        }, {\n            \"site_id\": self.site_ids.get(site.address, 0),\n            \"inner_path\": inner_path\n        })\n\n    def deleteContent(self, site, inner_path):\n        self.execute(\"DELETE FROM content WHERE ?\", {\"site_id\": self.site_ids.get(site.address, 0), \"inner_path\": inner_path})\n\n    def loadDbDict(self, site):\n        res = self.execute(\n            \"SELECT GROUP_CONCAT(inner_path, '|') AS inner_paths FROM content WHERE ?\",\n            {\"site_id\": self.site_ids.get(site.address, 0)}\n        )\n        row = res.fetchone()\n        if row and row[\"inner_paths\"]:\n            inner_paths = row[\"inner_paths\"].split(\"|\")\n            return dict.fromkeys(inner_paths, False)\n        else:\n            return {}\n\n    def getTotalSize(self, site, ignore=None):\n        params = {\"site_id\": self.site_ids.get(site.address, 0)}\n        if ignore:\n            params[\"not__inner_path\"] = ignore\n        res = self.execute(\"SELECT SUM(size) + SUM(size_files) AS size, SUM(size_files_optional) AS size_optional FROM content WHERE ?\", params)\n        row = dict(res.fetchone())\n\n        if not row[\"size\"]:\n            row[\"size\"] = 0\n        if not row[\"size_optional\"]:\n            row[\"size_optional\"] = 0\n\n        return row[\"size\"], row[\"size_optional\"]\n\n    def listModified(self, site, after=None, before=None):\n        params = {\"site_id\": self.site_ids.get(site.address, 0)}\n        if after:\n            params[\"modified>\"] = after\n        if before:\n            params[\"modified<\"] = before\n        res = self.execute(\"SELECT inner_path, modified FROM content WHERE ?\", params)\n        return {row[\"inner_path\"]: row[\"modified\"] for row in res}\n\ncontent_dbs = {}\n\n\ndef getContentDb(path=None):\n    if not path:\n        path = \"%s/content.db\" % config.data_dir\n    if path not in content_dbs:\n        content_dbs[path] = ContentDb(path)\n        content_dbs[path].init()\n    return content_dbs[path]\n\ngetContentDb()  # Pre-connect to default one\n"
  },
  {
    "path": "src/Content/ContentDbDict.py",
    "content": "import time\nimport os\n\nfrom . import ContentDb\nfrom Debug import Debug\nfrom Config import config\n\n\nclass ContentDbDict(dict):\n    def __init__(self, site, *args, **kwargs):\n        s = time.time()\n        self.site = site\n        self.cached_keys = []\n        self.log = self.site.log\n        self.db = ContentDb.getContentDb()\n        self.db_id = self.db.needSite(site)\n        self.num_loaded = 0\n        super(ContentDbDict, self).__init__(self.db.loadDbDict(site))  # Load keys from database\n        self.log.debug(\"ContentDb init: %.3fs, found files: %s, sites: %s\" % (time.time() - s, len(self), len(self.db.site_ids)))\n\n    def loadItem(self, key):\n        try:\n            self.num_loaded += 1\n            if self.num_loaded % 100 == 0:\n                if config.verbose:\n                    self.log.debug(\"Loaded json: %s (latest: %s) called by: %s\" % (self.num_loaded, key, Debug.formatStack()))\n                else:\n                    self.log.debug(\"Loaded json: %s (latest: %s)\" % (self.num_loaded, key))\n            content = self.site.storage.loadJson(key)\n            dict.__setitem__(self, key, content)\n        except IOError:\n            if dict.get(self, key):\n                self.__delitem__(key)  # File not exists anymore\n            raise KeyError(key)\n\n        self.addCachedKey(key)\n        self.checkLimit()\n\n        return content\n\n    def getItemSize(self, key):\n        return self.site.storage.getSize(key)\n\n    # Only keep last 10 accessed json in memory\n    def checkLimit(self):\n        if len(self.cached_keys) > 10:\n            key_deleted = self.cached_keys.pop(0)\n            dict.__setitem__(self, key_deleted, False)\n\n    def addCachedKey(self, key):\n        if key not in self.cached_keys and key != \"content.json\" and len(key) > 40:  # Always keep keys smaller than 40 char\n            self.cached_keys.append(key)\n\n    def __getitem__(self, key):\n        val = dict.get(self, key)\n        if val:  # Already loaded\n            return val\n        elif val is None:  # Unknown key\n            raise KeyError(key)\n        elif val is False:  # Loaded before, but purged from cache\n            return self.loadItem(key)\n\n    def __setitem__(self, key, val):\n        self.addCachedKey(key)\n        self.checkLimit()\n        size = self.getItemSize(key)\n        self.db.setContent(self.site, key, val, size)\n        dict.__setitem__(self, key, val)\n\n    def __delitem__(self, key):\n        self.db.deleteContent(self.site, key)\n        dict.__delitem__(self, key)\n        try:\n            self.cached_keys.remove(key)\n        except ValueError:\n            pass\n\n    def iteritems(self):\n        for key in dict.keys(self):\n            try:\n                val = self[key]\n            except Exception as err:\n                self.log.warning(\"Error loading %s: %s\" % (key, err))\n                continue\n            yield key, val\n\n    def items(self):\n        back = []\n        for key in dict.keys(self):\n            try:\n                val = self[key]\n            except Exception as err:\n                self.log.warning(\"Error loading %s: %s\" % (key, err))\n                continue\n            back.append((key, val))\n        return back\n\n    def values(self):\n        back = []\n        for key, val in dict.iteritems(self):\n            if not val:\n                try:\n                    val = self.loadItem(key)\n                except Exception:\n                    continue\n            back.append(val)\n        return back\n\n    def get(self, key, default=None):\n        try:\n            return self.__getitem__(key)\n        except KeyError:\n            return default\n        except Exception as err:\n            self.site.bad_files[key] = self.site.bad_files.get(key, 1)\n            dict.__delitem__(self, key)\n            self.log.warning(\"Error loading %s: %s\" % (key, err))\n            return default\n\n    def execute(self, query, params={}):\n        params[\"site_id\"] = self.db_id\n        return self.db.execute(query, params)\n\nif __name__ == \"__main__\":\n    import psutil\n    process = psutil.Process(os.getpid())\n    s_mem = process.memory_info()[0] / float(2 ** 20)\n    root = \"data-live/1MaiL5gfBM1cyb4a8e3iiL8L5gXmoAJu27\"\n    contents = ContentDbDict(\"1MaiL5gfBM1cyb4a8e3iiL8L5gXmoAJu27\", root)\n    print(\"Init len\", len(contents))\n\n    s = time.time()\n    for dir_name in os.listdir(root + \"/data/users/\")[0:8000]:\n        contents[\"data/users/%s/content.json\" % dir_name]\n    print(\"Load: %.3fs\" % (time.time() - s))\n\n    s = time.time()\n    found = 0\n    for key, val in contents.items():\n        found += 1\n        assert key\n        assert val\n    print(\"Found:\", found)\n    print(\"Iteritem: %.3fs\" % (time.time() - s))\n\n    s = time.time()\n    found = 0\n    for key in list(contents.keys()):\n        found += 1\n        assert key in contents\n    print(\"In: %.3fs\" % (time.time() - s))\n\n    print(\"Len:\", len(list(contents.values())), len(list(contents.keys())))\n\n    print(\"Mem: +\", process.memory_info()[0] / float(2 ** 20) - s_mem)\n"
  },
  {
    "path": "src/Content/ContentManager.py",
    "content": "import json\nimport time\nimport re\nimport os\nimport copy\nimport base64\nimport sys\n\nimport gevent\n\nfrom Debug import Debug\nfrom Crypt import CryptHash\nfrom Config import config\nfrom util import helper\nfrom util import Diff\nfrom util import SafeRe\nfrom Peer import PeerHashfield\nfrom .ContentDbDict import ContentDbDict\nfrom Plugin import PluginManager\n\n\nclass VerifyError(Exception):\n    pass\n\n\nclass SignError(Exception):\n    pass\n\n\n@PluginManager.acceptPlugins\nclass ContentManager(object):\n\n    def __init__(self, site):\n        self.site = site\n        self.log = self.site.log\n        self.contents = ContentDbDict(site)\n        self.hashfield = PeerHashfield()\n        self.has_optional_files = False\n\n    # Load all content.json files\n    def loadContents(self):\n        if len(self.contents) == 0:\n            self.log.info(\"ContentDb not initialized, load files from filesystem...\")\n            self.loadContent(add_bad_files=False, delete_removed_files=False)\n        self.site.settings[\"size\"], self.site.settings[\"size_optional\"] = self.getTotalSize()\n\n        # Load hashfield cache\n        if \"hashfield\" in self.site.settings.get(\"cache\", {}):\n            self.hashfield.frombytes(base64.b64decode(self.site.settings[\"cache\"][\"hashfield\"]))\n            del self.site.settings[\"cache\"][\"hashfield\"]\n        elif self.contents.get(\"content.json\") and self.site.settings[\"size_optional\"] > 0:\n            self.site.storage.updateBadFiles()  # No hashfield cache created yet\n        self.has_optional_files = bool(self.hashfield)\n\n        self.contents.db.initSite(self.site)\n\n    def getFileChanges(self, old_files, new_files):\n        deleted = {key: val for key, val in old_files.items() if key not in new_files}\n        deleted_hashes = {val.get(\"sha512\"): key for key, val in old_files.items() if key not in new_files}\n        added = {key: val for key, val in new_files.items() if key not in old_files}\n        renamed = {}\n        for relative_path, node in added.items():\n            hash = node.get(\"sha512\")\n            if hash in deleted_hashes:\n                relative_path_old = deleted_hashes[hash]\n                renamed[relative_path_old] = relative_path\n                del(deleted[relative_path_old])\n        return list(deleted), renamed\n\n    # Load content.json to self.content\n    # Return: Changed files [\"index.html\", \"data/messages.json\"], Deleted files [\"old.jpg\"]\n    def loadContent(self, content_inner_path=\"content.json\", add_bad_files=True, delete_removed_files=True, load_includes=True, force=False):\n        content_inner_path = content_inner_path.strip(\"/\")  # Remove / from beginning\n        old_content = self.contents.get(content_inner_path)\n        content_path = self.site.storage.getPath(content_inner_path)\n        content_dir = helper.getDirname(self.site.storage.getPath(content_inner_path))\n        content_inner_dir = helper.getDirname(content_inner_path)\n\n        if os.path.isfile(content_path):\n            try:\n                # Check if file is newer than what we have\n                if not force and old_content and not self.site.settings.get(\"own\"):\n                    for line in open(content_path):\n                        if '\"modified\"' not in line:\n                            continue\n                        match = re.search(r\"([0-9\\.]+),$\", line.strip(\" \\r\\n\"))\n                        if match and float(match.group(1)) <= old_content.get(\"modified\", 0):\n                            self.log.debug(\"%s loadContent same json file, skipping\" % content_inner_path)\n                            return [], []\n\n                new_content = self.site.storage.loadJson(content_inner_path)\n            except Exception as err:\n                self.log.warning(\"%s load error: %s\" % (content_path, Debug.formatException(err)))\n                return [], []\n        else:\n            self.log.debug(\"Content.json not exist: %s\" % content_path)\n            return [], []  # Content.json not exist\n\n        try:\n            # Get the files where the sha512 changed\n            changed = []\n            deleted = []\n            # Check changed\n            for relative_path, info in new_content.get(\"files\", {}).items():\n                if \"sha512\" in info:\n                    hash_type = \"sha512\"\n                else:  # Backward compatibility\n                    hash_type = \"sha1\"\n\n                new_hash = info[hash_type]\n                if old_content and old_content[\"files\"].get(relative_path):  # We have the file in the old content\n                    old_hash = old_content[\"files\"][relative_path].get(hash_type)\n                else:  # The file is not in the old content\n                    old_hash = None\n                if old_hash != new_hash:\n                    changed.append(content_inner_dir + relative_path)\n\n            # Check changed optional files\n            for relative_path, info in new_content.get(\"files_optional\", {}).items():\n                file_inner_path = content_inner_dir + relative_path\n                new_hash = info[\"sha512\"]\n                if old_content and old_content.get(\"files_optional\", {}).get(relative_path):\n                    # We have the file in the old content\n                    old_hash = old_content[\"files_optional\"][relative_path].get(\"sha512\")\n                    if old_hash != new_hash and self.site.isDownloadable(file_inner_path):\n                        changed.append(file_inner_path)  # Download new file\n                    elif old_hash != new_hash and self.hashfield.hasHash(old_hash) and not self.site.settings.get(\"own\"):\n                        try:\n                            old_hash_id = self.hashfield.getHashId(old_hash)\n                            self.optionalRemoved(file_inner_path, old_hash_id, old_content[\"files_optional\"][relative_path][\"size\"])\n                            self.optionalDelete(file_inner_path)\n                            self.log.debug(\"Deleted changed optional file: %s\" % file_inner_path)\n                        except Exception as err:\n                            self.log.warning(\"Error deleting file %s: %s\" % (file_inner_path, Debug.formatException(err)))\n                else:  # The file is not in the old content\n                    if self.site.isDownloadable(file_inner_path):\n                        changed.append(file_inner_path)  # Download new file\n\n            # Check deleted\n            if old_content:\n                old_files = dict(\n                    old_content.get(\"files\", {}),\n                    **old_content.get(\"files_optional\", {})\n                )\n\n                new_files = dict(\n                    new_content.get(\"files\", {}),\n                    **new_content.get(\"files_optional\", {})\n                )\n\n                deleted, renamed = self.getFileChanges(old_files, new_files)\n\n                for relative_path_old, relative_path_new in renamed.items():\n                    self.log.debug(\"Renaming: %s -> %s\" % (relative_path_old, relative_path_new))\n                    if relative_path_new in new_content.get(\"files_optional\", {}):\n                        self.optionalRenamed(content_inner_dir + relative_path_old, content_inner_dir + relative_path_new)\n                    if self.site.storage.isFile(relative_path_old):\n                        try:\n                            self.site.storage.rename(relative_path_old, relative_path_new)\n                            if relative_path_new in changed:\n                                changed.remove(relative_path_new)\n                            self.log.debug(\"Renamed: %s -> %s\" % (relative_path_old, relative_path_new))\n                        except Exception as err:\n                            self.log.warning(\"Error renaming file: %s -> %s %s\" % (relative_path_old, relative_path_new, err))\n\n                if deleted and not self.site.settings.get(\"own\"):\n                    # Deleting files that no longer in content.json\n                    for file_relative_path in deleted:\n                        file_inner_path = content_inner_dir + file_relative_path\n                        try:\n                            # Check if the deleted file is optional\n                            if old_content.get(\"files_optional\") and old_content[\"files_optional\"].get(file_relative_path):\n                                self.optionalDelete(file_inner_path)\n                                old_hash = old_content[\"files_optional\"][file_relative_path].get(\"sha512\")\n                                if self.hashfield.hasHash(old_hash):\n                                    old_hash_id = self.hashfield.getHashId(old_hash)\n                                    self.optionalRemoved(file_inner_path, old_hash_id, old_content[\"files_optional\"][file_relative_path][\"size\"])\n                            else:\n                                self.site.storage.delete(file_inner_path)\n\n                            self.log.debug(\"Deleted file: %s\" % file_inner_path)\n                        except Exception as err:\n                            self.log.debug(\"Error deleting file %s: %s\" % (file_inner_path, Debug.formatException(err)))\n\n                    # Cleanup empty dirs\n                    tree = {root: [dirs, files] for root, dirs, files in os.walk(self.site.storage.getPath(content_inner_dir))}\n                    for root in sorted(tree, key=len, reverse=True):\n                        dirs, files = tree[root]\n                        if dirs == [] and files == []:\n                            root_inner_path = self.site.storage.getInnerPath(root.replace(\"\\\\\", \"/\"))\n                            self.log.debug(\"Empty directory: %s, cleaning up.\" % root_inner_path)\n                            try:\n                                self.site.storage.deleteDir(root_inner_path)\n                                # Remove from tree dict to reflect changed state\n                                tree[os.path.dirname(root)][0].remove(os.path.basename(root))\n                            except Exception as err:\n                                self.log.debug(\"Error deleting empty directory %s: %s\" % (root_inner_path, err))\n\n            # Check archived\n            if old_content and \"user_contents\" in new_content and \"archived\" in new_content[\"user_contents\"]:\n                old_archived = old_content.get(\"user_contents\", {}).get(\"archived\", {})\n                new_archived = new_content.get(\"user_contents\", {}).get(\"archived\", {})\n                self.log.debug(\"old archived: %s, new archived: %s\" % (len(old_archived), len(new_archived)))\n                archived_changed = {\n                    key: date_archived\n                    for key, date_archived in new_archived.items()\n                    if old_archived.get(key) != new_archived[key]\n                }\n                if archived_changed:\n                    self.log.debug(\"Archived changed: %s\" % archived_changed)\n                    for archived_dirname, date_archived in archived_changed.items():\n                        archived_inner_path = content_inner_dir + archived_dirname + \"/content.json\"\n                        if self.contents.get(archived_inner_path, {}).get(\"modified\", 0) < date_archived:\n                            self.removeContent(archived_inner_path)\n                            deleted += archived_inner_path\n                    self.site.settings[\"size\"], self.site.settings[\"size_optional\"] = self.getTotalSize()\n\n            # Check archived before\n            if old_content and \"user_contents\" in new_content and \"archived_before\" in new_content[\"user_contents\"]:\n                old_archived_before = old_content.get(\"user_contents\", {}).get(\"archived_before\", 0)\n                new_archived_before = new_content.get(\"user_contents\", {}).get(\"archived_before\", 0)\n                if old_archived_before != new_archived_before:\n                    self.log.debug(\"Archived before changed: %s -> %s\" % (old_archived_before, new_archived_before))\n\n                    # Remove downloaded archived files\n                    num_removed_contents = 0\n                    for archived_inner_path in self.listModified(before=new_archived_before):\n                        if archived_inner_path.startswith(content_inner_dir) and archived_inner_path != content_inner_path:\n                            self.removeContent(archived_inner_path)\n                            num_removed_contents += 1\n                    self.site.settings[\"size\"], self.site.settings[\"size_optional\"] = self.getTotalSize()\n\n                    # Remove archived files from download queue\n                    num_removed_bad_files = 0\n                    for bad_file in list(self.site.bad_files.keys()):\n                        if bad_file.endswith(\"content.json\"):\n                            del self.site.bad_files[bad_file]\n                            num_removed_bad_files += 1\n\n                    if num_removed_bad_files > 0:\n                        self.site.worker_manager.removeSolvedFileTasks(mark_as_good=False)\n                        gevent.spawn(self.site.update, since=0)\n\n                    self.log.debug(\"Archived removed contents: %s, removed bad files: %s\" % (num_removed_contents, num_removed_bad_files))\n\n            # Load includes\n            if load_includes and \"includes\" in new_content:\n                for relative_path, info in list(new_content[\"includes\"].items()):\n                    include_inner_path = content_inner_dir + relative_path\n                    if self.site.storage.isFile(include_inner_path):  # Content.json exists, load it\n                        include_changed, include_deleted = self.loadContent(\n                            include_inner_path, add_bad_files=add_bad_files, delete_removed_files=delete_removed_files\n                        )\n                        if include_changed:\n                            changed += include_changed  # Add changed files\n                        if include_deleted:\n                            deleted += include_deleted  # Add changed files\n                    else:  # Content.json not exist, add to changed files\n                        self.log.debug(\"Missing include: %s\" % include_inner_path)\n                        changed += [include_inner_path]\n\n            # Load blind user includes (all subdir)\n            if load_includes and \"user_contents\" in new_content:\n                for relative_dir in os.listdir(content_dir):\n                    include_inner_path = content_inner_dir + relative_dir + \"/content.json\"\n                    if not self.site.storage.isFile(include_inner_path):\n                        continue  # Content.json not exist\n                    include_changed, include_deleted = self.loadContent(\n                        include_inner_path, add_bad_files=add_bad_files, delete_removed_files=delete_removed_files,\n                        load_includes=False\n                    )\n                    if include_changed:\n                        changed += include_changed  # Add changed files\n                    if include_deleted:\n                        deleted += include_deleted  # Add changed files\n\n            # Save some memory\n            new_content[\"signs\"] = None\n            if \"cert_sign\" in new_content:\n                new_content[\"cert_sign\"] = None\n\n            if new_content.get(\"files_optional\"):\n                self.has_optional_files = True\n            # Update the content\n            self.contents[content_inner_path] = new_content\n        except Exception as err:\n            self.log.warning(\"%s parse error: %s\" % (content_inner_path, Debug.formatException(err)))\n            return [], []  # Content.json parse error\n\n        # Add changed files to bad files\n        if add_bad_files:\n            for inner_path in changed:\n                self.site.bad_files[inner_path] = self.site.bad_files.get(inner_path, 0) + 1\n            for inner_path in deleted:\n                if inner_path in self.site.bad_files:\n                    del self.site.bad_files[inner_path]\n                self.site.worker_manager.removeSolvedFileTasks()\n\n        if new_content.get(\"modified\", 0) > self.site.settings.get(\"modified\", 0):\n            # Dont store modifications in the far future (more than 10 minute)\n            self.site.settings[\"modified\"] = min(time.time() + 60 * 10, new_content[\"modified\"])\n\n        return changed, deleted\n\n    def removeContent(self, inner_path):\n        inner_dir = helper.getDirname(inner_path)\n        try:\n            content = self.contents[inner_path]\n            files = dict(\n                content.get(\"files\", {}),\n                **content.get(\"files_optional\", {})\n            )\n        except Exception as err:\n            self.log.debug(\"Error loading %s for removeContent: %s\" % (inner_path, Debug.formatException(err)))\n            files = {}\n        files[\"content.json\"] = True\n        # Deleting files that no longer in content.json\n        for file_relative_path in files:\n            file_inner_path = inner_dir + file_relative_path\n            try:\n                self.site.storage.delete(file_inner_path)\n                self.log.debug(\"Deleted file: %s\" % file_inner_path)\n            except Exception as err:\n                self.log.debug(\"Error deleting file %s: %s\" % (file_inner_path, err))\n        try:\n            self.site.storage.deleteDir(inner_dir)\n        except Exception as err:\n            self.log.debug(\"Error deleting dir %s: %s\" % (inner_dir, err))\n\n        try:\n            del self.contents[inner_path]\n        except Exception as err:\n            self.log.debug(\"Error key from contents: %s\" % inner_path)\n\n    # Get total size of site\n    # Return: 32819 (size of files in kb)\n    def getTotalSize(self, ignore=None):\n        return self.contents.db.getTotalSize(self.site, ignore)\n\n    def listModified(self, after=None, before=None):\n        return self.contents.db.listModified(self.site, after=after, before=before)\n\n    def listContents(self, inner_path=\"content.json\", user_files=False):\n        if inner_path not in self.contents:\n            return []\n        back = [inner_path]\n        content_inner_dir = helper.getDirname(inner_path)\n        for relative_path in list(self.contents[inner_path].get(\"includes\", {}).keys()):\n            include_inner_path = content_inner_dir + relative_path\n            back += self.listContents(include_inner_path)\n        return back\n\n    # Returns if file with the given modification date is archived or not\n    def isArchived(self, inner_path, modified):\n        match = re.match(r\"(.*)/(.*?)/\", inner_path)\n        if not match:\n            return False\n        user_contents_inner_path = match.group(1) + \"/content.json\"\n        relative_directory = match.group(2)\n\n        file_info = self.getFileInfo(user_contents_inner_path)\n        if file_info:\n            time_archived_before = file_info.get(\"archived_before\", 0)\n            time_directory_archived = file_info.get(\"archived\", {}).get(relative_directory, 0)\n            if modified <= time_archived_before or modified <= time_directory_archived:\n                return True\n            else:\n                return False\n        else:\n            return False\n\n    def isDownloaded(self, inner_path, hash_id=None):\n        if not hash_id:\n            file_info = self.getFileInfo(inner_path)\n            if not file_info or \"sha512\" not in file_info:\n                return False\n            hash_id = self.hashfield.getHashId(file_info[\"sha512\"])\n        return hash_id in self.hashfield\n\n    # Is modified since signing\n    def isModified(self, inner_path):\n        s = time.time()\n        if inner_path.endswith(\"content.json\"):\n            try:\n                is_valid = self.verifyFile(inner_path, self.site.storage.open(inner_path), ignore_same=False)\n                if is_valid:\n                    is_modified = False\n                else:\n                    is_modified = True\n            except VerifyError:\n                is_modified = True\n        else:\n            try:\n                self.verifyFile(inner_path, self.site.storage.open(inner_path), ignore_same=False)\n                is_modified = False\n            except VerifyError:\n                is_modified = True\n        return is_modified\n\n    # Find the file info line from self.contents\n    # Return: { \"sha512\": \"c29d73d...21f518\", \"size\": 41 , \"content_inner_path\": \"content.json\"}\n    def getFileInfo(self, inner_path, new_file=False):\n        dirs = inner_path.split(\"/\")  # Parent dirs of content.json\n        inner_path_parts = [dirs.pop()]  # Filename relative to content.json\n        while True:\n            content_inner_path = \"%s/content.json\" % \"/\".join(dirs)\n            content_inner_path = content_inner_path.strip(\"/\")\n            content = self.contents.get(content_inner_path)\n\n            # Check in files\n            if content and \"files\" in content:\n                back = content[\"files\"].get(\"/\".join(inner_path_parts))\n                if back:\n                    back[\"content_inner_path\"] = content_inner_path\n                    back[\"optional\"] = False\n                    back[\"relative_path\"] = \"/\".join(inner_path_parts)\n                    return back\n\n            # Check in optional files\n            if content and \"files_optional\" in content:  # Check if file in this content.json\n                back = content[\"files_optional\"].get(\"/\".join(inner_path_parts))\n                if back:\n                    back[\"content_inner_path\"] = content_inner_path\n                    back[\"optional\"] = True\n                    back[\"relative_path\"] = \"/\".join(inner_path_parts)\n                    return back\n\n            # Return the rules if user dir\n            if content and \"user_contents\" in content:\n                back = content[\"user_contents\"]\n                content_inner_path_dir = helper.getDirname(content_inner_path)\n                relative_content_path = inner_path[len(content_inner_path_dir):]\n                user_auth_address_match = re.match(r\"([A-Za-z0-9]+)/.*\", relative_content_path)\n                if user_auth_address_match:\n                    user_auth_address = user_auth_address_match.group(1)\n                    back[\"content_inner_path\"] = \"%s%s/content.json\" % (content_inner_path_dir, user_auth_address)\n                else:\n                    back[\"content_inner_path\"] = content_inner_path_dir + \"content.json\"\n                back[\"optional\"] = None\n                back[\"relative_path\"] = \"/\".join(inner_path_parts)\n                return back\n\n            if new_file and content:\n                back = {}\n                back[\"content_inner_path\"] = content_inner_path\n                back[\"relative_path\"] = \"/\".join(inner_path_parts)\n                back[\"optional\"] = None\n                return back\n\n            # No inner path in this dir, lets try the parent dir\n            if dirs:\n                inner_path_parts.insert(0, dirs.pop())\n            else:  # No more parent dirs\n                break\n\n        # Not found\n        return False\n\n    # Get rules for the file\n    # Return: The rules for the file or False if not allowed\n    def getRules(self, inner_path, content=None):\n        if not inner_path.endswith(\"content.json\"):  # Find the files content.json first\n            file_info = self.getFileInfo(inner_path)\n            if not file_info:\n                return False  # File not found\n            inner_path = file_info[\"content_inner_path\"]\n\n        if inner_path == \"content.json\":  # Root content.json\n            rules = {}\n            rules[\"signers\"] = self.getValidSigners(inner_path, content)\n            return rules\n\n        dirs = inner_path.split(\"/\")  # Parent dirs of content.json\n        inner_path_parts = [dirs.pop()]  # Filename relative to content.json\n        inner_path_parts.insert(0, dirs.pop())  # Dont check in self dir\n        while True:\n            content_inner_path = \"%s/content.json\" % \"/\".join(dirs)\n            parent_content = self.contents.get(content_inner_path.strip(\"/\"))\n            if parent_content and \"includes\" in parent_content:\n                return parent_content[\"includes\"].get(\"/\".join(inner_path_parts))\n            elif parent_content and \"user_contents\" in parent_content:\n                return self.getUserContentRules(parent_content, inner_path, content)\n            else:  # No inner path in this dir, lets try the parent dir\n                if dirs:\n                    inner_path_parts.insert(0, dirs.pop())\n                else:  # No more parent dirs\n                    break\n\n        return False\n\n    # Get rules for a user file\n    # Return: The rules of the file or False if not allowed\n    def getUserContentRules(self, parent_content, inner_path, content):\n        user_contents = parent_content[\"user_contents\"]\n\n        # Delivered for directory\n        if \"inner_path\" in parent_content:\n            parent_content_dir = helper.getDirname(parent_content[\"inner_path\"])\n            user_address = re.match(r\"([A-Za-z0-9]*?)/\", inner_path[len(parent_content_dir):]).group(1)\n        else:\n            user_address = re.match(r\".*/([A-Za-z0-9]*?)/.*?$\", inner_path).group(1)\n\n        try:\n            if not content:\n                content = self.site.storage.loadJson(inner_path)  # Read the file if no content specified\n            user_urn = \"%s/%s\" % (content[\"cert_auth_type\"], content[\"cert_user_id\"])  # web/nofish@zeroid.bit\n            cert_user_id = content[\"cert_user_id\"]\n        except Exception:  # Content.json not exist\n            user_urn = \"n-a/n-a\"\n            cert_user_id = \"n-a\"\n\n        if user_address in user_contents[\"permissions\"]:\n            rules = copy.copy(user_contents[\"permissions\"].get(user_address, {}))  # Default rules based on address\n        else:\n            rules = copy.copy(user_contents[\"permissions\"].get(cert_user_id, {}))  # Default rules based on username\n\n        if rules is False:\n            banned = True\n            rules = {}\n        else:\n            banned = False\n        if \"signers\" in rules:\n            rules[\"signers\"] = rules[\"signers\"][:]  # Make copy of the signers\n        for permission_pattern, permission_rules in list(user_contents[\"permission_rules\"].items()):  # Regexp rules\n            if not SafeRe.match(permission_pattern, user_urn):\n                continue  # Rule is not valid for user\n            # Update rules if its better than current recorded ones\n            for key, val in permission_rules.items():\n                if key not in rules:\n                    if type(val) is list:\n                        rules[key] = val[:]  # Make copy\n                    else:\n                        rules[key] = val\n                elif type(val) is int:  # Int, update if larger\n                    if val > rules[key]:\n                        rules[key] = val\n                elif hasattr(val, \"startswith\"):  # String, update if longer\n                    if len(val) > len(rules[key]):\n                        rules[key] = val\n                elif type(val) is list:  # List, append\n                    rules[key] += val\n\n        # Accepted cert signers\n        rules[\"cert_signers\"] = user_contents.get(\"cert_signers\", {})\n        rules[\"cert_signers_pattern\"] = user_contents.get(\"cert_signers_pattern\")\n\n        if \"signers\" not in rules:\n            rules[\"signers\"] = []\n\n        if not banned:\n            rules[\"signers\"].append(user_address)  # Add user as valid signer\n        rules[\"user_address\"] = user_address\n        rules[\"includes_allowed\"] = False\n\n        return rules\n\n    # Get diffs for changed files\n    def getDiffs(self, inner_path, limit=30 * 1024, update_files=True):\n        if inner_path not in self.contents:\n            return {}\n        diffs = {}\n        content_inner_path_dir = helper.getDirname(inner_path)\n        for file_relative_path in self.contents[inner_path].get(\"files\", {}):\n            file_inner_path = content_inner_path_dir + file_relative_path\n            if self.site.storage.isFile(file_inner_path + \"-new\"):  # New version present\n                diffs[file_relative_path] = Diff.diff(\n                    list(self.site.storage.open(file_inner_path)),\n                    list(self.site.storage.open(file_inner_path + \"-new\")),\n                    limit=limit\n                )\n                if update_files:\n                    self.site.storage.delete(file_inner_path)\n                    self.site.storage.rename(file_inner_path + \"-new\", file_inner_path)\n            if self.site.storage.isFile(file_inner_path + \"-old\"):  # Old version present\n                diffs[file_relative_path] = Diff.diff(\n                    list(self.site.storage.open(file_inner_path + \"-old\")),\n                    list(self.site.storage.open(file_inner_path)),\n                    limit=limit\n                )\n                if update_files:\n                    self.site.storage.delete(file_inner_path + \"-old\")\n        return diffs\n\n    def hashFile(self, dir_inner_path, file_relative_path, optional=False):\n        back = {}\n        file_inner_path = dir_inner_path + \"/\" + file_relative_path\n\n        file_path = self.site.storage.getPath(file_inner_path)\n        file_size = os.path.getsize(file_path)\n        sha512sum = CryptHash.sha512sum(file_path)  # Calculate sha512 sum of file\n        if optional and not self.hashfield.hasHash(sha512sum):\n            self.optionalDownloaded(file_inner_path, self.hashfield.getHashId(sha512sum), file_size, own=True)\n\n        back[file_relative_path] = {\"sha512\": sha512sum, \"size\": os.path.getsize(file_path)}\n        return back\n\n    def isValidRelativePath(self, relative_path):\n        if \"..\" in relative_path.replace(\"\\\\\", \"/\").split(\"/\"):\n            return False\n        elif len(relative_path) > 255:\n            return False\n        elif relative_path[0] in (\"/\", \"\\\\\"):  # Starts with\n            return False\n        elif relative_path[-1] in (\".\", \" \"):  # Ends with\n            return False\n        elif re.match(r\".*(^|/)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]|CONOUT\\$|CONIN\\$)(\\.|/|$)\", relative_path, re.IGNORECASE):  # Protected on Windows\n            return False\n        else:\n            return re.match(r\"^[^\\x00-\\x1F\\\"*:<>?\\\\|]+$\", relative_path)\n\n    def sanitizePath(self, inner_path):\n        return re.sub(\"[\\x00-\\x1F\\\"*:<>?\\\\|]\", \"\", inner_path)\n\n    # Hash files in directory\n    def hashFiles(self, dir_inner_path, ignore_pattern=None, optional_pattern=None):\n        files_node = {}\n        files_optional_node = {}\n        db_inner_path = self.site.storage.getDbFile()\n        if dir_inner_path and not self.isValidRelativePath(dir_inner_path):\n            ignored = True\n            self.log.error(\"- [ERROR] Only ascii encoded directories allowed: %s\" % dir_inner_path)\n\n        for file_relative_path in self.site.storage.walk(dir_inner_path, ignore_pattern):\n            file_name = helper.getFilename(file_relative_path)\n\n            ignored = optional = False\n            if file_name == \"content.json\":\n                ignored = True\n            elif file_name.startswith(\".\") or file_name.endswith(\"-old\") or file_name.endswith(\"-new\"):\n                ignored = True\n            elif not self.isValidRelativePath(file_relative_path):\n                ignored = True\n                self.log.error(\"- [ERROR] Invalid filename: %s\" % file_relative_path)\n            elif dir_inner_path == \"\" and db_inner_path and file_relative_path.startswith(db_inner_path):\n                ignored = True\n            elif optional_pattern and SafeRe.match(optional_pattern, file_relative_path):\n                optional = True\n\n            if ignored:  # Ignore content.json, defined regexp and files starting with .\n                self.log.info(\"- [SKIPPED] %s\" % file_relative_path)\n            else:\n                if optional:\n                    self.log.info(\"- [OPTIONAL] %s\" % file_relative_path)\n                    files_optional_node.update(\n                        self.hashFile(dir_inner_path, file_relative_path, optional=True)\n                    )\n                else:\n                    self.log.info(\"- %s\" % file_relative_path)\n                    files_node.update(\n                        self.hashFile(dir_inner_path, file_relative_path)\n                    )\n        return files_node, files_optional_node\n\n    # Create and sign a content.json\n    # Return: The new content if filewrite = False\n    def sign(self, inner_path=\"content.json\", privatekey=None, filewrite=True, update_changed_files=False, extend=None, remove_missing_optional=False):\n        if not inner_path.endswith(\"content.json\"):\n            raise SignError(\"Invalid file name, you can only sign content.json files\")\n\n        if inner_path in self.contents:\n            content = self.contents.get(inner_path)\n            if content and content.get(\"cert_sign\", False) is None and self.site.storage.isFile(inner_path):\n                # Recover cert_sign from file\n                content[\"cert_sign\"] = self.site.storage.loadJson(inner_path).get(\"cert_sign\")\n        else:\n            content = None\n        if not content:  # Content not exist yet, load default one\n            self.log.info(\"File %s not exist yet, loading default values...\" % inner_path)\n\n            if self.site.storage.isFile(inner_path):\n                content = self.site.storage.loadJson(inner_path)\n                if \"files\" not in content:\n                    content[\"files\"] = {}\n                if \"signs\" not in content:\n                    content[\"signs\"] = {}\n            else:\n                content = {\"files\": {}, \"signs\": {}}  # Default content.json\n\n            if inner_path == \"content.json\":  # It's the root content.json, add some more fields\n                content[\"title\"] = \"%s - ZeroNet_\" % self.site.address\n                content[\"description\"] = \"\"\n                content[\"signs_required\"] = 1\n                content[\"ignore\"] = \"\"\n\n        if extend:\n            # Add extend keys if not exists\n            for key, val in list(extend.items()):\n                if not content.get(key):\n                    content[key] = val\n                    self.log.info(\"Extending content.json with: %s\" % key)\n\n        directory = helper.getDirname(self.site.storage.getPath(inner_path))\n        inner_directory = helper.getDirname(inner_path)\n        self.log.info(\"Opening site data directory: %s...\" % directory)\n\n        changed_files = [inner_path]\n        files_node, files_optional_node = self.hashFiles(\n            helper.getDirname(inner_path), content.get(\"ignore\"), content.get(\"optional\")\n        )\n\n        if not remove_missing_optional:\n            for file_inner_path, file_details in content.get(\"files_optional\", {}).items():\n                if file_inner_path not in files_optional_node:\n                    files_optional_node[file_inner_path] = file_details\n\n        # Find changed files\n        files_merged = files_node.copy()\n        files_merged.update(files_optional_node)\n        for file_relative_path, file_details in files_merged.items():\n            old_hash = content.get(\"files\", {}).get(file_relative_path, {}).get(\"sha512\")\n            new_hash = files_merged[file_relative_path][\"sha512\"]\n            if old_hash != new_hash:\n                changed_files.append(inner_directory + file_relative_path)\n\n        self.log.debug(\"Changed files: %s\" % changed_files)\n        if update_changed_files:\n            for file_path in changed_files:\n                self.site.storage.onUpdated(file_path)\n\n        # Generate new content.json\n        self.log.info(\"Adding timestamp and sha512sums to new content.json...\")\n\n        new_content = content.copy()  # Create a copy of current content.json\n        new_content[\"files\"] = files_node  # Add files sha512 hash\n        if files_optional_node:\n            new_content[\"files_optional\"] = files_optional_node\n        elif \"files_optional\" in new_content:\n            del new_content[\"files_optional\"]\n\n        new_content[\"modified\"] = int(time.time())  # Add timestamp\n        if inner_path == \"content.json\":\n            new_content[\"zeronet_version\"] = config.version\n            new_content[\"signs_required\"] = content.get(\"signs_required\", 1)\n\n        new_content[\"address\"] = self.site.address\n        new_content[\"inner_path\"] = inner_path\n\n        # Verify private key\n        from Crypt import CryptBitcoin\n        self.log.info(\"Verifying private key...\")\n        privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey)\n        valid_signers = self.getValidSigners(inner_path, new_content)\n        if privatekey_address not in valid_signers:\n            raise SignError(\n                \"Private key invalid! Valid signers: %s, Private key address: %s\" %\n                (valid_signers, privatekey_address)\n            )\n        self.log.info(\"Correct %s in valid signers: %s\" % (privatekey_address, valid_signers))\n\n        if inner_path == \"content.json\" and privatekey_address == self.site.address:\n            # If signing using the root key, then sign the valid signers\n            signers_data = \"%s:%s\" % (new_content[\"signs_required\"], \",\".join(valid_signers))\n            new_content[\"signers_sign\"] = CryptBitcoin.sign(str(signers_data), privatekey)\n            if not new_content[\"signers_sign\"]:\n                self.log.info(\"Old style address, signers_sign is none\")\n\n        self.log.info(\"Signing %s...\" % inner_path)\n\n        if \"signs\" in new_content:\n            del(new_content[\"signs\"])  # Delete old signs\n        if \"sign\" in new_content:\n            del(new_content[\"sign\"])  # Delete old sign (backward compatibility)\n\n        sign_content = json.dumps(new_content, sort_keys=True)\n        sign = CryptBitcoin.sign(sign_content, privatekey)\n        # new_content[\"signs\"] = content.get(\"signs\", {}) # TODO: Multisig\n        if sign:  # If signing is successful (not an old address)\n            new_content[\"signs\"] = {}\n            new_content[\"signs\"][privatekey_address] = sign\n\n        self.verifyContent(inner_path, new_content)\n\n        if filewrite:\n            self.log.info(\"Saving to %s...\" % inner_path)\n            self.site.storage.writeJson(inner_path, new_content)\n            self.contents[inner_path] = new_content\n\n        self.log.info(\"File %s signed!\" % inner_path)\n\n        if filewrite:  # Written to file\n            return True\n        else:  # Return the new content\n            return new_content\n\n    # The valid signers of content.json file\n    # Return: [\"1KRxE1s3oDyNDawuYWpzbLUwNm8oDbeEp6\", \"13ReyhCsjhpuCVahn1DHdf6eMqqEVev162\"]\n    def getValidSigners(self, inner_path, content=None):\n        valid_signers = []\n        if inner_path == \"content.json\":  # Root content.json\n            if \"content.json\" in self.contents and \"signers\" in self.contents[\"content.json\"]:\n                valid_signers += self.contents[\"content.json\"][\"signers\"][:]\n        else:\n            rules = self.getRules(inner_path, content)\n            if rules and \"signers\" in rules:\n                valid_signers += rules[\"signers\"]\n\n        if self.site.address not in valid_signers:\n            valid_signers.append(self.site.address)  # Site address always valid\n        return valid_signers\n\n    # Return: The required number of valid signs for the content.json\n    def getSignsRequired(self, inner_path, content=None):\n        return 1  # Todo: Multisig\n\n    def verifyCertSign(self, user_address, user_auth_type, user_name, issuer_address, sign):\n        from Crypt import CryptBitcoin\n        cert_subject = \"%s#%s/%s\" % (user_address, user_auth_type, user_name)\n        return CryptBitcoin.verify(cert_subject, issuer_address, sign)\n\n    def verifyCert(self, inner_path, content):\n        rules = self.getRules(inner_path, content)\n\n        if not rules:\n            raise VerifyError(\"No rules for this file\")\n\n        if not rules.get(\"cert_signers\") and not rules.get(\"cert_signers_pattern\"):\n            return True  # Does not need cert\n\n        if \"cert_user_id\" not in content:\n            raise VerifyError(\"Missing cert_user_id\")\n\n        if content[\"cert_user_id\"].count(\"@\") != 1:\n            raise VerifyError(\"Invalid domain in cert_user_id\")\n\n        name, domain = content[\"cert_user_id\"].rsplit(\"@\", 1)\n        cert_address = rules[\"cert_signers\"].get(domain)\n        if not cert_address:  # Unknown Cert signer\n            if rules.get(\"cert_signers_pattern\") and SafeRe.match(rules[\"cert_signers_pattern\"], domain):\n                cert_address = domain\n            else:\n                raise VerifyError(\"Invalid cert signer: %s\" % domain)\n\n        return self.verifyCertSign(rules[\"user_address\"], content[\"cert_auth_type\"], name, cert_address, content[\"cert_sign\"])\n\n    # Checks if the content.json content is valid\n    # Return: True or False\n    def verifyContent(self, inner_path, content):\n        content_size = len(json.dumps(content, indent=1)) + sum([file[\"size\"] for file in list(content[\"files\"].values()) if file[\"size\"] >= 0])  # Size of new content\n        # Calculate old content size\n        old_content = self.contents.get(inner_path)\n        if old_content:\n            old_content_size = len(json.dumps(old_content, indent=1)) + sum([file[\"size\"] for file in list(old_content.get(\"files\", {}).values())])\n            old_content_size_optional = sum([file[\"size\"] for file in list(old_content.get(\"files_optional\", {}).values())])\n        else:\n            old_content_size = 0\n            old_content_size_optional = 0\n\n        # Reset site site on first content.json\n        if not old_content and inner_path == \"content.json\":\n            self.site.settings[\"size\"] = 0\n\n        content_size_optional = sum([file[\"size\"] for file in list(content.get(\"files_optional\", {}).values()) if file[\"size\"] >= 0])\n        site_size = self.site.settings[\"size\"] - old_content_size + content_size  # Site size without old content plus the new\n        site_size_optional = self.site.settings[\"size_optional\"] - old_content_size_optional + content_size_optional  # Site size without old content plus the new\n\n        site_size_limit = self.site.getSizeLimit() * 1024 * 1024\n\n        # Check site address\n        if content.get(\"address\") and content[\"address\"] != self.site.address:\n            raise VerifyError(\"Wrong site address: %s != %s\" % (content[\"address\"], self.site.address))\n\n        # Check file inner path\n        if content.get(\"inner_path\") and content[\"inner_path\"] != inner_path:\n            raise VerifyError(\"Wrong inner_path: %s\" % content[\"inner_path\"])\n\n        # If our content.json file bigger than the size limit throw error\n        if inner_path == \"content.json\":\n            content_size_file = len(json.dumps(content, indent=1))\n            if content_size_file > site_size_limit:\n                # Save site size to display warning\n                self.site.settings[\"size\"] = site_size\n                task = self.site.worker_manager.tasks.findTask(inner_path)\n                if task:  # Dont try to download from other peers\n                    self.site.worker_manager.failTask(task)\n                raise VerifyError(\"Content too large %s B > %s B, aborting task...\" % (site_size, site_size_limit))\n\n        # Verify valid filenames\n        for file_relative_path in list(content.get(\"files\", {}).keys()) + list(content.get(\"files_optional\", {}).keys()):\n            if not self.isValidRelativePath(file_relative_path):\n                raise VerifyError(\"Invalid relative path: %s\" % file_relative_path)\n\n        if inner_path == \"content.json\":\n            self.site.settings[\"size\"] = site_size\n            self.site.settings[\"size_optional\"] = site_size_optional\n            return True  # Root content.json is passed\n        else:\n            if self.verifyContentInclude(inner_path, content, content_size, content_size_optional):\n                self.site.settings[\"size\"] = site_size\n                self.site.settings[\"size_optional\"] = site_size_optional\n                return True\n            else:\n                raise VerifyError(\"Content verify error\")\n\n    def verifyContentInclude(self, inner_path, content, content_size, content_size_optional):\n        # Load include details\n        rules = self.getRules(inner_path, content)\n        if not rules:\n            raise VerifyError(\"No rules\")\n\n        # Check include size limit\n        if rules.get(\"max_size\") is not None:  # Include size limit\n            if content_size > rules[\"max_size\"]:\n                raise VerifyError(\"Include too large %sB > %sB\" % (content_size, rules[\"max_size\"]))\n\n        if rules.get(\"max_size_optional\") is not None:  # Include optional files limit\n            if content_size_optional > rules[\"max_size_optional\"]:\n                raise VerifyError(\"Include optional files too large %sB > %sB\" % (\n                    content_size_optional, rules[\"max_size_optional\"])\n                )\n\n        # Filename limit\n        if rules.get(\"files_allowed\"):\n            for file_inner_path in list(content[\"files\"].keys()):\n                if not SafeRe.match(r\"^%s$\" % rules[\"files_allowed\"], file_inner_path):\n                    raise VerifyError(\"File not allowed: %s\" % file_inner_path)\n\n        if rules.get(\"files_allowed_optional\"):\n            for file_inner_path in list(content.get(\"files_optional\", {}).keys()):\n                if not SafeRe.match(r\"^%s$\" % rules[\"files_allowed_optional\"], file_inner_path):\n                    raise VerifyError(\"Optional file not allowed: %s\" % file_inner_path)\n\n        # Check if content includes allowed\n        if rules.get(\"includes_allowed\") is False and content.get(\"includes\"):\n            raise VerifyError(\"Includes not allowed\")\n\n        return True  # All good\n\n    # Verify file validity\n    # Return: None = Same as before, False = Invalid, True = Valid\n    def verifyFile(self, inner_path, file, ignore_same=True):\n        if inner_path.endswith(\"content.json\"):  # content.json: Check using sign\n            from Crypt import CryptBitcoin\n            try:\n                if type(file) is dict:\n                    new_content = file\n                else:\n                    try:\n                        if sys.version_info.major == 3 and sys.version_info.minor < 6:\n                            new_content = json.loads(file.read().decode(\"utf8\"))\n                        else:\n                            new_content = json.load(file)\n                    except Exception as err:\n                        raise VerifyError(\"Invalid json file: %s\" % err)\n                if inner_path in self.contents:\n                    old_content = self.contents.get(inner_path, {\"modified\": 0})\n                    # Checks if its newer the ours\n                    if old_content[\"modified\"] == new_content[\"modified\"] and ignore_same:  # Ignore, have the same content.json\n                        return None\n                    elif old_content[\"modified\"] > new_content[\"modified\"]:  # We have newer\n                        raise VerifyError(\n                            \"We have newer (Our: %s, Sent: %s)\" %\n                            (old_content[\"modified\"], new_content[\"modified\"])\n                        )\n                if new_content[\"modified\"] > time.time() + 60 * 60 * 24:  # Content modified in the far future (allow 1 day+)\n                    raise VerifyError(\"Modify timestamp is in the far future!\")\n                if self.isArchived(inner_path, new_content[\"modified\"]):\n                    if inner_path in self.site.bad_files:\n                        del self.site.bad_files[inner_path]\n                    raise VerifyError(\"This file is archived!\")\n                # Check sign\n                sign = new_content.get(\"sign\")\n                signs = new_content.get(\"signs\", {})\n                if \"sign\" in new_content:\n                    del(new_content[\"sign\"])  # The file signed without the sign\n                if \"signs\" in new_content:\n                    del(new_content[\"signs\"])  # The file signed without the signs\n\n                sign_content = json.dumps(new_content, sort_keys=True)  # Dump the json to string to remove whitepsace\n\n                # Fix float representation error on Android\n                modified = new_content[\"modified\"]\n                if config.fix_float_decimals and type(modified) is float and not str(modified).endswith(\".0\"):\n                    modified_fixed = \"{:.6f}\".format(modified).strip(\"0.\")\n                    sign_content = sign_content.replace(\n                        '\"modified\": %s' % repr(modified),\n                        '\"modified\": %s' % modified_fixed\n                    )\n\n                if signs:  # New style signing\n                    valid_signers = self.getValidSigners(inner_path, new_content)\n                    signs_required = self.getSignsRequired(inner_path, new_content)\n\n                    if inner_path == \"content.json\" and len(valid_signers) > 1:  # Check signers_sign on root content.json\n                        signers_data = \"%s:%s\" % (signs_required, \",\".join(valid_signers))\n                        if not CryptBitcoin.verify(signers_data, self.site.address, new_content[\"signers_sign\"]):\n                            raise VerifyError(\"Invalid signers_sign!\")\n\n                    if inner_path != \"content.json\" and not self.verifyCert(inner_path, new_content):  # Check if cert valid\n                        raise VerifyError(\"Invalid cert!\")\n\n                    valid_signs = 0\n                    for address in valid_signers:\n                        if address in signs:\n                            valid_signs += CryptBitcoin.verify(sign_content, address, signs[address])\n                        if valid_signs >= signs_required:\n                            break  # Break if we has enough signs\n                    if valid_signs < signs_required:\n                        raise VerifyError(\"Valid signs: %s/%s\" % (valid_signs, signs_required))\n                    else:\n                        return self.verifyContent(inner_path, new_content)\n                else:  # Old style signing\n                    raise VerifyError(\"Invalid old-style sign\")\n\n            except Exception as err:\n                self.log.warning(\"%s: verify sign error: %s\" % (inner_path, Debug.formatException(err)))\n                raise err\n\n        else:  # Check using sha512 hash\n            file_info = self.getFileInfo(inner_path)\n            if file_info:\n                if CryptHash.sha512sum(file) != file_info.get(\"sha512\", \"\"):\n                    raise VerifyError(\"Invalid hash\")\n\n                if file_info.get(\"size\", 0) != file.tell():\n                    raise VerifyError(\n                        \"File size does not match %s <> %s\" %\n                        (inner_path, file.tell(), file_info.get(\"size\", 0))\n                    )\n\n                return True\n\n            else:  # File not in content.json\n                raise VerifyError(\"File not in content.json\")\n\n    def optionalDelete(self, inner_path):\n        self.site.storage.delete(inner_path)\n\n    def optionalDownloaded(self, inner_path, hash_id, size=None, own=False):\n        if size is None:\n            size = self.site.storage.getSize(inner_path)\n\n        done = self.hashfield.appendHashId(hash_id)\n        self.site.settings[\"optional_downloaded\"] += size\n        return done\n\n    def optionalRemoved(self, inner_path, hash_id, size=None):\n        if size is None:\n            size = self.site.storage.getSize(inner_path)\n        done = self.hashfield.removeHashId(hash_id)\n\n        self.site.settings[\"optional_downloaded\"] -= size\n        return done\n\n    def optionalRenamed(self, inner_path_old, inner_path_new):\n        return True\n"
  },
  {
    "path": "src/Content/__init__.py",
    "content": "from .ContentManager import ContentManager"
  },
  {
    "path": "src/Crypt/Crypt.py",
    "content": "from Config import config\nfrom util import ThreadPool\n\nthread_pool_crypt = ThreadPool.ThreadPool(config.threads_crypt)"
  },
  {
    "path": "src/Crypt/CryptBitcoin.py",
    "content": "import logging\nimport base64\nimport binascii\nimport time\nimport hashlib\n\nfrom util.Electrum import dbl_format\nfrom Config import config\n\nimport util.OpensslFindPatch\n\nlib_verify_best = \"sslcrypto\"\n\nfrom lib import sslcrypto\nsslcurve_native = sslcrypto.ecc.get_curve(\"secp256k1\")\nsslcurve_fallback = sslcrypto.fallback.ecc.get_curve(\"secp256k1\")\nsslcurve = sslcurve_native\n\ndef loadLib(lib_name, silent=False):\n    global sslcurve, libsecp256k1message, lib_verify_best\n    if lib_name == \"libsecp256k1\":\n        s = time.time()\n        from lib import libsecp256k1message\n        import coincurve\n        lib_verify_best = \"libsecp256k1\"\n        if not silent:\n            logging.info(\n                \"Libsecpk256k1 loaded: %s in %.3fs\" %\n                (type(coincurve._libsecp256k1.lib).__name__, time.time() - s)\n            )\n    elif lib_name == \"sslcrypto\":\n        sslcurve = sslcurve_native\n        if sslcurve_native == sslcurve_fallback:\n            logging.warning(\"SSLCurve fallback loaded instead of native\")\n    elif lib_name == \"sslcrypto_fallback\":\n        sslcurve = sslcurve_fallback\n\ntry:\n    if not config.use_libsecp256k1:\n        raise Exception(\"Disabled by config\")\n    loadLib(\"libsecp256k1\")\n    lib_verify_best = \"libsecp256k1\"\nexcept Exception as err:\n    logging.info(\"Libsecp256k1 load failed: %s\" % err)\n\n\ndef newPrivatekey():  # Return new private key\n    return sslcurve.private_to_wif(sslcurve.new_private_key()).decode()\n\n\ndef newSeed():\n    return binascii.hexlify(sslcurve.new_private_key()).decode()\n\n\ndef hdPrivatekey(seed, child):\n    # Too large child id could cause problems\n    privatekey_bin = sslcurve.derive_child(seed.encode(), child % 100000000)\n    return sslcurve.private_to_wif(privatekey_bin).decode()\n\n\ndef privatekeyToAddress(privatekey):  # Return address from private key\n    try:\n        if len(privatekey) == 64:\n            privatekey_bin = bytes.fromhex(privatekey)\n        else:\n            privatekey_bin = sslcurve.wif_to_private(privatekey.encode())\n        return sslcurve.private_to_address(privatekey_bin).decode()\n    except Exception:  # Invalid privatekey\n        return False\n\n\ndef sign(data, privatekey):  # Return sign to data using private key\n    if privatekey.startswith(\"23\") and len(privatekey) > 52:\n        return None  # Old style private key not supported\n    return base64.b64encode(sslcurve.sign(\n        data.encode(),\n        sslcurve.wif_to_private(privatekey.encode()),\n        recoverable=True,\n        hash=dbl_format\n    )).decode()\n\n\ndef verify(data, valid_address, sign, lib_verify=None):  # Verify data using address and sign\n    if not lib_verify:\n        lib_verify = lib_verify_best\n\n    if not sign:\n        return False\n\n    if lib_verify == \"libsecp256k1\":\n        sign_address = libsecp256k1message.recover_address(data.encode(\"utf8\"), sign).decode(\"utf8\")\n    elif lib_verify in (\"sslcrypto\", \"sslcrypto_fallback\"):\n        publickey = sslcurve.recover(base64.b64decode(sign), data.encode(), hash=dbl_format)\n        sign_address = sslcurve.public_to_address(publickey).decode()\n    else:\n        raise Exception(\"No library enabled for signature verification\")\n\n    if type(valid_address) is list:  # Any address in the list\n        return sign_address in valid_address\n    else:  # One possible address\n        return sign_address == valid_address\n"
  },
  {
    "path": "src/Crypt/CryptConnection.py",
    "content": "import sys\nimport logging\nimport os\nimport ssl\nimport hashlib\nimport random\n\nfrom Config import config\nfrom util import helper\n\n\nclass CryptConnectionManager:\n    def __init__(self):\n        if config.openssl_bin_file:\n            self.openssl_bin = config.openssl_bin_file\n        elif sys.platform.startswith(\"win\"):\n            self.openssl_bin = \"tools\\\\openssl\\\\openssl.exe\"\n        elif config.dist_type.startswith(\"bundle_linux\"):\n            self.openssl_bin = \"../runtime/bin/openssl\"\n        else:\n            self.openssl_bin = \"openssl\"\n\n        self.context_client = None\n        self.context_server = None\n\n        self.openssl_conf_template = \"src/lib/openssl/openssl.cnf\"\n        self.openssl_conf = config.data_dir + \"/openssl.cnf\"\n\n        self.openssl_env = {\n            \"OPENSSL_CONF\": self.openssl_conf,\n            \"RANDFILE\": config.data_dir + \"/openssl-rand.tmp\"\n        }\n\n        self.crypt_supported = []  # Supported cryptos\n\n        self.cacert_pem = config.data_dir + \"/cacert-rsa.pem\"\n        self.cakey_pem = config.data_dir + \"/cakey-rsa.pem\"\n        self.cert_pem = config.data_dir + \"/cert-rsa.pem\"\n        self.cert_csr = config.data_dir + \"/cert-rsa.csr\"\n        self.key_pem = config.data_dir + \"/key-rsa.pem\"\n\n        self.log = logging.getLogger(\"CryptConnectionManager\")\n        self.log.debug(\"Version: %s\" % ssl.OPENSSL_VERSION)\n\n        self.fakedomains = [\n            \"yahoo.com\", \"amazon.com\", \"live.com\", \"microsoft.com\", \"mail.ru\", \"csdn.net\", \"bing.com\",\n            \"amazon.co.jp\", \"office.com\", \"imdb.com\", \"msn.com\", \"samsung.com\", \"huawei.com\", \"ztedevices.com\",\n            \"godaddy.com\", \"w3.org\", \"gravatar.com\", \"creativecommons.org\", \"hatena.ne.jp\",\n            \"adobe.com\", \"opera.com\", \"apache.org\", \"rambler.ru\", \"one.com\", \"nationalgeographic.com\",\n            \"networksolutions.com\", \"php.net\", \"python.org\", \"phoca.cz\", \"debian.org\", \"ubuntu.com\",\n            \"nazwa.pl\", \"symantec.com\"\n        ]\n\n    def createSslContexts(self):\n        if self.context_server and self.context_client:\n            return False\n        ciphers = \"ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:AES128-SHA256:AES256-SHA:\"\n        ciphers += \"!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK\"\n\n        if hasattr(ssl, \"PROTOCOL_TLS\"):\n            protocol = ssl.PROTOCOL_TLS\n        else:\n            protocol = ssl.PROTOCOL_TLSv1_2\n        self.context_client = ssl.SSLContext(protocol)\n        self.context_client.check_hostname = False\n        self.context_client.verify_mode = ssl.CERT_NONE\n\n        self.context_server = ssl.SSLContext(protocol)\n        self.context_server.load_cert_chain(self.cert_pem, self.key_pem)\n\n        for ctx in (self.context_client, self.context_server):\n            ctx.set_ciphers(ciphers)\n            ctx.options |= ssl.OP_NO_COMPRESSION\n            try:\n                ctx.set_alpn_protocols([\"h2\", \"http/1.1\"])\n                ctx.set_npn_protocols([\"h2\", \"http/1.1\"])\n            except Exception:\n                pass\n\n    # Select crypt that supported by both sides\n    # Return: Name of the crypto\n    def selectCrypt(self, client_supported):\n        for crypt in self.crypt_supported:\n            if crypt in client_supported:\n                return crypt\n        return False\n\n    # Wrap socket for crypt\n    # Return: wrapped socket\n    def wrapSocket(self, sock, crypt, server=False, cert_pin=None):\n        if crypt == \"tls-rsa\":\n            if server:\n                sock_wrapped = self.context_server.wrap_socket(sock, server_side=True)\n            else:\n                sock_wrapped = self.context_client.wrap_socket(sock, server_hostname=random.choice(self.fakedomains))\n            if cert_pin:\n                cert_hash = hashlib.sha256(sock_wrapped.getpeercert(True)).hexdigest()\n                if cert_hash != cert_pin:\n                    raise Exception(\"Socket certificate does not match (%s != %s)\" % (cert_hash, cert_pin))\n            return sock_wrapped\n        else:\n            return sock\n\n    def removeCerts(self):\n        if config.keep_ssl_cert:\n            return False\n        for file_name in [\"cert-rsa.pem\", \"key-rsa.pem\", \"cacert-rsa.pem\", \"cakey-rsa.pem\", \"cacert-rsa.srl\", \"cert-rsa.csr\", \"openssl-rand.tmp\"]:\n            file_path = \"%s/%s\" % (config.data_dir, file_name)\n            if os.path.isfile(file_path):\n                os.unlink(file_path)\n\n    # Load and create cert files is necessary\n    def loadCerts(self):\n        if config.disable_encryption:\n            return False\n\n        if self.createSslRsaCert() and \"tls-rsa\" not in self.crypt_supported:\n            self.crypt_supported.append(\"tls-rsa\")\n\n    # Try to create RSA server cert + sign for connection encryption\n    # Return: True on success\n    def createSslRsaCert(self):\n        casubjects = [\n            \"/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon\",\n            \"/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3\",\n            \"/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA\",\n            \"/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA\"\n        ]\n        self.openssl_env['CN'] = random.choice(self.fakedomains)\n\n        if os.path.isfile(self.cert_pem) and os.path.isfile(self.key_pem):\n            self.createSslContexts()\n            return True  # Files already exits\n\n        import subprocess\n\n        # Replace variables in config template\n        conf_template = open(self.openssl_conf_template).read()\n        conf_template = conf_template.replace(\"$ENV::CN\", self.openssl_env['CN'])\n        open(self.openssl_conf, \"w\").write(conf_template)\n\n        # Generate CAcert and CAkey\n        cmd_params = helper.shellquote(\n            self.openssl_bin,\n            self.openssl_conf,\n            random.choice(casubjects),\n            self.cakey_pem,\n            self.cacert_pem\n        )\n        cmd = \"%s req -new -newkey rsa:2048 -days 3650 -nodes -x509 -config %s -subj %s -keyout %s -out %s -batch\" % cmd_params\n        self.log.debug(\"Generating RSA CAcert and CAkey PEM files...\")\n        self.log.debug(\"Running: %s\" % cmd)\n        proc = subprocess.Popen(\n            cmd, shell=True, stderr=subprocess.STDOUT,\n            stdout=subprocess.PIPE, env=self.openssl_env\n        )\n        back = proc.stdout.read().strip().decode(errors=\"replace\").replace(\"\\r\", \"\")\n        proc.wait()\n\n        if not (os.path.isfile(self.cacert_pem) and os.path.isfile(self.cakey_pem)):\n            self.log.error(\"RSA ECC SSL CAcert generation failed, CAcert or CAkey files not exist. (%s)\" % back)\n            return False\n        else:\n            self.log.debug(\"Result: %s\" % back)\n\n        # Generate certificate key and signing request\n        cmd_params = helper.shellquote(\n            self.openssl_bin,\n            self.key_pem,\n            self.cert_csr,\n            \"/CN=\" + self.openssl_env['CN'],\n            self.openssl_conf,\n        )\n        cmd = \"%s req -new -newkey rsa:2048 -keyout %s -out %s -subj %s -sha256 -nodes -batch -config %s\" % cmd_params\n        self.log.debug(\"Generating certificate key and signing request...\")\n        proc = subprocess.Popen(\n            cmd, shell=True, stderr=subprocess.STDOUT,\n            stdout=subprocess.PIPE, env=self.openssl_env\n        )\n        back = proc.stdout.read().strip().decode(errors=\"replace\").replace(\"\\r\", \"\")\n        proc.wait()\n        self.log.debug(\"Running: %s\\n%s\" % (cmd, back))\n\n        # Sign request and generate certificate\n        cmd_params = helper.shellquote(\n            self.openssl_bin,\n            self.cert_csr,\n            self.cacert_pem,\n            self.cakey_pem,\n            self.cert_pem,\n            self.openssl_conf\n        )\n        cmd = \"%s x509 -req -in %s -CA %s -CAkey %s -set_serial 01 -out %s -days 730 -sha256 -extensions x509_ext -extfile %s\" % cmd_params\n        self.log.debug(\"Generating RSA cert...\")\n        proc = subprocess.Popen(\n            cmd, shell=True, stderr=subprocess.STDOUT,\n            stdout=subprocess.PIPE, env=self.openssl_env\n        )\n        back = proc.stdout.read().strip().decode(errors=\"replace\").replace(\"\\r\", \"\")\n        proc.wait()\n        self.log.debug(\"Running: %s\\n%s\" % (cmd, back))\n\n        if os.path.isfile(self.cert_pem) and os.path.isfile(self.key_pem):\n            self.createSslContexts()\n\n            # Remove no longer necessary files\n            os.unlink(self.openssl_conf)\n            os.unlink(self.cacert_pem)\n            os.unlink(self.cakey_pem)\n            os.unlink(self.cert_csr)\n\n            return True\n        else:\n            self.log.error(\"RSA ECC SSL cert generation failed, cert or key files not exist.\")\n\n\nmanager = CryptConnectionManager()\n"
  },
  {
    "path": "src/Crypt/CryptHash.py",
    "content": "import hashlib\nimport os\nimport base64\n\n\ndef sha512sum(file, blocksize=65536, format=\"hexdigest\"):\n    if type(file) is str:  # Filename specified\n        file = open(file, \"rb\")\n    hash = hashlib.sha512()\n    for block in iter(lambda: file.read(blocksize), b\"\"):\n        hash.update(block)\n\n    # Truncate to 256bits is good enough\n    if format == \"hexdigest\":\n        return hash.hexdigest()[0:64]\n    else:\n        return hash.digest()[0:32]\n\n\ndef sha256sum(file, blocksize=65536):\n    if type(file) is str:  # Filename specified\n        file = open(file, \"rb\")\n    hash = hashlib.sha256()\n    for block in iter(lambda: file.read(blocksize), b\"\"):\n        hash.update(block)\n    return hash.hexdigest()\n\n\ndef random(length=64, encoding=\"hex\"):\n    if encoding == \"base64\":  # Characters: A-Za-z0-9\n        hash = hashlib.sha512(os.urandom(256)).digest()\n        return base64.b64encode(hash).decode(\"ascii\").replace(\"+\", \"\").replace(\"/\", \"\").replace(\"=\", \"\")[0:length]\n    else:  # Characters: a-f0-9 (faster)\n        return hashlib.sha512(os.urandom(256)).hexdigest()[0:length]\n\n\n# Sha512 truncated to 256bits\nclass Sha512t:\n    def __init__(self, data):\n        if data:\n            self.sha512 = hashlib.sha512(data)\n        else:\n            self.sha512 = hashlib.sha512()\n\n    def hexdigest(self):\n        return self.sha512.hexdigest()[0:64]\n\n    def digest(self):\n        return self.sha512.digest()[0:32]\n\n    def update(self, data):\n        return self.sha512.update(data)\n\n\ndef sha512t(data=None):\n    return Sha512t(data)\n"
  },
  {
    "path": "src/Crypt/CryptRsa.py",
    "content": "import base64\nimport hashlib\n\ndef sign(data, privatekey):\n    import rsa\n    from rsa import pkcs1\n\n    if \"BEGIN RSA PRIVATE KEY\" not in privatekey:\n        privatekey = \"-----BEGIN RSA PRIVATE KEY-----\\n%s\\n-----END RSA PRIVATE KEY-----\" % privatekey\n\n    priv = rsa.PrivateKey.load_pkcs1(privatekey)\n    sign = rsa.pkcs1.sign(data, priv, 'SHA-256')\n    return sign\n\ndef verify(data, publickey, sign):\n    import rsa\n    from rsa import pkcs1\n\n    pub = rsa.PublicKey.load_pkcs1(publickey, format=\"DER\")\n    try:\n        valid = rsa.pkcs1.verify(data, sign, pub)\n    except pkcs1.VerificationError:\n        valid = False\n    return valid\n\ndef privatekeyToPublickey(privatekey):\n    import rsa\n    from rsa import pkcs1\n\n    if \"BEGIN RSA PRIVATE KEY\" not in privatekey:\n        privatekey = \"-----BEGIN RSA PRIVATE KEY-----\\n%s\\n-----END RSA PRIVATE KEY-----\" % privatekey\n\n    priv = rsa.PrivateKey.load_pkcs1(privatekey)\n    pub = rsa.PublicKey(priv.n, priv.e)\n    return pub.save_pkcs1(\"DER\")\n\ndef publickeyToOnion(publickey):\n    return base64.b32encode(hashlib.sha1(publickey).digest()[:10]).lower().decode(\"ascii\")\n"
  },
  {
    "path": "src/Crypt/__init__.py",
    "content": ""
  },
  {
    "path": "src/Db/Db.py",
    "content": "import sqlite3\nimport json\nimport time\nimport logging\nimport re\nimport os\nimport atexit\nimport threading\nimport sys\nimport weakref\nimport errno\n\nimport gevent\n\nfrom Debug import Debug\nfrom .DbCursor import DbCursor\nfrom util import SafeRe\nfrom util import helper\nfrom util import ThreadPool\nfrom Config import config\n\nthread_pool_db = ThreadPool.ThreadPool(config.threads_db)\n\nnext_db_id = 0\nopened_dbs = []\n\n\n# Close idle databases to save some memory\ndef dbCleanup():\n    while 1:\n        time.sleep(60 * 5)\n        for db in opened_dbs[:]:\n            idle = time.time() - db.last_query_time\n            if idle > 60 * 5 and db.close_idle:\n                db.close(\"Cleanup\")\n\n\ndef dbCommitCheck():\n    while 1:\n        time.sleep(5)\n        for db in opened_dbs[:]:\n            if not db.need_commit:\n                continue\n\n            success = db.commit(\"Interval\")\n            if success:\n                db.need_commit = False\n            time.sleep(0.1)\n\n\ndef dbCloseAll():\n    for db in opened_dbs[:]:\n        db.close(\"Close all\")\n\n\ngevent.spawn(dbCleanup)\ngevent.spawn(dbCommitCheck)\natexit.register(dbCloseAll)\n\n\nclass DbTableError(Exception):\n    def __init__(self, message, table):\n        super().__init__(message)\n        self.table = table\n\n\nclass Db(object):\n\n    def __init__(self, schema, db_path, close_idle=False):\n        global next_db_id\n        self.db_path = db_path\n        self.db_dir = os.path.dirname(db_path) + \"/\"\n        self.schema = schema\n        self.schema[\"version\"] = self.schema.get(\"version\", 1)\n        self.conn = None\n        self.cur = None\n        self.cursors = weakref.WeakSet()\n        self.id = next_db_id\n        next_db_id += 1\n        self.progress_sleeping = False\n        self.commiting = False\n        self.log = logging.getLogger(\"Db#%s:%s\" % (self.id, schema[\"db_name\"]))\n        self.table_names = None\n        self.collect_stats = False\n        self.foreign_keys = False\n        self.need_commit = False\n        self.query_stats = {}\n        self.db_keyvalues = {}\n        self.delayed_queue = []\n        self.delayed_queue_thread = None\n        self.close_idle = close_idle\n        self.last_query_time = time.time()\n        self.last_sleep_time = time.time()\n        self.num_execute_since_sleep = 0\n        self.lock = ThreadPool.Lock()\n        self.connect_lock = ThreadPool.Lock()\n\n    def __repr__(self):\n        return \"<Db#%s:%s close_idle:%s>\" % (id(self), self.db_path, self.close_idle)\n\n    def connect(self):\n        self.connect_lock.acquire(True)\n        try:\n            if self.conn:\n                self.log.debug(\"Already connected, connection ignored\")\n                return\n\n            if self not in opened_dbs:\n                opened_dbs.append(self)\n            s = time.time()\n            try:  # Directory not exist yet\n                os.makedirs(self.db_dir)\n                self.log.debug(\"Created Db path: %s\" % self.db_dir)\n            except OSError as err:\n                if err.errno != errno.EEXIST:\n                    raise err\n            if not os.path.isfile(self.db_path):\n                self.log.debug(\"Db file not exist yet: %s\" % self.db_path)\n            self.conn = sqlite3.connect(self.db_path, isolation_level=\"DEFERRED\", check_same_thread=False)\n            self.conn.row_factory = sqlite3.Row\n            self.conn.set_progress_handler(self.progress, 5000000)\n            self.conn.execute('PRAGMA journal_mode=WAL')\n            if self.foreign_keys:\n                self.conn.execute(\"PRAGMA foreign_keys = ON\")\n            self.cur = self.getCursor()\n\n            self.log.debug(\n                \"Connected to %s in %.3fs (opened: %s, sqlite version: %s)...\" %\n                (self.db_path, time.time() - s, len(opened_dbs), sqlite3.version)\n            )\n            self.log.debug(\"Connect by thread: %s\" % threading.current_thread().ident)\n            self.log.debug(\"Connect called by %s\" % Debug.formatStack())\n        finally:\n            self.connect_lock.release()\n\n    def getConn(self):\n        if not self.conn:\n            self.connect()\n        return self.conn\n\n    def progress(self, *args, **kwargs):\n        self.progress_sleeping = True\n        time.sleep(0.001)\n        self.progress_sleeping = False\n\n    # Execute query using dbcursor\n    def execute(self, query, params=None):\n        if not self.conn:\n            self.connect()\n        return self.cur.execute(query, params)\n\n    @thread_pool_db.wrap\n    def commit(self, reason=\"Unknown\"):\n        if self.progress_sleeping:\n            self.log.debug(\"Commit ignored: Progress sleeping\")\n            return False\n\n        if not self.conn:\n            self.log.debug(\"Commit ignored: No connection\")\n            return False\n\n        if self.commiting:\n            self.log.debug(\"Commit ignored: Already commiting\")\n            return False\n\n        try:\n            s = time.time()\n            self.commiting = True\n            self.conn.commit()\n            self.log.debug(\"Commited in %.3fs (reason: %s)\" % (time.time() - s, reason))\n            return True\n        except Exception as err:\n            if \"SQL statements in progress\" in str(err):\n                self.log.warning(\"Commit delayed: %s (reason: %s)\" % (Debug.formatException(err), reason))\n            else:\n                self.log.error(\"Commit error: %s (reason: %s)\" % (Debug.formatException(err), reason))\n            return False\n        finally:\n            self.commiting = False\n\n    def insertOrUpdate(self, *args, **kwargs):\n        if not self.conn:\n            self.connect()\n        return self.cur.insertOrUpdate(*args, **kwargs)\n\n    def executeDelayed(self, *args, **kwargs):\n        if not self.delayed_queue_thread:\n            self.delayed_queue_thread = gevent.spawn_later(1, self.processDelayed)\n        self.delayed_queue.append((\"execute\", (args, kwargs)))\n\n    def insertOrUpdateDelayed(self, *args, **kwargs):\n        if not self.delayed_queue:\n            gevent.spawn_later(1, self.processDelayed)\n        self.delayed_queue.append((\"insertOrUpdate\", (args, kwargs)))\n\n    def processDelayed(self):\n        if not self.delayed_queue:\n            self.log.debug(\"processDelayed aborted\")\n            return\n        if not self.conn:\n            self.connect()\n\n        s = time.time()\n        cur = self.getCursor()\n        for command, params in self.delayed_queue:\n            if command == \"insertOrUpdate\":\n                cur.insertOrUpdate(*params[0], **params[1])\n            else:\n                cur.execute(*params[0], **params[1])\n\n        if len(self.delayed_queue) > 10:\n            self.log.debug(\"Processed %s delayed queue in %.3fs\" % (len(self.delayed_queue), time.time() - s))\n        self.delayed_queue = []\n        self.delayed_queue_thread = None\n\n    def close(self, reason=\"Unknown\"):\n        if not self.conn:\n            return False\n        self.connect_lock.acquire()\n        s = time.time()\n        if self.delayed_queue:\n            self.processDelayed()\n        if self in opened_dbs:\n            opened_dbs.remove(self)\n        self.need_commit = False\n        self.commit(\"Closing: %s\" % reason)\n        self.log.debug(\"Close called by %s\" % Debug.formatStack())\n        for i in range(5):\n            if len(self.cursors) == 0:\n                break\n            self.log.debug(\"Pending cursors: %s\" % len(self.cursors))\n            time.sleep(0.1 * i)\n        if len(self.cursors):\n            self.log.debug(\"Killing cursors: %s\" % len(self.cursors))\n            self.conn.interrupt()\n\n        if self.cur:\n            self.cur.close()\n        if self.conn:\n            ThreadPool.main_loop.call(self.conn.close)\n        self.conn = None\n        self.cur = None\n        self.log.debug(\"%s closed (reason: %s) in %.3fs, opened: %s\" % (self.db_path, reason, time.time() - s, len(opened_dbs)))\n        self.connect_lock.release()\n        return True\n\n    # Gets a cursor object to database\n    # Return: Cursor class\n    def getCursor(self):\n        if not self.conn:\n            self.connect()\n\n        cur = DbCursor(self)\n        return cur\n\n    def getSharedCursor(self):\n        if not self.conn:\n            self.connect()\n        return self.cur\n\n    # Get the table version\n    # Return: Table version or None if not exist\n    def getTableVersion(self, table_name):\n        if not self.db_keyvalues:  # Get db keyvalues\n            try:\n                res = self.execute(\"SELECT * FROM keyvalue WHERE json_id=0\")  # json_id = 0 is internal keyvalues\n            except sqlite3.OperationalError as err:  # Table not exist\n                self.log.debug(\"Query table version error: %s\" % err)\n                return False\n\n            for row in res:\n                self.db_keyvalues[row[\"key\"]] = row[\"value\"]\n\n        return self.db_keyvalues.get(\"table.%s.version\" % table_name, 0)\n\n    # Check Db tables\n    # Return: <list> Changed table names\n    def checkTables(self):\n        s = time.time()\n        changed_tables = []\n\n        cur = self.getSharedCursor()\n\n        # Check internal tables\n        # Check keyvalue table\n        changed = cur.needTable(\"keyvalue\", [\n            [\"keyvalue_id\", \"INTEGER PRIMARY KEY AUTOINCREMENT\"],\n            [\"key\", \"TEXT\"],\n            [\"value\", \"INTEGER\"],\n            [\"json_id\", \"INTEGER\"],\n        ], [\n            \"CREATE UNIQUE INDEX key_id ON keyvalue(json_id, key)\"\n        ], version=self.schema[\"version\"])\n        if changed:\n            changed_tables.append(\"keyvalue\")\n\n        # Create json table if no custom one defined\n        if \"json\" not in self.schema.get(\"tables\", {}):\n            if self.schema[\"version\"] == 1:\n                changed = cur.needTable(\"json\", [\n                    [\"json_id\", \"INTEGER PRIMARY KEY AUTOINCREMENT\"],\n                    [\"path\", \"VARCHAR(255)\"]\n                ], [\n                    \"CREATE UNIQUE INDEX path ON json(path)\"\n                ], version=self.schema[\"version\"])\n            elif self.schema[\"version\"] == 2:\n                changed = cur.needTable(\"json\", [\n                    [\"json_id\", \"INTEGER PRIMARY KEY AUTOINCREMENT\"],\n                    [\"directory\", \"VARCHAR(255)\"],\n                    [\"file_name\", \"VARCHAR(255)\"]\n                ], [\n                    \"CREATE UNIQUE INDEX path ON json(directory, file_name)\"\n                ], version=self.schema[\"version\"])\n            elif self.schema[\"version\"] == 3:\n                changed = cur.needTable(\"json\", [\n                    [\"json_id\", \"INTEGER PRIMARY KEY AUTOINCREMENT\"],\n                    [\"site\", \"VARCHAR(255)\"],\n                    [\"directory\", \"VARCHAR(255)\"],\n                    [\"file_name\", \"VARCHAR(255)\"]\n                ], [\n                    \"CREATE UNIQUE INDEX path ON json(directory, site, file_name)\"\n                ], version=self.schema[\"version\"])\n            if changed:\n                changed_tables.append(\"json\")\n\n        # Check schema tables\n        for table_name, table_settings in self.schema.get(\"tables\", {}).items():\n            try:\n                indexes = table_settings.get(\"indexes\", [])\n                version = table_settings.get(\"schema_changed\", 0)\n                changed = cur.needTable(\n                    table_name, table_settings[\"cols\"],\n                    indexes, version=version\n                )\n                if changed:\n                    changed_tables.append(table_name)\n            except Exception as err:\n                self.log.error(\"Error creating table %s: %s\" % (table_name, Debug.formatException(err)))\n                raise DbTableError(err, table_name)\n\n        self.log.debug(\"Db check done in %.3fs, changed tables: %s\" % (time.time() - s, changed_tables))\n        if changed_tables:\n            self.db_keyvalues = {}  # Refresh table version cache\n\n        return changed_tables\n\n    # Update json file to db\n    # Return: True if matched\n    def updateJson(self, file_path, file=None, cur=None):\n        if not file_path.startswith(self.db_dir):\n            return False  # Not from the db dir: Skipping\n        relative_path = file_path[len(self.db_dir):]  # File path realative to db file\n\n        # Check if filename matches any of mappings in schema\n        matched_maps = []\n        for match, map_settings in self.schema[\"maps\"].items():\n            try:\n                if SafeRe.match(match, relative_path):\n                    matched_maps.append(map_settings)\n            except SafeRe.UnsafePatternError as err:\n                self.log.error(err)\n\n        # No match found for the file\n        if not matched_maps:\n            return False\n\n        # Load the json file\n        try:\n            if file is None:  # Open file is not file object passed\n                file = open(file_path, \"rb\")\n\n            if file is False:  # File deleted\n                data = {}\n            else:\n                if file_path.endswith(\"json.gz\"):\n                    file = helper.limitedGzipFile(fileobj=file)\n\n                if sys.version_info.major == 3 and sys.version_info.minor < 6:\n                    data = json.loads(file.read().decode(\"utf8\"))\n                else:\n                    data = json.load(file)\n        except Exception as err:\n            self.log.debug(\"Json file %s load error: %s\" % (file_path, err))\n            data = {}\n\n        # No cursor specificed\n        if not cur:\n            cur = self.getSharedCursor()\n            cur.logging = False\n\n        # Row for current json file if required\n        if not data or [dbmap for dbmap in matched_maps if \"to_keyvalue\" in dbmap or \"to_table\" in dbmap]:\n            json_row = cur.getJsonRow(relative_path)\n\n        # Check matched mappings in schema\n        for dbmap in matched_maps:\n            # Insert non-relational key values\n            if dbmap.get(\"to_keyvalue\"):\n                # Get current values\n                res = cur.execute(\"SELECT * FROM keyvalue WHERE json_id = ?\", (json_row[\"json_id\"],))\n                current_keyvalue = {}\n                current_keyvalue_id = {}\n                for row in res:\n                    current_keyvalue[row[\"key\"]] = row[\"value\"]\n                    current_keyvalue_id[row[\"key\"]] = row[\"keyvalue_id\"]\n\n                for key in dbmap[\"to_keyvalue\"]:\n                    if key not in current_keyvalue:  # Keyvalue not exist yet in the db\n                        cur.execute(\n                            \"INSERT INTO keyvalue ?\",\n                            {\"key\": key, \"value\": data.get(key), \"json_id\": json_row[\"json_id\"]}\n                        )\n                    elif data.get(key) != current_keyvalue[key]:  # Keyvalue different value\n                        cur.execute(\n                            \"UPDATE keyvalue SET value = ? WHERE keyvalue_id = ?\",\n                            (data.get(key), current_keyvalue_id[key])\n                        )\n\n            # Insert data to json table for easier joins\n            if dbmap.get(\"to_json_table\"):\n                directory, file_name = re.match(\"^(.*?)/*([^/]*)$\", relative_path).groups()\n                data_json_row = dict(cur.getJsonRow(directory + \"/\" + dbmap.get(\"file_name\", file_name)))\n                changed = False\n                for key in dbmap[\"to_json_table\"]:\n                    if data.get(key) != data_json_row.get(key):\n                        changed = True\n                if changed:\n                    # Add the custom col values\n                    data_json_row.update({key: val for key, val in data.items() if key in dbmap[\"to_json_table\"]})\n                    cur.execute(\"INSERT OR REPLACE INTO json ?\", data_json_row)\n\n            # Insert data to tables\n            for table_settings in dbmap.get(\"to_table\", []):\n                if isinstance(table_settings, dict):  # Custom settings\n                    table_name = table_settings[\"table\"]  # Table name to insert datas\n                    node = table_settings.get(\"node\", table_name)  # Node keyname in data json file\n                    key_col = table_settings.get(\"key_col\")  # Map dict key as this col\n                    val_col = table_settings.get(\"val_col\")  # Map dict value as this col\n                    import_cols = table_settings.get(\"import_cols\")\n                    replaces = table_settings.get(\"replaces\")\n                else:  # Simple settings\n                    table_name = table_settings\n                    node = table_settings\n                    key_col = None\n                    val_col = None\n                    import_cols = None\n                    replaces = None\n\n                # Fill import cols from table cols\n                if not import_cols:\n                    import_cols = set([item[0] for item in self.schema[\"tables\"][table_name][\"cols\"]])\n\n                cur.execute(\"DELETE FROM %s WHERE json_id = ?\" % table_name, (json_row[\"json_id\"],))\n\n                if node not in data:\n                    continue\n\n                if key_col:  # Map as dict\n                    for key, val in data[node].items():\n                        if val_col:  # Single value\n                            cur.execute(\n                                \"INSERT OR REPLACE INTO %s ?\" % table_name,\n                                {key_col: key, val_col: val, \"json_id\": json_row[\"json_id\"]}\n                            )\n                        else:  # Multi value\n                            if type(val) is dict:  # Single row\n                                row = val\n                                if import_cols:\n                                    row = {key: row[key] for key in row if key in import_cols}  # Filter row by import_cols\n                                row[key_col] = key\n                                # Replace in value if necessary\n                                if replaces:\n                                    for replace_key, replace in replaces.items():\n                                        if replace_key in row:\n                                            for replace_from, replace_to in replace.items():\n                                                row[replace_key] = row[replace_key].replace(replace_from, replace_to)\n\n                                row[\"json_id\"] = json_row[\"json_id\"]\n                                cur.execute(\"INSERT OR REPLACE INTO %s ?\" % table_name, row)\n                            elif type(val) is list:  # Multi row\n                                for row in val:\n                                    row[key_col] = key\n                                    row[\"json_id\"] = json_row[\"json_id\"]\n                                    cur.execute(\"INSERT OR REPLACE INTO %s ?\" % table_name, row)\n                else:  # Map as list\n                    for row in data[node]:\n                        row[\"json_id\"] = json_row[\"json_id\"]\n                        if import_cols:\n                            row = {key: row[key] for key in row if key in import_cols}  # Filter row by import_cols\n                        cur.execute(\"INSERT OR REPLACE INTO %s ?\" % table_name, row)\n\n        # Cleanup json row\n        if not data:\n            self.log.debug(\"Cleanup json row for %s\" % file_path)\n            cur.execute(\"DELETE FROM json WHERE json_id = %s\" % json_row[\"json_id\"])\n\n        return True\n\n\nif __name__ == \"__main__\":\n    s = time.time()\n    console_log = logging.StreamHandler()\n    logging.getLogger('').setLevel(logging.DEBUG)\n    logging.getLogger('').addHandler(console_log)\n    console_log.setLevel(logging.DEBUG)\n    dbjson = Db(json.load(open(\"zerotalk.schema.json\")), \"data/users/zerotalk.db\")\n    dbjson.collect_stats = True\n    dbjson.checkTables()\n    cur = dbjson.getCursor()\n    cur.logging = False\n    dbjson.updateJson(\"data/users/content.json\", cur=cur)\n    for user_dir in os.listdir(\"data/users\"):\n        if os.path.isdir(\"data/users/%s\" % user_dir):\n            dbjson.updateJson(\"data/users/%s/data.json\" % user_dir, cur=cur)\n            # print \".\",\n    cur.logging = True\n    print(\"Done in %.3fs\" % (time.time() - s))\n    for query, stats in sorted(dbjson.query_stats.items()):\n        print(\"-\", query, stats)\n"
  },
  {
    "path": "src/Db/DbCursor.py",
    "content": "import time\nimport re\nfrom util import helper\n\n# Special sqlite cursor\n\n\nclass DbCursor:\n\n    def __init__(self, db):\n        self.db = db\n        self.logging = False\n\n    def quoteValue(self, value):\n        if type(value) is int:\n            return str(value)\n        else:\n            return \"'%s'\" % value.replace(\"'\", \"''\")\n\n    def parseQuery(self, query, params):\n        query_type = query.split(\" \", 1)[0].upper()\n        if isinstance(params, dict) and \"?\" in query:  # Make easier select and insert by allowing dict params\n            if query_type in (\"SELECT\", \"DELETE\", \"UPDATE\"):\n                # Convert param dict to SELECT * FROM table WHERE key = ? AND key2 = ? format\n                query_wheres = []\n                values = []\n                for key, value in params.items():\n                    if type(value) is list:\n                        if key.startswith(\"not__\"):\n                            field = key.replace(\"not__\", \"\")\n                            operator = \"NOT IN\"\n                        else:\n                            field = key\n                            operator = \"IN\"\n                        if len(value) > 100:\n                            # Embed values in query to avoid \"too many SQL variables\" error\n                            query_values = \",\".join(map(helper.sqlquote, value))\n                        else:\n                            query_values = \",\".join([\"?\"] * len(value))\n                            values += value\n                        query_wheres.append(\n                            \"%s %s (%s)\" %\n                            (field, operator, query_values)\n                        )\n                    else:\n                        if key.startswith(\"not__\"):\n                            query_wheres.append(key.replace(\"not__\", \"\") + \" != ?\")\n                        elif key.endswith(\"__like\"):\n                            query_wheres.append(key.replace(\"__like\", \"\") + \" LIKE ?\")\n                        elif key.endswith(\">\"):\n                            query_wheres.append(key.replace(\">\", \"\") + \" > ?\")\n                        elif key.endswith(\"<\"):\n                            query_wheres.append(key.replace(\"<\", \"\") + \" < ?\")\n                        else:\n                            query_wheres.append(key + \" = ?\")\n                        values.append(value)\n                wheres = \" AND \".join(query_wheres)\n                if wheres == \"\":\n                    wheres = \"1\"\n                query = re.sub(\"(.*)[?]\", \"\\\\1 %s\" % wheres, query)  # Replace the last ?\n                params = values\n            else:\n                # Convert param dict to INSERT INTO table (key, key2) VALUES (?, ?) format\n                keys = \", \".join(params.keys())\n                values = \", \".join(['?' for key in params.keys()])\n                keysvalues = \"(%s) VALUES (%s)\" % (keys, values)\n                query = re.sub(\"(.*)[?]\", \"\\\\1%s\" % keysvalues, query)  # Replace the last ?\n                params = tuple(params.values())\n        elif isinstance(params, dict) and \":\" in query:\n            new_params = dict()\n            values = []\n            for key, value in params.items():\n                if type(value) is list:\n                    for idx, val in enumerate(value):\n                        new_params[key + \"__\" + str(idx)] = val\n\n                    new_names = [\":\" + key + \"__\" + str(idx) for idx in range(len(value))]\n                    query = re.sub(r\":\" + re.escape(key) + r\"([)\\s]|$)\", \"(%s)%s\" % (\", \".join(new_names), r\"\\1\"), query)\n                else:\n                    new_params[key] = value\n\n            params = new_params\n        return query, params\n\n    def execute(self, query, params=None):\n        query = query.strip()\n        while self.db.progress_sleeping or self.db.commiting:\n            time.sleep(0.1)\n\n        self.db.last_query_time = time.time()\n\n        query, params = self.parseQuery(query, params)\n\n        cursor = self.db.getConn().cursor()\n        self.db.cursors.add(cursor)\n        if self.db.lock.locked():\n            self.db.log.debug(\"Locked for %.3fs\" % (time.time() - self.db.lock.time_lock))\n\n        try:\n            s = time.time()\n            self.db.lock.acquire(True)\n            if query.upper().strip(\"; \") == \"VACUUM\":\n                self.db.commit(\"vacuum called\")\n            if params:\n                res = cursor.execute(query, params)\n            else:\n                res = cursor.execute(query)\n        finally:\n            self.db.lock.release()\n\n        taken_query = time.time() - s\n        if self.logging or taken_query > 1:\n            if params:  # Query has parameters\n                self.db.log.debug(\"Query: \" + query + \" \" + str(params) + \" (Done in %.4f)\" % (time.time() - s))\n            else:\n                self.db.log.debug(\"Query: \" + query + \" (Done in %.4f)\" % (time.time() - s))\n\n        # Log query stats\n        if self.db.collect_stats:\n            if query not in self.db.query_stats:\n                self.db.query_stats[query] = {\"call\": 0, \"time\": 0.0}\n            self.db.query_stats[query][\"call\"] += 1\n            self.db.query_stats[query][\"time\"] += time.time() - s\n\n        query_type = query.split(\" \", 1)[0].upper()\n        is_update_query = query_type in [\"UPDATE\", \"DELETE\", \"INSERT\", \"CREATE\"]\n        if not self.db.need_commit and is_update_query:\n            self.db.need_commit = True\n\n        if is_update_query:\n            return cursor\n        else:\n            return res\n\n    def executemany(self, query, params):\n        while self.db.progress_sleeping or self.db.commiting:\n            time.sleep(0.1)\n\n        self.db.last_query_time = time.time()\n\n        s = time.time()\n        cursor = self.db.getConn().cursor()\n        self.db.cursors.add(cursor)\n\n        try:\n            self.db.lock.acquire(True)\n            cursor.executemany(query, params)\n        finally:\n            self.db.lock.release()\n\n        taken_query = time.time() - s\n        if self.logging or taken_query > 0.1:\n            self.db.log.debug(\"Execute many: %s (Done in %.4f)\" % (query, taken_query))\n\n        self.db.need_commit = True\n\n        return cursor\n\n    # Creates on updates a database row without incrementing the rowid\n    def insertOrUpdate(self, table, query_sets, query_wheres, oninsert={}):\n        sql_sets = [\"%s = :%s\" % (key, key) for key in query_sets.keys()]\n        sql_wheres = [\"%s = :%s\" % (key, key) for key in query_wheres.keys()]\n\n        params = query_sets\n        params.update(query_wheres)\n        res = self.execute(\n            \"UPDATE %s SET %s WHERE %s\" % (table, \", \".join(sql_sets), \" AND \".join(sql_wheres)),\n            params\n        )\n        if res.rowcount == 0:\n            params.update(oninsert)  # Add insert-only fields\n            self.execute(\"INSERT INTO %s ?\" % table, params)\n\n    # Create new table\n    # Return: True on success\n    def createTable(self, table, cols):\n        # TODO: Check current structure\n        self.execute(\"DROP TABLE IF EXISTS %s\" % table)\n        col_definitions = []\n        for col_name, col_type in cols:\n            col_definitions.append(\"%s %s\" % (col_name, col_type))\n\n        self.execute(\"CREATE TABLE %s (%s)\" % (table, \",\".join(col_definitions)))\n        return True\n\n    # Create indexes on table\n    # Return: True on success\n    def createIndexes(self, table, indexes):\n        for index in indexes:\n            if not index.strip().upper().startswith(\"CREATE\"):\n                self.db.log.error(\"Index command should start with CREATE: %s\" % index)\n                continue\n            self.execute(index)\n\n    # Create table if not exist\n    # Return: True if updated\n    def needTable(self, table, cols, indexes=None, version=1):\n        current_version = self.db.getTableVersion(table)\n        if int(current_version) < int(version):  # Table need update or not extis\n            self.db.log.debug(\"Table %s outdated...version: %s need: %s, rebuilding...\" % (table, current_version, version))\n            self.createTable(table, cols)\n            if indexes:\n                self.createIndexes(table, indexes)\n            self.execute(\n                \"INSERT OR REPLACE INTO keyvalue ?\",\n                {\"json_id\": 0, \"key\": \"table.%s.version\" % table, \"value\": version}\n            )\n            return True\n        else:  # Not changed\n            return False\n\n    # Get or create a row for json file\n    # Return: The database row\n    def getJsonRow(self, file_path):\n        directory, file_name = re.match(\"^(.*?)/*([^/]*)$\", file_path).groups()\n        if self.db.schema[\"version\"] == 1:\n            # One path field\n            res = self.execute(\"SELECT * FROM json WHERE ? LIMIT 1\", {\"path\": file_path})\n            row = res.fetchone()\n            if not row:  # No row yet, create it\n                self.execute(\"INSERT INTO json ?\", {\"path\": file_path})\n                res = self.execute(\"SELECT * FROM json WHERE ? LIMIT 1\", {\"path\": file_path})\n                row = res.fetchone()\n        elif self.db.schema[\"version\"] == 2:\n            # Separate directory, file_name (easier join)\n            res = self.execute(\"SELECT * FROM json WHERE ? LIMIT 1\", {\"directory\": directory, \"file_name\": file_name})\n            row = res.fetchone()\n            if not row:  # No row yet, create it\n                self.execute(\"INSERT INTO json ?\", {\"directory\": directory, \"file_name\": file_name})\n                res = self.execute(\"SELECT * FROM json WHERE ? LIMIT 1\", {\"directory\": directory, \"file_name\": file_name})\n                row = res.fetchone()\n        elif self.db.schema[\"version\"] == 3:\n            # Separate site, directory, file_name (for merger sites)\n            site_address, directory = re.match(\"^([^/]*)/(.*)$\", directory).groups()\n            res = self.execute(\"SELECT * FROM json WHERE ? LIMIT 1\", {\"site\": site_address, \"directory\": directory, \"file_name\": file_name})\n            row = res.fetchone()\n            if not row:  # No row yet, create it\n                self.execute(\"INSERT INTO json ?\", {\"site\": site_address, \"directory\": directory, \"file_name\": file_name})\n                res = self.execute(\"SELECT * FROM json WHERE ? LIMIT 1\", {\"site\": site_address, \"directory\": directory, \"file_name\": file_name})\n                row = res.fetchone()\n        else:\n            raise Exception(\"Dbschema version %s not supported\" % self.db.schema.get(\"version\"))\n        return row\n\n    def close(self):\n        pass\n"
  },
  {
    "path": "src/Db/DbQuery.py",
    "content": "import re\n\n\n# Parse and modify sql queries\nclass DbQuery:\n    def __init__(self, query):\n        self.setQuery(query.strip())\n\n    # Split main parts of query\n    def parseParts(self, query):\n        parts = re.split(\"(SELECT|FROM|WHERE|ORDER BY|LIMIT)\", query)\n        parts = [_f for _f in parts if _f]  # Remove empty parts\n        parts = [s.strip() for s in parts]  # Remove whitespace\n        return dict(list(zip(parts[0::2], parts[1::2])))\n\n    # Parse selected fields SELECT ... FROM\n    def parseFields(self, query_select):\n        fields = re.findall(\"([^,]+) AS ([^,]+)\", query_select)\n        return {key: val.strip() for val, key in fields}\n\n    # Parse query conditions WHERE ...\n    def parseWheres(self, query_where):\n        if \" AND \" in query_where:\n            return query_where.split(\" AND \")\n        elif query_where:\n            return [query_where]\n        else:\n            return []\n\n    # Set the query\n    def setQuery(self, query):\n        self.parts = self.parseParts(query)\n        self.fields = self.parseFields(self.parts[\"SELECT\"])\n        self.wheres = self.parseWheres(self.parts.get(\"WHERE\", \"\"))\n\n    # Convert query back to string\n    def __str__(self):\n        query_parts = []\n        for part_name in [\"SELECT\", \"FROM\", \"WHERE\", \"ORDER BY\", \"LIMIT\"]:\n            if part_name == \"WHERE\" and self.wheres:\n                query_parts.append(\"WHERE\")\n                query_parts.append(\" AND \".join(self.wheres))\n            elif part_name in self.parts:\n                query_parts.append(part_name)\n                query_parts.append(self.parts[part_name])\n        return \"\\n\".join(query_parts)\n"
  },
  {
    "path": "src/Db/__init__.py",
    "content": ""
  },
  {
    "path": "src/Debug/Debug.py",
    "content": "import sys\nimport os\nimport re\nfrom Config import config\n\n\n# Non fatal exception\nclass Notify(Exception):\n    def __init__(self, message=None):\n        if message:\n            self.message = message\n\n    def __str__(self):\n        return self.message\n\n\n# Gevent greenlet.kill accept Exception type\ndef createNotifyType(message):\n    return type(\"Notify\", (Notify, ), {\"message\": message})\n\n\ndef formatExceptionMessage(err):\n    err_type = err.__class__.__name__\n    if err.args:\n        err_message = err.args[-1]\n    else:\n        err_message = err.__str__()\n    return \"%s: %s\" % (err_type, err_message)\n\n\npython_lib_dirs = [path.replace(\"\\\\\", \"/\") for path in sys.path if re.sub(r\".*[\\\\/]\", \"\", path) in (\"site-packages\", \"dist-packages\")]\npython_lib_dirs.append(os.path.dirname(os.__file__).replace(\"\\\\\", \"/\"))  # TODO: check if returns the correct path for PyPy\n\nroot_dir = os.path.realpath(os.path.dirname(__file__) + \"/../../\")\nroot_dir = root_dir.replace(\"\\\\\", \"/\")\n\n\ndef formatTraceback(items, limit=None, fold_builtin=True):\n    back = []\n    i = 0\n    prev_file_title = \"\"\n    is_prev_builtin = False\n\n    for path, line in items:\n        i += 1\n        is_last = i == len(items)\n        path = path.replace(\"\\\\\", \"/\")\n\n        if path.startswith(\"src/gevent/\"):\n            file_title = \"<gevent>/\" + path[len(\"src/gevent/\"):]\n            is_builtin = True\n            is_skippable_builtin = False\n        elif path in (\"<frozen importlib._bootstrap>\", \"<frozen importlib._bootstrap_external>\"):\n            file_title = \"(importlib)\"\n            is_builtin = True\n            is_skippable_builtin = True\n        else:\n            is_skippable_builtin = False\n            for base in python_lib_dirs:\n                if path.startswith(base + \"/\"):\n                    file_title = path[len(base + \"/\"):]\n                    module_name, *tail = file_title.split(\"/\")\n                    if module_name.endswith(\".py\"):\n                        module_name = module_name[:-3]\n                    file_title = \"/\".join([\"<%s>\" % module_name] + tail)\n                    is_builtin = True\n                    break\n            else:\n                is_builtin = False\n                for base in (root_dir + \"/src\", root_dir + \"/plugins\", root_dir):\n                    if path.startswith(base + \"/\"):\n                        file_title = path[len(base + \"/\"):]\n                        break\n                else:\n                    # For unknown paths, do our best to hide absolute path\n                    file_title = path\n                    for needle in (\"/zeronet/\", \"/core/\"):\n                        if needle in file_title.lower():\n                            file_title = \"?/\" + file_title[file_title.lower().rindex(needle) + len(needle):]\n\n        # Path compression: A/AB/ABC/X/Y.py -> ABC/X/Y.py\n        # E.g.: in 'Db/DbCursor.py' the directory part is unnecessary\n        if not file_title.startswith(\"/\"):\n            prev_part = \"\"\n            for i, part in enumerate(file_title.split(\"/\") + [\"\"]):\n                if not part.startswith(prev_part):\n                    break\n                prev_part = part\n            file_title = \"/\".join(file_title.split(\"/\")[i - 1:])\n\n        if is_skippable_builtin and fold_builtin:\n            pass\n        elif is_builtin and is_prev_builtin and not is_last and fold_builtin:\n            if back[-1] != \"...\":\n                back.append(\"...\")\n        else:\n            if file_title == prev_file_title:\n                back.append(\"%s\" % line)\n            else:\n                back.append(\"%s line %s\" % (file_title, line))\n\n        prev_file_title = file_title\n        is_prev_builtin = is_builtin\n\n        if limit and i >= limit:\n            back.append(\"...\")\n            break\n    return back\n\n\ndef formatException(err=None, format=\"text\"):\n    import traceback\n    if type(err) == Notify:\n        return err\n    elif type(err) == tuple and err and err[0] is not None:  # Passed trackeback info\n        exc_type, exc_obj, exc_tb = err\n        err = None\n    else:  # No trackeback info passed, get latest\n        exc_type, exc_obj, exc_tb = sys.exc_info()\n\n    if not err:\n        if hasattr(err, \"message\"):\n            err = exc_obj.message\n        else:\n            err = exc_obj\n\n    tb = formatTraceback([[frame[0], frame[1]] for frame in traceback.extract_tb(exc_tb)])\n    if format == \"html\":\n        return \"%s: %s<br><small class='multiline'>%s</small>\" % (repr(err), err, \" > \".join(tb))\n    else:\n        return \"%s: %s in %s\" % (exc_type.__name__, err, \" > \".join(tb))\n\n\ndef formatStack(limit=None):\n    import inspect\n    tb = formatTraceback([[frame[1], frame[2]] for frame in inspect.stack()[1:]], limit=limit)\n    return \" > \".join(tb)\n\n\n# Test if gevent eventloop blocks\nimport logging\nimport gevent\nimport time\n\n\nnum_block = 0\n\n\ndef testBlock():\n    global num_block\n    logging.debug(\"Gevent block checker started\")\n    last_time = time.time()\n    while 1:\n        time.sleep(1)\n        if time.time() - last_time > 1.1:\n            logging.debug(\"Gevent block detected: %.3fs\" % (time.time() - last_time - 1))\n            num_block += 1\n        last_time = time.time()\n\n\ngevent.spawn(testBlock)\n\n\nif __name__ == \"__main__\":\n    try:\n        print(1 / 0)\n    except Exception as err:\n        print(type(err).__name__)\n        print(\"1/0 error: %s\" % formatException(err))\n\n    def loadJson():\n        json.loads(\"Errr\")\n\n    import json\n    try:\n        loadJson()\n    except Exception as err:\n        print(err)\n        print(\"Json load error: %s\" % formatException(err))\n\n    try:\n        raise Notify(\"nothing...\")\n    except Exception as err:\n        print(\"Notify: %s\" % formatException(err))\n\n    loadJson()\n"
  },
  {
    "path": "src/Debug/DebugHook.py",
    "content": "import sys\nimport logging\nimport signal\nimport importlib\n\nimport gevent\nimport gevent.hub\n\nfrom Config import config\nfrom . import Debug\n\nlast_error = None\n\ndef shutdown(reason=\"Unknown\"):\n    logging.info(\"Shutting down (reason: %s)...\" % reason)\n    import main\n    if \"file_server\" in dir(main):\n        try:\n            gevent.spawn(main.file_server.stop)\n            if \"ui_server\" in dir(main):\n                gevent.spawn(main.ui_server.stop)\n        except Exception as err:\n            print(\"Proper shutdown error: %s\" % err)\n            sys.exit(0)\n    else:\n        sys.exit(0)\n\n# Store last error, ignore notify, allow manual error logging\ndef handleError(*args, **kwargs):\n    global last_error\n    if not args:  # Manual called\n        args = sys.exc_info()\n        silent = True\n    else:\n        silent = False\n    if args[0].__name__ != \"Notify\":\n        last_error = args\n\n    if args[0].__name__ == \"KeyboardInterrupt\":\n        shutdown(\"Keyboard interrupt\")\n    elif not silent and args[0].__name__ != \"Notify\":\n        logging.exception(\"Unhandled exception\")\n        if \"greenlet.py\" not in args[2].tb_frame.f_code.co_filename:  # Don't display error twice\n            sys.__excepthook__(*args, **kwargs)\n\n\n# Ignore notify errors\ndef handleErrorNotify(*args, **kwargs):\n    err = args[0]\n    if err.__name__ == \"KeyboardInterrupt\":\n        shutdown(\"Keyboard interrupt\")\n    elif err.__name__ != \"Notify\":\n        logging.error(\"Unhandled exception: %s\" % Debug.formatException(args))\n        sys.__excepthook__(*args, **kwargs)\n\n\nif config.debug:  # Keep last error for /Debug\n    sys.excepthook = handleError\nelse:\n    sys.excepthook = handleErrorNotify\n\n\n# Override default error handler to allow silent killing / custom logging\nif \"handle_error\" in dir(gevent.hub.Hub):\n    gevent.hub.Hub._original_handle_error = gevent.hub.Hub.handle_error\nelse:\n    logging.debug(\"gevent.hub.Hub.handle_error not found using old gevent hooks\")\n    OriginalGreenlet = gevent.Greenlet\n    class ErrorhookedGreenlet(OriginalGreenlet):\n        def _report_error(self, exc_info):\n            sys.excepthook(exc_info[0], exc_info[1], exc_info[2])\n\n    gevent.Greenlet = gevent.greenlet.Greenlet = ErrorhookedGreenlet\n    importlib.reload(gevent)\n\ndef handleGreenletError(context, type, value, tb):\n    if context.__class__ is tuple and context[0].__class__.__name__ == \"ThreadPool\":\n        # Exceptions in ThreadPool will be handled in the main Thread\n        return None\n\n    if isinstance(value, str):\n        # Cython can raise errors where the value is a plain string\n        # e.g., AttributeError, \"_semaphore.Semaphore has no attr\", <traceback>\n        value = type(value)\n\n    if not issubclass(type, gevent.get_hub().NOT_ERROR):\n        sys.excepthook(type, value, tb)\n\ngevent.get_hub().handle_error = handleGreenletError\n\ntry:\n    signal.signal(signal.SIGTERM, lambda signum, stack_frame: shutdown(\"SIGTERM\"))\nexcept Exception as err:\n    logging.debug(\"Error setting up SIGTERM watcher: %s\" % err)\n\n\nif __name__ == \"__main__\":\n    import time\n    from gevent import monkey\n    monkey.patch_all(thread=False, ssl=False)\n    from . import Debug\n\n    def sleeper(num):\n        print(\"started\", num)\n        time.sleep(3)\n        raise Exception(\"Error\")\n        print(\"stopped\", num)\n    thread1 = gevent.spawn(sleeper, 1)\n    thread2 = gevent.spawn(sleeper, 2)\n    time.sleep(1)\n    print(\"killing...\")\n    thread1.kill(exception=Debug.Notify(\"Worker stopped\"))\n    #thread2.throw(Debug.Notify(\"Throw\"))\n    print(\"killed\")\n    gevent.joinall([thread1,thread2])\n"
  },
  {
    "path": "src/Debug/DebugLock.py",
    "content": "import time\nimport logging\n\nimport gevent.lock\n\nfrom Debug import Debug\n\n\nclass DebugLock:\n    def __init__(self, log_after=0.01, name=\"Lock\"):\n        self.name = name\n        self.log_after = log_after\n        self.lock = gevent.lock.Semaphore(1)\n        self.release = self.lock.release\n\n    def acquire(self, *args, **kwargs):\n        s = time.time()\n        res = self.lock.acquire(*args, **kwargs)\n        time_taken = time.time() - s\n        if time_taken >= self.log_after:\n            logging.debug(\"%s: Waited %.3fs after called by %s\" %\n                (self.name, time_taken, Debug.formatStack())\n            )\n        return res\n"
  },
  {
    "path": "src/Debug/DebugMedia.py",
    "content": "import os\nimport subprocess\nimport re\nimport logging\nimport time\nimport functools\n\nfrom Config import config\nfrom util import helper\n\n\n# Find files with extension in path\ndef findfiles(path, find_ext):\n    def sorter(f1, f2):\n        f1 = f1[0].replace(path, \"\")\n        f2 = f2[0].replace(path, \"\")\n        if f1 == \"\":\n            return 1\n        elif f2 == \"\":\n            return -1\n        else:\n            return helper.cmp(f1.lower(), f2.lower())\n\n    for root, dirs, files in sorted(os.walk(path, topdown=False), key=functools.cmp_to_key(sorter)):\n        for file in sorted(files):\n            file_path = root + \"/\" + file\n            file_ext = file.split(\".\")[-1]\n            if file_ext in find_ext and not file.startswith(\"all.\"):\n                yield file_path.replace(\"\\\\\", \"/\")\n\n\n# Try to find coffeescript compiler in path\ndef findCoffeescriptCompiler():\n    coffeescript_compiler = None\n    try:\n        import distutils.spawn\n        coffeescript_compiler = helper.shellquote(distutils.spawn.find_executable(\"coffee\")) + \" --no-header -p\"\n    except:\n        pass\n    if coffeescript_compiler:\n        return coffeescript_compiler\n    else:\n        return False\n\n\n# Generates: all.js: merge *.js, compile coffeescript, all.css: merge *.css, vendor prefix features\ndef merge(merged_path):\n    merged_path = merged_path.replace(\"\\\\\", \"/\")\n    merge_dir = os.path.dirname(merged_path)\n    s = time.time()\n    ext = merged_path.split(\".\")[-1]\n    if ext == \"js\":  # If merging .js find .coffee too\n        find_ext = [\"js\", \"coffee\"]\n    else:\n        find_ext = [ext]\n\n    # If exist check the other files modification date\n    if os.path.isfile(merged_path):\n        merged_mtime = os.path.getmtime(merged_path)\n    else:\n        merged_mtime = 0\n\n    changed = {}\n    for file_path in findfiles(merge_dir, find_ext):\n        if os.path.getmtime(file_path) > merged_mtime + 1:\n            changed[file_path] = True\n    if not changed:\n        return  # Assets not changed, nothing to do\n\n    old_parts = {}\n    if os.path.isfile(merged_path):  # Find old parts to avoid unncessary recompile\n        merged_old = open(merged_path, \"rb\").read()\n        for match in re.findall(rb\"(/\\* ---- (.*?) ---- \\*/(.*?)(?=/\\* ----|$))\", merged_old, re.DOTALL):\n            old_parts[match[1].decode()] = match[2].strip(b\"\\n\\r\")\n\n    logging.debug(\"Merging %s (changed: %s, old parts: %s)\" % (merged_path, changed, len(old_parts)))\n    # Merge files\n    parts = []\n    s_total = time.time()\n    for file_path in findfiles(merge_dir, find_ext):\n        file_relative_path = file_path.replace(merge_dir + \"/\", \"\")\n        parts.append(b\"\\n/* ---- %s ---- */\\n\\n\" % file_relative_path.encode(\"utf8\"))\n        if file_path.endswith(\".coffee\"):  # Compile coffee script\n            if file_path in changed or file_relative_path not in old_parts:  # Only recompile if changed or its not compiled before\n                if config.coffeescript_compiler is None:\n                    config.coffeescript_compiler = findCoffeescriptCompiler()\n                if not config.coffeescript_compiler:\n                    logging.error(\"No coffeescript compiler defined, skipping compiling %s\" % merged_path)\n                    return False  # No coffeescript compiler, skip this file\n\n                # Replace / with os separators and escape it\n                file_path_escaped = helper.shellquote(file_path.replace(\"/\", os.path.sep))\n\n                if \"%s\" in config.coffeescript_compiler:  # Replace %s with coffeescript file\n                    command = config.coffeescript_compiler.replace(\"%s\", file_path_escaped)\n                else:  # Put coffeescript file to end\n                    command = config.coffeescript_compiler + \" \" + file_path_escaped\n\n                # Start compiling\n                s = time.time()\n                compiler = subprocess.Popen(command, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)\n                out = compiler.stdout.read()\n                compiler.wait()\n                logging.debug(\"Running: %s (Done in %.2fs)\" % (command, time.time() - s))\n\n                # Check errors\n                if out and out.startswith(b\"(\"):  # No error found\n                    parts.append(out)\n                else:  # Put error message in place of source code\n                    error = out\n                    logging.error(\"%s Compile error: %s\" % (file_relative_path, error))\n                    error_escaped = re.escape(error).replace(b\"\\n\", b\"\\\\n\").replace(br\"\\\\n\", br\"\\n\")\n                    parts.append(\n                        b\"alert('%s compile error: %s');\" %\n                        (file_relative_path.encode(), error_escaped)\n                    )\n            else:  # Not changed use the old_part\n                parts.append(old_parts[file_relative_path])\n        else:  # Add to parts\n            parts.append(open(file_path, \"rb\").read())\n\n    merged = b\"\\n\".join(parts)\n    if ext == \"css\":  # Vendor prefix css\n        from lib.cssvendor import cssvendor\n        merged = cssvendor.prefix(merged)\n    merged = merged.replace(b\"\\r\", b\"\")\n    open(merged_path, \"wb\").write(merged)\n    logging.debug(\"Merged %s (%.2fs)\" % (merged_path, time.time() - s_total))\n\n\nif __name__ == \"__main__\":\n    logging.getLogger().setLevel(logging.DEBUG)\n    os.chdir(\"..\")\n    config.coffeescript_compiler = r'type \"%s\" | tools\\coffee-node\\bin\\node.exe tools\\coffee-node\\bin\\coffee --no-header -s -p'\n    merge(\"data/12Hw8rTgzrNo4DSh2AkqwPRqDyTticwJyH/js/all.js\")\n"
  },
  {
    "path": "src/Debug/DebugReloader.py",
    "content": "import logging\nimport time\nimport os\n\nfrom Config import config\n\nif config.debug and config.action == \"main\":\n    try:\n        import watchdog\n        import watchdog.observers\n        import watchdog.events\n        logging.debug(\"Watchdog fs listener detected, source code autoreload enabled\")\n        enabled = True\n    except Exception as err:\n        logging.debug(\"Watchdog fs listener could not be loaded: %s\" % err)\n        enabled = False\nelse:\n    enabled = False\n\n\nclass DebugReloader:\n    def __init__(self, paths=None):\n        if not paths:\n            paths = [\"src\", \"plugins\", config.data_dir + \"/__plugins__\"]\n        self.log = logging.getLogger(\"DebugReloader\")\n        self.last_chaged = 0\n        self.callbacks = []\n        if enabled:\n            self.observer = watchdog.observers.Observer()\n            event_handler = watchdog.events.FileSystemEventHandler()\n            event_handler.on_modified = event_handler.on_deleted = self.onChanged\n            event_handler.on_created = event_handler.on_moved = self.onChanged\n            for path in paths:\n                if not os.path.isdir(path):\n                    continue\n                self.log.debug(\"Adding autoreload: %s\" % path)\n                self.observer.schedule(event_handler, path, recursive=True)\n            self.observer.start()\n\n    def addCallback(self, f):\n        self.callbacks.append(f)\n\n    def onChanged(self, evt):\n        path = evt.src_path\n        ext = path.rsplit(\".\", 1)[-1]\n        if ext not in [\"py\", \"json\"] or \"Test\" in path or time.time() - self.last_chaged < 1.0:\n            return False\n        self.last_chaged = time.time()\n        if os.path.isfile(path):\n            time_modified = os.path.getmtime(path)\n        else:\n            time_modified = 0\n        self.log.debug(\"File changed: %s reloading source code (modified %.3fs ago)\" % (evt, time.time() - time_modified))\n        if time.time() - time_modified > 5:  # Probably it's just an attribute change, ignore it\n            return False\n\n        time.sleep(0.1)  # Wait for lock release\n        for callback in self.callbacks:\n            try:\n                callback()\n            except Exception as err:\n                self.log.exception(err)\n\n    def stop(self):\n        if enabled:\n            self.observer.stop()\n            self.log.debug(\"Stopped autoreload observer\")\n\nwatcher = DebugReloader()\n"
  },
  {
    "path": "src/Debug/__init__.py",
    "content": ""
  },
  {
    "path": "src/File/FileRequest.py",
    "content": "# Included modules\nimport os\nimport time\nimport json\nimport collections\nimport itertools\n\n# Third party modules\nimport gevent\n\nfrom Debug import Debug\nfrom Config import config\nfrom util import RateLimit\nfrom util import Msgpack\nfrom util import helper\nfrom Plugin import PluginManager\nfrom contextlib import closing\n\nFILE_BUFF = 1024 * 512\n\n\nclass RequestError(Exception):\n    pass\n\n\n# Incoming requests\n@PluginManager.acceptPlugins\nclass FileRequest(object):\n    __slots__ = (\"server\", \"connection\", \"req_id\", \"sites\", \"log\", \"responded\")\n\n    def __init__(self, server, connection):\n        self.server = server\n        self.connection = connection\n\n        self.req_id = None\n        self.sites = self.server.sites\n        self.log = server.log\n        self.responded = False  # Responded to the request\n\n    def send(self, msg, streaming=False):\n        if not self.connection.closed:\n            self.connection.send(msg, streaming)\n\n    def sendRawfile(self, file, read_bytes):\n        if not self.connection.closed:\n            self.connection.sendRawfile(file, read_bytes)\n\n    def response(self, msg, streaming=False):\n        if self.responded:\n            if config.verbose:\n                self.log.debug(\"Req id %s already responded\" % self.req_id)\n            return\n        if not isinstance(msg, dict):  # If msg not a dict create a {\"body\": msg}\n            msg = {\"body\": msg}\n        msg[\"cmd\"] = \"response\"\n        msg[\"to\"] = self.req_id\n        self.responded = True\n        self.send(msg, streaming=streaming)\n\n    # Route file requests\n    def route(self, cmd, req_id, params):\n        self.req_id = req_id\n        # Don't allow other sites than locked\n        if \"site\" in params and self.connection.target_onion:\n            valid_sites = self.connection.getValidSites()\n            if params[\"site\"] not in valid_sites and valid_sites != [\"global\"]:\n                self.response({\"error\": \"Invalid site\"})\n                self.connection.log(\n                    \"Site lock violation: %s not in %s, target onion: %s\" %\n                    (params[\"site\"], valid_sites, self.connection.target_onion)\n                )\n                self.connection.badAction(5)\n                return False\n\n        if cmd == \"update\":\n            event = \"%s update %s %s\" % (self.connection.id, params[\"site\"], params[\"inner_path\"])\n            # If called more than once within 15 sec only keep the last update\n            RateLimit.callAsync(event, max(self.connection.bad_actions, 15), self.actionUpdate, params)\n        else:\n            func_name = \"action\" + cmd[0].upper() + cmd[1:]\n            func = getattr(self, func_name, None)\n            if cmd not in [\"getFile\", \"streamFile\"]:  # Skip IO bound functions\n                if self.connection.cpu_time > 0.5:\n                    self.log.debug(\n                        \"Delay %s %s, cpu_time used by connection: %.3fs\" %\n                        (self.connection.ip, cmd, self.connection.cpu_time)\n                    )\n                    time.sleep(self.connection.cpu_time)\n                    if self.connection.cpu_time > 5:\n                        self.connection.close(\"Cpu time: %.3fs\" % self.connection.cpu_time)\n                s = time.time()\n            if func:\n                func(params)\n            else:\n                self.actionUnknown(cmd, params)\n\n            if cmd not in [\"getFile\", \"streamFile\"]:\n                taken = time.time() - s\n                taken_sent = self.connection.last_sent_time - self.connection.last_send_time\n                self.connection.cpu_time += taken - taken_sent\n\n    # Update a site file request\n    def actionUpdate(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(1)\n            self.connection.badAction(5)\n            return False\n\n        inner_path = params.get(\"inner_path\", \"\")\n        current_content_modified = site.content_manager.contents.get(inner_path, {}).get(\"modified\", 0)\n        body = params[\"body\"]\n\n        if not inner_path.endswith(\"content.json\"):\n            self.response({\"error\": \"Only content.json update allowed\"})\n            self.connection.badAction(5)\n            return\n\n        should_validate_content = True\n        if \"modified\" in params and params[\"modified\"] <= current_content_modified:\n            should_validate_content = False\n            valid = None  # Same or earlier content as we have\n        elif not body:  # No body sent, we have to download it first\n            site.log.debug(\"Missing body from update for file %s, downloading ...\" % inner_path)\n            peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source=\"update\")  # Add or get peer\n            try:\n                body = peer.getFile(site.address, inner_path).read()\n            except Exception as err:\n                site.log.debug(\"Can't download updated file %s: %s\" % (inner_path, err))\n                self.response({\"error\": \"File invalid update: Can't download updaed file\"})\n                self.connection.badAction(5)\n                return\n\n        if should_validate_content:\n            try:\n                content = json.loads(body.decode())\n            except Exception as err:\n                site.log.debug(\"Update for %s is invalid JSON: %s\" % (inner_path, err))\n                self.response({\"error\": \"File invalid JSON\"})\n                self.connection.badAction(5)\n                return\n\n            file_uri = \"%s/%s:%s\" % (site.address, inner_path, content[\"modified\"])\n\n            if self.server.files_parsing.get(file_uri):  # Check if we already working on it\n                valid = None  # Same file\n            else:\n                try:\n                    valid = site.content_manager.verifyFile(inner_path, content)\n                except Exception as err:\n                    site.log.debug(\"Update for %s is invalid: %s\" % (inner_path, err))\n                    error = err\n                    valid = False\n\n        if valid is True:  # Valid and changed\n            site.log.info(\"Update for %s looks valid, saving...\" % inner_path)\n            self.server.files_parsing[file_uri] = True\n            site.storage.write(inner_path, body)\n            del params[\"body\"]\n\n            site.onFileDone(inner_path)  # Trigger filedone\n\n            if inner_path.endswith(\"content.json\"):  # Download every changed file from peer\n                peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source=\"update\")  # Add or get peer\n                # On complete publish to other peers\n                diffs = params.get(\"diffs\", {})\n                site.onComplete.once(lambda: site.publish(inner_path=inner_path, diffs=diffs, limit=3), \"publish_%s\" % inner_path)\n\n                # Load new content file and download changed files in new thread\n                def downloader():\n                    site.downloadContent(inner_path, peer=peer, diffs=params.get(\"diffs\", {}))\n                    del self.server.files_parsing[file_uri]\n\n                gevent.spawn(downloader)\n            else:\n                del self.server.files_parsing[file_uri]\n\n            self.response({\"ok\": \"Thanks, file %s updated!\" % inner_path})\n            self.connection.goodAction()\n\n        elif valid is None:  # Not changed\n            peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source=\"update old\")  # Add or get peer\n            if peer:\n                if not peer.connection:\n                    peer.connect(self.connection)  # Assign current connection to peer\n                if inner_path in site.content_manager.contents:\n                    peer.last_content_json_update = site.content_manager.contents[inner_path][\"modified\"]\n                if config.verbose:\n                    site.log.debug(\n                        \"Same version, adding new peer for locked files: %s, tasks: %s\" %\n                        (peer.key, len(site.worker_manager.tasks))\n                    )\n                for task in site.worker_manager.tasks:  # New peer add to every ongoing task\n                    if task[\"peers\"] and not task[\"optional_hash_id\"]:\n                        # Download file from this peer too if its peer locked\n                        site.needFile(task[\"inner_path\"], peer=peer, update=True, blocking=False)\n\n            self.response({\"ok\": \"File not changed\"})\n            self.connection.badAction()\n\n        else:  # Invalid sign or sha hash\n            self.response({\"error\": \"File %s invalid: %s\" % (inner_path, error)})\n            self.connection.badAction(5)\n\n    def isReadable(self, site, inner_path, file, pos):\n        return True\n\n    # Send file content request\n    def handleGetFile(self, params, streaming=False):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n        try:\n            file_path = site.storage.getPath(params[\"inner_path\"])\n            if streaming:\n                file_obj = site.storage.open(params[\"inner_path\"])\n            else:\n                file_obj = Msgpack.FilePart(file_path, \"rb\")\n\n            with file_obj as file:\n                file.seek(params[\"location\"])\n                read_bytes = params.get(\"read_bytes\", FILE_BUFF)\n                file_size = os.fstat(file.fileno()).st_size\n\n                if file_size > read_bytes:  # Check if file is readable at current position (for big files)\n                    if not self.isReadable(site, params[\"inner_path\"], file, params[\"location\"]):\n                        raise RequestError(\"File not readable at position: %s\" % params[\"location\"])\n                else:\n                    if params.get(\"file_size\") and params[\"file_size\"] != file_size:\n                        self.connection.badAction(2)\n                        raise RequestError(\"File size does not match: %sB != %sB\" % (params[\"file_size\"], file_size))\n\n                if not streaming:\n                    file.read_bytes = read_bytes\n\n                if params[\"location\"] > file_size:\n                    self.connection.badAction(5)\n                    raise RequestError(\"Bad file location\")\n\n                if streaming:\n                    back = {\n                        \"size\": file_size,\n                        \"location\": min(file.tell() + read_bytes, file_size),\n                        \"stream_bytes\": min(read_bytes, file_size - params[\"location\"])\n                    }\n                    self.response(back)\n                    self.sendRawfile(file, read_bytes=read_bytes)\n                else:\n                    back = {\n                        \"body\": file,\n                        \"size\": file_size,\n                        \"location\": min(file.tell() + file.read_bytes, file_size)\n                    }\n                    self.response(back, streaming=True)\n\n                bytes_sent = min(read_bytes, file_size - params[\"location\"])  # Number of bytes we going to send\n                site.settings[\"bytes_sent\"] = site.settings.get(\"bytes_sent\", 0) + bytes_sent\n            if config.debug_socket:\n                self.log.debug(\"File %s at position %s sent %s bytes\" % (file_path, params[\"location\"], bytes_sent))\n\n            # Add peer to site if not added before\n            connected_peer = site.addPeer(self.connection.ip, self.connection.port, source=\"request\")\n            if connected_peer:  # Just added\n                connected_peer.connect(self.connection)  # Assign current connection to peer\n\n            return {\"bytes_sent\": bytes_sent, \"file_size\": file_size, \"location\": params[\"location\"]}\n\n        except RequestError as err:\n            self.log.debug(\"GetFile %s %s %s request error: %s\" % (self.connection, params[\"site\"], params[\"inner_path\"], Debug.formatException(err)))\n            self.response({\"error\": \"File read error: %s\" % err})\n        except OSError as err:\n            if config.verbose:\n                self.log.debug(\"GetFile read error: %s\" % Debug.formatException(err))\n            self.response({\"error\": \"File read error\"})\n            return False\n        except Exception as err:\n            self.log.error(\"GetFile exception: %s\" % Debug.formatException(err))\n            self.response({\"error\": \"File read exception\"})\n            return False\n\n    def actionGetFile(self, params):\n        return self.handleGetFile(params)\n\n    def actionStreamFile(self, params):\n        return self.handleGetFile(params, streaming=True)\n\n    # Peer exchange request\n    def actionPex(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n\n        got_peer_keys = []\n        added = 0\n\n        # Add requester peer to site\n        connected_peer = site.addPeer(self.connection.ip, self.connection.port, source=\"request\")\n\n        if connected_peer:  # It was not registered before\n            added += 1\n            connected_peer.connect(self.connection)  # Assign current connection to peer\n\n        # Add sent peers to site\n        for packed_address in itertools.chain(params.get(\"peers\", []), params.get(\"peers_ipv6\", [])):\n            address = helper.unpackAddress(packed_address)\n            got_peer_keys.append(\"%s:%s\" % address)\n            if site.addPeer(*address, source=\"pex\"):\n                added += 1\n\n        # Add sent onion peers to site\n        for packed_address in params.get(\"peers_onion\", []):\n            address = helper.unpackOnionAddress(packed_address)\n            got_peer_keys.append(\"%s:%s\" % address)\n            if site.addPeer(*address, source=\"pex\"):\n                added += 1\n\n        # Send back peers that is not in the sent list and connectable (not port 0)\n        packed_peers = helper.packPeers(site.getConnectablePeers(params[\"need\"], ignore=got_peer_keys, allow_private=False))\n\n        if added:\n            site.worker_manager.onPeers()\n            if config.verbose:\n                self.log.debug(\n                    \"Added %s peers to %s using pex, sending back %s\" %\n                    (added, site, {key: len(val) for key, val in packed_peers.items()})\n                )\n\n        back = {\n            \"peers\": packed_peers[\"ipv4\"],\n            \"peers_ipv6\": packed_peers[\"ipv6\"],\n            \"peers_onion\": packed_peers[\"onion\"]\n        }\n\n        self.response(back)\n\n    # Get modified content.json files since\n    def actionListModified(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n        modified_files = site.content_manager.listModified(params[\"since\"])\n\n        # Add peer to site if not added before\n        connected_peer = site.addPeer(self.connection.ip, self.connection.port, source=\"request\")\n        if connected_peer:  # Just added\n            connected_peer.connect(self.connection)  # Assign current connection to peer\n\n        self.response({\"modified_files\": modified_files})\n\n    def actionGetHashfield(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n\n        # Add peer to site if not added before\n        peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, source=\"request\")\n        if not peer.connection:  # Just added\n            peer.connect(self.connection)  # Assign current connection to peer\n\n        peer.time_my_hashfield_sent = time.time()  # Don't send again if not changed\n\n        self.response({\"hashfield_raw\": site.content_manager.hashfield.tobytes()})\n\n    def findHashIds(self, site, hash_ids, limit=100):\n        back = collections.defaultdict(lambda: collections.defaultdict(list))\n        found = site.worker_manager.findOptionalHashIds(hash_ids, limit=limit)\n\n        for hash_id, peers in found.items():\n            for peer in peers:\n                ip_type = helper.getIpType(peer.ip)\n                if len(back[ip_type][hash_id]) < 20:\n                    back[ip_type][hash_id].append(peer.packMyAddress())\n        return back\n\n    def actionFindHashIds(self, params):\n        site = self.sites.get(params[\"site\"])\n        s = time.time()\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n\n        event_key = \"%s_findHashIds_%s_%s\" % (self.connection.ip, params[\"site\"], len(params[\"hash_ids\"]))\n        if self.connection.cpu_time > 0.5 or not RateLimit.isAllowed(event_key, 60 * 5):\n            time.sleep(0.1)\n            back = self.findHashIds(site, params[\"hash_ids\"], limit=10)\n        else:\n            back = self.findHashIds(site, params[\"hash_ids\"])\n        RateLimit.called(event_key)\n\n        my_hashes = []\n        my_hashfield_set = set(site.content_manager.hashfield)\n        for hash_id in params[\"hash_ids\"]:\n            if hash_id in my_hashfield_set:\n                my_hashes.append(hash_id)\n\n        if config.verbose:\n            self.log.debug(\n                \"Found: %s for %s hashids in %.3fs\" %\n                ({key: len(val) for key, val in back.items()}, len(params[\"hash_ids\"]), time.time() - s)\n            )\n        self.response({\"peers\": back[\"ipv4\"], \"peers_onion\": back[\"onion\"], \"peers_ipv6\": back[\"ipv6\"], \"my\": my_hashes})\n\n    def actionSetHashfield(self, params):\n        site = self.sites.get(params[\"site\"])\n        if not site or not site.isServing():  # Site unknown or not serving\n            self.response({\"error\": \"Unknown site\"})\n            self.connection.badAction(5)\n            return False\n\n        # Add or get peer\n        peer = site.addPeer(self.connection.ip, self.connection.port, return_peer=True, connection=self.connection, source=\"request\")\n        if not peer.connection:\n            peer.connect(self.connection)\n        peer.hashfield.replaceFromBytes(params[\"hashfield_raw\"])\n        self.response({\"ok\": \"Updated\"})\n\n    # Send a simple Pong! answer\n    def actionPing(self, params):\n        self.response(b\"Pong!\")\n\n    # Check requested port of the other peer\n    def actionCheckport(self, params):\n        if helper.getIpType(self.connection.ip) == \"ipv6\":\n            sock_address = (self.connection.ip, params[\"port\"], 0, 0)\n        else:\n            sock_address = (self.connection.ip, params[\"port\"])\n\n        with closing(helper.createSocket(self.connection.ip)) as sock:\n            sock.settimeout(5)\n            if sock.connect_ex(sock_address) == 0:\n                self.response({\"status\": \"open\", \"ip_external\": self.connection.ip})\n            else:\n                self.response({\"status\": \"closed\", \"ip_external\": self.connection.ip})\n\n    # Unknown command\n    def actionUnknown(self, cmd, params):\n        self.response({\"error\": \"Unknown command: %s\" % cmd})\n        self.connection.badAction(5)\n"
  },
  {
    "path": "src/File/FileServer.py",
    "content": "import logging\nimport time\nimport random\nimport socket\nimport sys\n\nimport gevent\nimport gevent.pool\nfrom gevent.server import StreamServer\n\nimport util\nfrom util import helper\nfrom Config import config\nfrom .FileRequest import FileRequest\nfrom Peer import PeerPortchecker\nfrom Site import SiteManager\nfrom Connection import ConnectionServer\nfrom Plugin import PluginManager\nfrom Debug import Debug\n\n\n@PluginManager.acceptPlugins\nclass FileServer(ConnectionServer):\n\n    def __init__(self, ip=config.fileserver_ip, port=config.fileserver_port, ip_type=config.fileserver_ip_type):\n        self.site_manager = SiteManager.site_manager\n        self.portchecker = PeerPortchecker.PeerPortchecker(self)\n        self.log = logging.getLogger(\"FileServer\")\n        self.ip_type = ip_type\n        self.ip_external_list = []\n\n        self.supported_ip_types = [\"ipv4\"]  # Outgoing ip_type support\n        if helper.getIpType(ip) == \"ipv6\" or self.isIpv6Supported():\n            self.supported_ip_types.append(\"ipv6\")\n\n        if ip_type == \"ipv6\" or (ip_type == \"dual\" and \"ipv6\" in self.supported_ip_types):\n            ip = ip.replace(\"*\", \"::\")\n        else:\n            ip = ip.replace(\"*\", \"0.0.0.0\")\n\n        if config.tor == \"always\":\n            port = config.tor_hs_port\n            config.fileserver_port = port\n        elif port == 0:  # Use random port\n            port_range_from, port_range_to = list(map(int, config.fileserver_port_range.split(\"-\")))\n            port = self.getRandomPort(ip, port_range_from, port_range_to)\n            config.fileserver_port = port\n            if not port:\n                raise Exception(\"Can't find bindable port\")\n            if not config.tor == \"always\":\n                config.saveValue(\"fileserver_port\", port)  # Save random port value for next restart\n                config.arguments.fileserver_port = port\n\n        ConnectionServer.__init__(self, ip, port, self.handleRequest)\n        self.log.debug(\"Supported IP types: %s\" % self.supported_ip_types)\n\n        if ip_type == \"dual\" and ip == \"::\":\n            # Also bind to ipv4 addres in dual mode\n            try:\n                self.log.debug(\"Binding proxy to %s:%s\" % (\"::\", self.port))\n                self.stream_server_proxy = StreamServer(\n                    (\"0.0.0.0\", self.port), self.handleIncomingConnection, spawn=self.pool, backlog=100\n                )\n            except Exception as err:\n                self.log.info(\"StreamServer proxy create error: %s\" % Debug.formatException(err))\n\n        self.port_opened = {}\n\n        self.sites = self.site_manager.sites\n        self.last_request = time.time()\n        self.files_parsing = {}\n        self.ui_server = None\n\n    def getRandomPort(self, ip, port_range_from, port_range_to):\n        self.log.info(\"Getting random port in range %s-%s...\" % (port_range_from, port_range_to))\n        tried = []\n        for bind_retry in range(100):\n            port = random.randint(port_range_from, port_range_to)\n            if port in tried:\n                continue\n            tried.append(port)\n            sock = helper.createSocket(ip)\n            try:\n                sock.bind((ip, port))\n                success = True\n            except Exception as err:\n                self.log.warning(\"Error binding to port %s: %s\" % (port, err))\n                success = False\n            sock.close()\n            if success:\n                self.log.info(\"Found unused random port: %s\" % port)\n                return port\n            else:\n                time.sleep(0.1)\n        return False\n\n    def isIpv6Supported(self):\n        if config.tor == \"always\":\n            return True\n        # Test if we can connect to ipv6 address\n        ipv6_testip = \"fcec:ae97:8902:d810:6c92:ec67:efb2:3ec5\"\n        try:\n            sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)\n            sock.connect((ipv6_testip, 80))\n            local_ipv6 = sock.getsockname()[0]\n            if local_ipv6 == \"::1\":\n                self.log.debug(\"IPv6 not supported, no local IPv6 address\")\n                return False\n            else:\n                self.log.debug(\"IPv6 supported on IP %s\" % local_ipv6)\n                return True\n        except socket.error as err:\n            self.log.warning(\"IPv6 not supported: %s\" % err)\n            return False\n        except Exception as err:\n            self.log.error(\"IPv6 check error: %s\" % err)\n            return False\n\n    def listenProxy(self):\n        try:\n            self.stream_server_proxy.serve_forever()\n        except Exception as err:\n            if err.errno == 98:  # Address already in use error\n                self.log.debug(\"StreamServer proxy listen error: %s\" % err)\n            else:\n                self.log.info(\"StreamServer proxy listen error: %s\" % err)\n\n    # Handle request to fileserver\n    def handleRequest(self, connection, message):\n        if config.verbose:\n            if \"params\" in message:\n                self.log.debug(\n                    \"FileRequest: %s %s %s %s\" %\n                    (str(connection), message[\"cmd\"], message[\"params\"].get(\"site\"), message[\"params\"].get(\"inner_path\"))\n                )\n            else:\n                self.log.debug(\"FileRequest: %s %s\" % (str(connection), message[\"cmd\"]))\n        req = FileRequest(self, connection)\n        req.route(message[\"cmd\"], message.get(\"req_id\"), message.get(\"params\"))\n        if not self.has_internet and not connection.is_private_ip:\n            self.has_internet = True\n            self.onInternetOnline()\n\n    def onInternetOnline(self):\n        self.log.info(\"Internet online\")\n        gevent.spawn(self.checkSites, check_files=False, force_port_check=True)\n\n    # Reload the FileRequest class to prevent restarts in debug mode\n    def reload(self):\n        global FileRequest\n        import imp\n        FileRequest = imp.load_source(\"FileRequest\", \"src/File/FileRequest.py\").FileRequest\n\n    def portCheck(self):\n        if config.offline:\n            self.log.info(\"Offline mode: port check disabled\")\n            res = {\"ipv4\": None, \"ipv6\": None}\n            self.port_opened = res\n            return res\n\n        if config.ip_external:\n            for ip_external in config.ip_external:\n                SiteManager.peer_blacklist.append((ip_external, self.port))  # Add myself to peer blacklist\n\n            ip_external_types = set([helper.getIpType(ip) for ip in config.ip_external])\n            res = {\n                \"ipv4\": \"ipv4\" in ip_external_types,\n                \"ipv6\": \"ipv6\" in ip_external_types\n            }\n            self.ip_external_list = config.ip_external\n            self.port_opened.update(res)\n            self.log.info(\"Server port opened based on configuration ipv4: %s, ipv6: %s\" % (res[\"ipv4\"], res[\"ipv6\"]))\n            return res\n\n        self.port_opened = {}\n        if self.ui_server:\n            self.ui_server.updateWebsocket()\n\n        if \"ipv6\" in self.supported_ip_types:\n            res_ipv6_thread = gevent.spawn(self.portchecker.portCheck, self.port, \"ipv6\")\n        else:\n            res_ipv6_thread = None\n\n        res_ipv4 = self.portchecker.portCheck(self.port, \"ipv4\")\n        if not res_ipv4[\"opened\"] and config.tor != \"always\":\n            if self.portchecker.portOpen(self.port):\n                res_ipv4 = self.portchecker.portCheck(self.port, \"ipv4\")\n\n        if res_ipv6_thread is None:\n            res_ipv6 = {\"ip\": None, \"opened\": None}\n        else:\n            res_ipv6 = res_ipv6_thread.get()\n            if res_ipv6[\"opened\"] and not helper.getIpType(res_ipv6[\"ip\"]) == \"ipv6\":\n                self.log.info(\"Invalid IPv6 address from port check: %s\" % res_ipv6[\"ip\"])\n                res_ipv6[\"opened\"] = False\n\n        self.ip_external_list = []\n        for res_ip in [res_ipv4, res_ipv6]:\n            if res_ip[\"ip\"] and res_ip[\"ip\"] not in self.ip_external_list:\n                self.ip_external_list.append(res_ip[\"ip\"])\n                SiteManager.peer_blacklist.append((res_ip[\"ip\"], self.port))\n\n        self.log.info(\"Server port opened ipv4: %s, ipv6: %s\" % (res_ipv4[\"opened\"], res_ipv6[\"opened\"]))\n\n        res = {\"ipv4\": res_ipv4[\"opened\"], \"ipv6\": res_ipv6[\"opened\"]}\n\n        # Add external IPs from local interfaces\n        interface_ips = helper.getInterfaceIps(\"ipv4\")\n        if \"ipv6\" in self.supported_ip_types:\n            interface_ips += helper.getInterfaceIps(\"ipv6\")\n        for ip in interface_ips:\n            if not helper.isPrivateIp(ip) and ip not in self.ip_external_list:\n                self.ip_external_list.append(ip)\n                res[helper.getIpType(ip)] = True  # We have opened port if we have external ip\n                SiteManager.peer_blacklist.append((ip, self.port))\n                self.log.debug(\"External ip found on interfaces: %s\" % ip)\n\n        self.port_opened.update(res)\n\n        if self.ui_server:\n            self.ui_server.updateWebsocket()\n\n        return res\n\n    # Check site file integrity\n    def checkSite(self, site, check_files=False):\n        if site.isServing():\n            site.announce(mode=\"startup\")  # Announce site to tracker\n            site.update(check_files=check_files)  # Update site's content.json and download changed files\n            site.sendMyHashfield()\n            site.updateHashfield()\n\n    # Check sites integrity\n    @util.Noparallel()\n    def checkSites(self, check_files=False, force_port_check=False):\n        self.log.debug(\"Checking sites...\")\n        s = time.time()\n        sites_checking = False\n        if not self.port_opened or force_port_check:  # Test and open port if not tested yet\n            if len(self.sites) <= 2:  # Don't wait port opening on first startup\n                sites_checking = True\n                for address, site in list(self.sites.items()):\n                    gevent.spawn(self.checkSite, site, check_files)\n\n            self.portCheck()\n\n            if not self.port_opened[\"ipv4\"]:\n                self.tor_manager.startOnions()\n\n        if not sites_checking:\n            check_pool = gevent.pool.Pool(5)\n            # Check sites integrity\n            for site in sorted(list(self.sites.values()), key=lambda site: site.settings.get(\"modified\", 0), reverse=True):\n                if not site.isServing():\n                    continue\n                check_thread = check_pool.spawn(self.checkSite, site, check_files)  # Check in new thread\n                time.sleep(2)\n                if site.settings.get(\"modified\", 0) < time.time() - 60 * 60 * 24:  # Not so active site, wait some sec to finish\n                    check_thread.join(timeout=5)\n        self.log.debug(\"Checksites done in %.3fs\" % (time.time() - s))\n\n    def cleanupSites(self):\n        import gc\n        startup = True\n        time.sleep(5 * 60)  # Sites already cleaned up on startup\n        peers_protected = set([])\n        while 1:\n            # Sites health care every 20 min\n            self.log.debug(\n                \"Running site cleanup, connections: %s, internet: %s, protected peers: %s\" %\n                (len(self.connections), self.has_internet, len(peers_protected))\n            )\n\n            for address, site in list(self.sites.items()):\n                if not site.isServing():\n                    continue\n\n                if not startup:\n                    site.cleanupPeers(peers_protected)\n\n                time.sleep(1)  # Prevent too quick request\n\n            peers_protected = set([])\n            for address, site in list(self.sites.items()):\n                if not site.isServing():\n                    continue\n\n                if site.peers:\n                    with gevent.Timeout(10, exception=False):\n                        site.announcer.announcePex()\n\n                # Last check modification failed\n                if site.content_updated is False:\n                    site.update()\n                elif site.bad_files:\n                    site.retryBadFiles()\n\n                if time.time() - site.settings.get(\"modified\", 0) < 60 * 60 * 24 * 7:\n                    # Keep active connections if site has been modified witin 7 days\n                    connected_num = site.needConnections(check_site_on_reconnect=True)\n\n                    if connected_num < config.connected_limit:  # This site has small amount of peers, protect them from closing\n                        peers_protected.update([peer.key for peer in site.getConnectedPeers()])\n\n                time.sleep(1)  # Prevent too quick request\n\n            site = None\n            gc.collect()  # Implicit garbage collection\n            startup = False\n            time.sleep(60 * 20)\n\n    def announceSite(self, site):\n        site.announce(mode=\"update\", pex=False)\n        active_site = time.time() - site.settings.get(\"modified\", 0) < 24 * 60 * 60\n        if site.settings[\"own\"] or active_site:\n            # Check connections more frequently on own and active sites to speed-up first connections\n            site.needConnections(check_site_on_reconnect=True)\n        site.sendMyHashfield(3)\n        site.updateHashfield(3)\n\n    # Announce sites every 20 min\n    def announceSites(self):\n        time.sleep(5 * 60)  # Sites already announced on startup\n        while 1:\n            config.loadTrackersFile()\n            s = time.time()\n            for address, site in list(self.sites.items()):\n                if not site.isServing():\n                    continue\n                gevent.spawn(self.announceSite, site).join(timeout=10)\n                time.sleep(1)\n            taken = time.time() - s\n\n            # Query all trackers one-by-one in 20 minutes evenly distributed\n            sleep = max(0, 60 * 20 / len(config.trackers) - taken)\n\n            self.log.debug(\"Site announce tracker done in %.3fs, sleeping for %.3fs...\" % (taken, sleep))\n            time.sleep(sleep)\n\n    # Detects if computer back from wakeup\n    def wakeupWatcher(self):\n        last_time = time.time()\n        last_my_ips = socket.gethostbyname_ex('')[2]\n        while 1:\n            time.sleep(30)\n            is_time_changed = time.time() - max(self.last_request, last_time) > 60 * 3\n            if is_time_changed:\n                # If taken more than 3 minute then the computer was in sleep mode\n                self.log.info(\n                    \"Wakeup detected: time warp from %0.f to %0.f (%0.f sleep seconds), acting like startup...\" %\n                    (last_time, time.time(), time.time() - last_time)\n                )\n\n            my_ips = socket.gethostbyname_ex('')[2]\n            is_ip_changed = my_ips != last_my_ips\n            if is_ip_changed:\n                self.log.info(\"IP change detected from %s to %s\" % (last_my_ips, my_ips))\n\n            if is_time_changed or is_ip_changed:\n                self.checkSites(check_files=False, force_port_check=True)\n\n            last_time = time.time()\n            last_my_ips = my_ips\n\n    # Bind and start serving sites\n    def start(self, check_sites=True):\n        if self.stopping:\n            return False\n\n        ConnectionServer.start(self)\n\n        try:\n            self.stream_server.start()\n        except Exception as err:\n            self.log.error(\"Error listening on: %s:%s: %s\" % (self.ip, self.port, err))\n\n        self.sites = self.site_manager.list()\n        if config.debug:\n            # Auto reload FileRequest on change\n            from Debug import DebugReloader\n            DebugReloader.watcher.addCallback(self.reload)\n\n        if check_sites:  # Open port, Update sites, Check files integrity\n            gevent.spawn(self.checkSites)\n\n        thread_announce_sites = gevent.spawn(self.announceSites)\n        thread_cleanup_sites = gevent.spawn(self.cleanupSites)\n        thread_wakeup_watcher = gevent.spawn(self.wakeupWatcher)\n\n        ConnectionServer.listen(self)\n\n        self.log.debug(\"Stopped.\")\n\n    def stop(self):\n        if self.running and self.portchecker.upnp_port_opened:\n            self.log.debug('Closing port %d' % self.port)\n            try:\n                self.portchecker.portClose(self.port)\n                self.log.info('Closed port via upnp.')\n            except Exception as err:\n                self.log.info(\"Failed at attempt to use upnp to close port: %s\" % err)\n\n        return ConnectionServer.stop(self)\n"
  },
  {
    "path": "src/File/__init__.py",
    "content": "from .FileServer import FileServer\nfrom .FileRequest import FileRequest"
  },
  {
    "path": "src/Peer/Peer.py",
    "content": "import logging\nimport time\nimport sys\nimport itertools\nimport collections\n\nimport gevent\n\nimport io\nfrom Debug import Debug\nfrom Config import config\nfrom util import helper\nfrom .PeerHashfield import PeerHashfield\nfrom Plugin import PluginManager\n\nif config.use_tempfiles:\n    import tempfile\n\n\n# Communicate remote peers\n@PluginManager.acceptPlugins\nclass Peer(object):\n    __slots__ = (\n        \"ip\", \"port\", \"site\", \"key\", \"connection\", \"connection_server\", \"time_found\", \"time_response\", \"time_hashfield\",\n        \"time_added\", \"has_hashfield\", \"is_tracker_connection\", \"time_my_hashfield_sent\", \"last_ping\", \"reputation\",\n        \"last_content_json_update\", \"hashfield\", \"connection_error\", \"hash_failed\", \"download_bytes\", \"download_time\"\n    )\n\n    def __init__(self, ip, port, site=None, connection_server=None):\n        self.ip = ip\n        self.port = port\n        self.site = site\n        self.key = \"%s:%s\" % (ip, port)\n\n        self.connection = None\n        self.connection_server = connection_server\n        self.has_hashfield = False  # Lazy hashfield object not created yet\n        self.time_hashfield = None  # Last time peer's hashfiled downloaded\n        self.time_my_hashfield_sent = None  # Last time my hashfield sent to peer\n        self.time_found = time.time()  # Time of last found in the torrent tracker\n        self.time_response = None  # Time of last successful response from peer\n        self.time_added = time.time()\n        self.last_ping = None  # Last response time for ping\n        self.is_tracker_connection = False  # Tracker connection instead of normal peer\n        self.reputation = 0  # More likely to connect if larger\n        self.last_content_json_update = 0.0  # Modify date of last received content.json\n\n        self.connection_error = 0  # Series of connection error\n        self.hash_failed = 0  # Number of bad files from peer\n        self.download_bytes = 0  # Bytes downloaded\n        self.download_time = 0  # Time spent to download\n\n    def __getattr__(self, key):\n        if key == \"hashfield\":\n            self.has_hashfield = True\n            self.hashfield = PeerHashfield()\n            return self.hashfield\n        else:\n            return getattr(self, key)\n\n    def log(self, text):\n        if not config.verbose:\n            return  # Only log if we are in debug mode\n        if self.site:\n            self.site.log.debug(\"%s:%s %s\" % (self.ip, self.port, text))\n        else:\n            logging.debug(\"%s:%s %s\" % (self.ip, self.port, text))\n\n    # Connect to host\n    def connect(self, connection=None):\n        if self.reputation < -10:\n            self.reputation = -10\n        if self.reputation > 10:\n            self.reputation = 10\n\n        if self.connection:\n            self.log(\"Getting connection (Closing %s)...\" % self.connection)\n            self.connection.close(\"Connection change\")\n        else:\n            self.log(\"Getting connection (reputation: %s)...\" % self.reputation)\n\n        if connection:  # Connection specified\n            self.log(\"Assigning connection %s\" % connection)\n            self.connection = connection\n            self.connection.sites += 1\n        else:  # Try to find from connection pool or create new connection\n            self.connection = None\n\n            try:\n                if self.connection_server:\n                    connection_server = self.connection_server\n                elif self.site:\n                    connection_server = self.site.connection_server\n                else:\n                    import main\n                    connection_server = main.file_server\n                self.connection = connection_server.getConnection(self.ip, self.port, site=self.site, is_tracker_connection=self.is_tracker_connection)\n                self.reputation += 1\n                self.connection.sites += 1\n            except Exception as err:\n                self.onConnectionError(\"Getting connection error\")\n                self.log(\"Getting connection error: %s (connection_error: %s, hash_failed: %s)\" %\n                         (Debug.formatException(err), self.connection_error, self.hash_failed))\n                self.connection = None\n        return self.connection\n\n    # Check if we have connection to peer\n    def findConnection(self):\n        if self.connection and self.connection.connected:  # We have connection to peer\n            return self.connection\n        else:  # Try to find from other sites connections\n            self.connection = self.site.connection_server.getConnection(self.ip, self.port, create=False, site=self.site)\n            if self.connection:\n                self.connection.sites += 1\n        return self.connection\n\n    def __str__(self):\n        if self.site:\n            return \"Peer:%-12s of %s\" % (self.ip, self.site.address_short)\n        else:\n            return \"Peer:%-12s\" % self.ip\n\n    def __repr__(self):\n        return \"<%s>\" % self.__str__()\n\n    def packMyAddress(self):\n        if self.ip.endswith(\".onion\"):\n            return helper.packOnionAddress(self.ip, self.port)\n        else:\n            return helper.packAddress(self.ip, self.port)\n\n    # Found a peer from a source\n    def found(self, source=\"other\"):\n        if self.reputation < 5:\n            if source == \"tracker\":\n                if self.ip.endswith(\".onion\"):\n                    self.reputation += 1\n                else:\n                    self.reputation += 2\n            elif source == \"local\":\n                self.reputation += 20\n\n        if source in (\"tracker\", \"local\"):\n            self.site.peers_recent.appendleft(self)\n        self.time_found = time.time()\n\n    # Send a command to peer and return response value\n    def request(self, cmd, params={}, stream_to=None):\n        if not self.connection or self.connection.closed:\n            self.connect()\n            if not self.connection:\n                self.onConnectionError(\"Reconnect error\")\n                return None  # Connection failed\n\n        self.log(\"Send request: %s %s %s %s\" % (params.get(\"site\", \"\"), cmd, params.get(\"inner_path\", \"\"), params.get(\"location\", \"\")))\n\n        for retry in range(1, 4):  # Retry 3 times\n            try:\n                if not self.connection:\n                    raise Exception(\"No connection found\")\n                res = self.connection.request(cmd, params, stream_to)\n                if not res:\n                    raise Exception(\"Send error\")\n                if \"error\" in res:\n                    self.log(\"%s error: %s\" % (cmd, res[\"error\"]))\n                    self.onConnectionError(\"Response error\")\n                    break\n                else:  # Successful request, reset connection error num\n                    self.connection_error = 0\n                self.time_response = time.time()\n                if res:\n                    return res\n                else:\n                    raise Exception(\"Invalid response: %s\" % res)\n            except Exception as err:\n                if type(err).__name__ == \"Notify\":  # Greenlet killed by worker\n                    self.log(\"Peer worker got killed: %s, aborting cmd: %s\" % (err.message, cmd))\n                    break\n                else:\n                    self.onConnectionError(\"Request error\")\n                    self.log(\n                        \"%s (connection_error: %s, hash_failed: %s, retry: %s)\" %\n                        (Debug.formatException(err), self.connection_error, self.hash_failed, retry)\n                    )\n                    time.sleep(1 * retry)\n                    self.connect()\n        return None  # Failed after 4 retry\n\n    # Get a file content from peer\n    def getFile(self, site, inner_path, file_size=None, pos_from=0, pos_to=None, streaming=False):\n        if file_size and file_size > 5 * 1024 * 1024:\n            max_read_size = 1024 * 1024\n        else:\n            max_read_size = 512 * 1024\n\n        if pos_to:\n            read_bytes = min(max_read_size, pos_to - pos_from)\n        else:\n            read_bytes = max_read_size\n\n        location = pos_from\n\n        if config.use_tempfiles:\n            buff = tempfile.SpooledTemporaryFile(max_size=16 * 1024, mode='w+b')\n        else:\n            buff = io.BytesIO()\n\n        s = time.time()\n        while True:  # Read in smaller parts\n            if config.stream_downloads or read_bytes > 256 * 1024 or streaming:\n                res = self.request(\"streamFile\", {\"site\": site, \"inner_path\": inner_path, \"location\": location, \"read_bytes\": read_bytes, \"file_size\": file_size}, stream_to=buff)\n                if not res or \"location\" not in res:  # Error\n                    return False\n            else:\n                self.log(\"Send: %s\" % inner_path)\n                res = self.request(\"getFile\", {\"site\": site, \"inner_path\": inner_path, \"location\": location, \"read_bytes\": read_bytes, \"file_size\": file_size})\n                if not res or \"location\" not in res:  # Error\n                    return False\n                self.log(\"Recv: %s\" % inner_path)\n                buff.write(res[\"body\"])\n                res[\"body\"] = None  # Save memory\n\n            if res[\"location\"] == res[\"size\"] or res[\"location\"] == pos_to:  # End of file\n                break\n            else:\n                location = res[\"location\"]\n                if pos_to:\n                    read_bytes = min(max_read_size, pos_to - location)\n\n        if pos_to:\n            recv = pos_to - pos_from\n        else:\n            recv = res[\"location\"]\n\n        self.download_bytes += recv\n        self.download_time += (time.time() - s)\n        if self.site:\n            self.site.settings[\"bytes_recv\"] = self.site.settings.get(\"bytes_recv\", 0) + recv\n        self.log(\"Downloaded: %s, pos: %s, read_bytes: %s\" % (inner_path, buff.tell(), read_bytes))\n        buff.seek(0)\n        return buff\n\n    # Send a ping request\n    def ping(self):\n        response_time = None\n        for retry in range(1, 3):  # Retry 3 times\n            s = time.time()\n            with gevent.Timeout(10.0, False):  # 10 sec timeout, don't raise exception\n                res = self.request(\"ping\")\n\n                if res and \"body\" in res and res[\"body\"] == b\"Pong!\":\n                    response_time = time.time() - s\n                    break  # All fine, exit from for loop\n            # Timeout reached or bad response\n            self.onConnectionError(\"Ping timeout\")\n            self.connect()\n            time.sleep(1)\n\n        if response_time:\n            self.log(\"Ping: %.3f\" % response_time)\n        else:\n            self.log(\"Ping failed\")\n        self.last_ping = response_time\n        return response_time\n\n    # Request peer exchange from peer\n    def pex(self, site=None, need_num=5):\n        if not site:\n            site = self.site  # If no site defined request peers for this site\n\n        # give back 5 connectible peers\n        packed_peers = helper.packPeers(self.site.getConnectablePeers(5, allow_private=False))\n        request = {\"site\": site.address, \"peers\": packed_peers[\"ipv4\"], \"need\": need_num}\n        if packed_peers[\"onion\"]:\n            request[\"peers_onion\"] = packed_peers[\"onion\"]\n        if packed_peers[\"ipv6\"]:\n            request[\"peers_ipv6\"] = packed_peers[\"ipv6\"]\n        res = self.request(\"pex\", request)\n        if not res or \"error\" in res:\n            return False\n        added = 0\n\n        # Remove unsupported peer types\n        if \"peers_ipv6\" in res and self.connection and \"ipv6\" not in self.connection.server.supported_ip_types:\n            del res[\"peers_ipv6\"]\n\n        if \"peers_onion\" in res and self.connection and \"onion\" not in self.connection.server.supported_ip_types:\n            del res[\"peers_onion\"]\n\n        # Add IPv4 + IPv6\n        for peer in itertools.chain(res.get(\"peers\", []), res.get(\"peers_ipv6\", [])):\n            address = helper.unpackAddress(peer)\n            if site.addPeer(*address, source=\"pex\"):\n                added += 1\n\n        # Add Onion\n        for peer in res.get(\"peers_onion\", []):\n            address = helper.unpackOnionAddress(peer)\n            if site.addPeer(*address, source=\"pex\"):\n                added += 1\n\n        if added:\n            self.log(\"Added peers using pex: %s\" % added)\n\n        return added\n\n    # List modified files since the date\n    # Return: {inner_path: modification date,...}\n    def listModified(self, since):\n        return self.request(\"listModified\", {\"since\": since, \"site\": self.site.address})\n\n    def updateHashfield(self, force=False):\n        # Don't update hashfield again in 5 min\n        if self.time_hashfield and time.time() - self.time_hashfield < 5 * 60 and not force:\n            return False\n\n        self.time_hashfield = time.time()\n        res = self.request(\"getHashfield\", {\"site\": self.site.address})\n        if not res or \"error\" in res or \"hashfield_raw\" not in res:\n            return False\n        self.hashfield.replaceFromBytes(res[\"hashfield_raw\"])\n\n        return self.hashfield\n\n    # Find peers for hashids\n    # Return: {hash1: [\"ip:port\", \"ip:port\",...],...}\n    def findHashIds(self, hash_ids):\n        res = self.request(\"findHashIds\", {\"site\": self.site.address, \"hash_ids\": hash_ids})\n        if not res or \"error\" in res or type(res) is not dict:\n            return False\n\n        back = collections.defaultdict(list)\n\n        for ip_type in [\"ipv4\", \"ipv6\", \"onion\"]:\n            if ip_type == \"ipv4\":\n                key = \"peers\"\n            else:\n                key = \"peers_%s\" % ip_type\n            for hash, peers in list(res.get(key, {}).items())[0:30]:\n                if ip_type == \"onion\":\n                    unpacker_func = helper.unpackOnionAddress\n                else:\n                    unpacker_func = helper.unpackAddress\n\n                back[hash] += list(map(unpacker_func, peers))\n\n        for hash in res.get(\"my\", []):\n            if self.connection:\n                back[hash].append((self.connection.ip, self.connection.port))\n            else:\n                back[hash].append((self.ip, self.port))\n\n        return back\n\n    # Send my hashfield to peer\n    # Return: True if sent\n    def sendMyHashfield(self):\n        if self.connection and self.connection.handshake.get(\"rev\", 0) < 510:\n            return False  # Not supported\n        if self.time_my_hashfield_sent and self.site.content_manager.hashfield.time_changed <= self.time_my_hashfield_sent:\n            return False  # Peer already has the latest hashfield\n\n        res = self.request(\"setHashfield\", {\"site\": self.site.address, \"hashfield_raw\": self.site.content_manager.hashfield.tobytes()})\n        if not res or \"error\" in res:\n            return False\n        else:\n            self.time_my_hashfield_sent = time.time()\n            return True\n\n    def publish(self, address, inner_path, body, modified, diffs=[]):\n        if len(body) > 10 * 1024 and self.connection and self.connection.handshake.get(\"rev\", 0) >= 4095:\n            # To save bw we don't push big content.json to peers\n            body = b\"\"\n\n        return self.request(\"update\", {\n            \"site\": address,\n            \"inner_path\": inner_path,\n            \"body\": body,\n            \"modified\": modified,\n            \"diffs\": diffs\n        })\n\n    # Stop and remove from site\n    def remove(self, reason=\"Removing\"):\n        self.log(\"Removing peer...Connection error: %s, Hash failed: %s\" % (self.connection_error, self.hash_failed))\n        if self.site and self.key in self.site.peers:\n            del(self.site.peers[self.key])\n\n        if self.site and self in self.site.peers_recent:\n            self.site.peers_recent.remove(self)\n\n        if self.connection:\n            self.connection.close(reason)\n\n    # - EVENTS -\n\n    # On connection error\n    def onConnectionError(self, reason=\"Unknown\"):\n        self.connection_error += 1\n        if self.site and len(self.site.peers) > 200:\n            limit = 3\n        else:\n            limit = 6\n        self.reputation -= 1\n        if self.connection_error >= limit:  # Dead peer\n            self.remove(\"Peer connection: %s\" % reason)\n\n    # Done working with peer\n    def onWorkerDone(self):\n        pass\n"
  },
  {
    "path": "src/Peer/PeerHashfield.py",
    "content": "import array\nimport time\n\n\nclass PeerHashfield(object):\n    __slots__ = (\"storage\", \"time_changed\", \"append\", \"remove\", \"tobytes\", \"frombytes\", \"__len__\", \"__iter__\")\n    def __init__(self):\n        self.storage = self.createStorage()\n        self.time_changed = time.time()\n\n    def createStorage(self):\n        storage = array.array(\"H\")\n        self.append = storage.append\n        self.remove = storage.remove\n        self.tobytes = storage.tobytes\n        self.frombytes = storage.frombytes\n        self.__len__ = storage.__len__\n        self.__iter__ = storage.__iter__\n        return storage\n\n    def appendHash(self, hash):\n        hash_id = int(hash[0:4], 16)\n        if hash_id not in self.storage:\n            self.storage.append(hash_id)\n            self.time_changed = time.time()\n            return True\n        else:\n            return False\n\n    def appendHashId(self, hash_id):\n        if hash_id not in self.storage:\n            self.storage.append(hash_id)\n            self.time_changed = time.time()\n            return True\n        else:\n            return False\n\n    def removeHash(self, hash):\n        hash_id = int(hash[0:4], 16)\n        if hash_id in self.storage:\n            self.storage.remove(hash_id)\n            self.time_changed = time.time()\n            return True\n        else:\n            return False\n\n    def removeHashId(self, hash_id):\n        if hash_id in self.storage:\n            self.storage.remove(hash_id)\n            self.time_changed = time.time()\n            return True\n        else:\n            return False\n\n    def getHashId(self, hash):\n        return int(hash[0:4], 16)\n\n    def hasHash(self, hash):\n        return int(hash[0:4], 16) in self.storage\n\n    def replaceFromBytes(self, hashfield_raw):\n        self.storage = self.createStorage()\n        self.storage.frombytes(hashfield_raw)\n        self.time_changed = time.time()\n\nif __name__ == \"__main__\":\n    field = PeerHashfield()\n    s = time.time()\n    for i in range(10000):\n        field.appendHashId(i)\n    print(time.time()-s)\n    s = time.time()\n    for i in range(10000):\n        field.hasHash(\"AABB\")\n    print(time.time()-s)"
  },
  {
    "path": "src/Peer/PeerPortchecker.py",
    "content": "import logging\nimport urllib.request\nimport urllib.parse\nimport re\nimport time\n\nfrom Debug import Debug\nfrom util import UpnpPunch\n\n\nclass PeerPortchecker(object):\n    checker_functions = {\n        \"ipv4\": [\"checkIpfingerprints\", \"checkCanyouseeme\"],\n        \"ipv6\": [\"checkMyaddr\", \"checkIpv6scanner\"]\n    }\n    def __init__(self, file_server):\n        self.log = logging.getLogger(\"PeerPortchecker\")\n        self.upnp_port_opened = False\n        self.file_server = file_server\n\n    def requestUrl(self, url, post_data=None):\n        if type(post_data) is dict:\n            post_data = urllib.parse.urlencode(post_data).encode(\"utf8\")\n        req = urllib.request.Request(url, post_data)\n        req.add_header(\"Referer\", url)\n        req.add_header(\"User-Agent\", \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11\")\n        req.add_header(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\")\n        return urllib.request.urlopen(req, timeout=20.0)\n\n    def portOpen(self, port):\n        self.log.info(\"Trying to open port using UpnpPunch...\")\n\n        try:\n            UpnpPunch.ask_to_open_port(port, 'ZeroNet', retries=3, protos=[\"TCP\"])\n            self.upnp_port_opened = True\n        except Exception as err:\n            self.log.warning(\"UpnpPunch run error: %s\" % Debug.formatException(err))\n            return False\n\n        return True\n\n    def portClose(self, port):\n        return UpnpPunch.ask_to_close_port(port, protos=[\"TCP\"])\n\n    def portCheck(self, port, ip_type=\"ipv4\"):\n        checker_functions = self.checker_functions[ip_type]\n\n        for func_name in checker_functions:\n            func = getattr(self, func_name)\n            s = time.time()\n            try:\n                res = func(port)\n                if res:\n                    self.log.info(\n                        \"Checked port %s (%s) using %s result: %s in %.3fs\" %\n                        (port, ip_type, func_name, res, time.time() - s)\n                    )\n                    time.sleep(0.1)\n                    if res[\"opened\"] and not self.file_server.had_external_incoming:\n                        res[\"opened\"] = False\n                        self.log.warning(\"Port %s:%s looks opened, but no incoming connection\" % (res[\"ip\"], port))\n                    break\n            except Exception as err:\n                self.log.warning(\n                    \"%s check error: %s in %.3fs\" %\n                    (func_name, Debug.formatException(err), time.time() - s)\n                )\n                res = {\"ip\": None, \"opened\": False}\n\n        return res\n\n    def checkCanyouseeme(self, port):\n        data = urllib.request.urlopen(\"https://www.canyouseeme.org/\", b\"ip=1.1.1.1&port=%s\" % str(port).encode(\"ascii\"), timeout=20.0).read().decode(\"utf8\")\n\n        message = re.match(r'.*<p style=\"padding-left:15px\">(.*?)</p>', data, re.DOTALL).group(1)\n        message = re.sub(r\"<.*?>\", \"\", message.replace(\"<br>\", \" \").replace(\"&nbsp;\", \" \"))  # Strip http tags\n\n        match = re.match(r\".*service on (.*?) on\", message)\n        if match:\n            ip = match.group(1)\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n\n        if \"Success\" in message:\n            return {\"ip\": ip, \"opened\": True}\n        elif \"Error\" in message:\n            return {\"ip\": ip, \"opened\": False}\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n\n    def checkIpfingerprints(self, port):\n        data = self.requestUrl(\"https://www.ipfingerprints.com/portscan.php\").read().decode(\"utf8\")\n        ip = re.match(r'.*name=\"remoteHost\".*?value=\"(.*?)\"', data, re.DOTALL).group(1)\n\n        post_data = {\n            \"remoteHost\": ip, \"start_port\": port, \"end_port\": port,\n            \"normalScan\": \"Yes\", \"scan_type\": \"connect2\", \"ping_type\": \"none\"\n        }\n        message = self.requestUrl(\"https://www.ipfingerprints.com/scripts/getPortsInfo.php\", post_data).read().decode(\"utf8\")\n\n        if \"open\" in message:\n            return {\"ip\": ip, \"opened\": True}\n        elif \"filtered\" in message or \"closed\" in message:\n            return {\"ip\": ip, \"opened\": False}\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n\n    def checkMyaddr(self, port):\n        url = \"http://ipv6.my-addr.com/online-ipv6-port-scan.php\"\n\n        data = self.requestUrl(url).read().decode(\"utf8\")\n\n        ip = re.match(r'.*Your IP address is:[ ]*([0-9\\.:a-z]+)', data.replace(\"&nbsp;\", \"\"), re.DOTALL).group(1)\n\n        post_data = {\"addr\": ip, \"ports_selected\": \"\", \"ports_list\": port}\n        data = self.requestUrl(url, post_data).read().decode(\"utf8\")\n\n        message = re.match(r\".*<table class='table_font_16'>(.*?)</table>\", data, re.DOTALL).group(1)\n\n        if \"ok.png\" in message:\n            return {\"ip\": ip, \"opened\": True}\n        elif \"fail.png\" in message:\n            return {\"ip\": ip, \"opened\": False}\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n\n    def checkIpv6scanner(self, port):\n        url = \"http://www.ipv6scanner.com/cgi-bin/main.py\"\n\n        data = self.requestUrl(url).read().decode(\"utf8\")\n\n        ip = re.match(r'.*Your IP address is[ ]*([0-9\\.:a-z]+)', data.replace(\"&nbsp;\", \"\"), re.DOTALL).group(1)\n\n        post_data = {\"host\": ip, \"scanType\": \"1\", \"port\": port, \"protocol\": \"tcp\", \"authorized\": \"yes\"}\n        data = self.requestUrl(url, post_data).read().decode(\"utf8\")\n\n        message = re.match(r\".*<table id='scantable'>(.*?)</table>\", data, re.DOTALL).group(1)\n        message_text = re.sub(\"<.*?>\", \" \", message.replace(\"<br>\", \" \").replace(\"&nbsp;\", \" \").strip())  # Strip http tags\n\n        if \"OPEN\" in message_text:\n            return {\"ip\": ip, \"opened\": True}\n        elif \"CLOSED\" in message_text or \"FILTERED\" in message_text:\n            return {\"ip\": ip, \"opened\": False}\n        else:\n            raise Exception(\"Invalid response: %s\" % message_text)\n\n    def checkPortchecker(self, port):  # Not working: Forbidden\n        data = self.requestUrl(\"https://portchecker.co\").read().decode(\"utf8\")\n        csrf = re.match(r'.*name=\"_csrf\" value=\"(.*?)\"', data, re.DOTALL).group(1)\n\n        data = self.requestUrl(\"https://portchecker.co\", {\"port\": port, \"_csrf\": csrf}).read().decode(\"utf8\")\n        message = re.match(r'.*<div id=\"results-wrapper\">(.*?)</div>', data, re.DOTALL).group(1)\n        message = re.sub(r\"<.*?>\", \"\", message.replace(\"<br>\", \" \").replace(\"&nbsp;\", \" \").strip())  # Strip http tags\n\n        match = re.match(r\".*targetIP.*?value=\\\"(.*?)\\\"\", data, re.DOTALL)\n        if match:\n            ip = match.group(1)\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n\n        if \"open\" in message:\n            return {\"ip\": ip, \"opened\": True}\n        elif \"closed\" in message:\n            return {\"ip\": ip, \"opened\": False}\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n\n    def checkSubnetonline(self, port):  # Not working: Invalid response\n        url = \"https://www.subnetonline.com/pages/ipv6-network-tools/online-ipv6-port-scanner.php\"\n\n        data = self.requestUrl(url).read().decode(\"utf8\")\n\n        ip = re.match(r'.*Your IP is.*?name=\"host\".*?value=\"(.*?)\"', data, re.DOTALL).group(1)\n        token = re.match(r'.*name=\"token\".*?value=\"(.*?)\"', data, re.DOTALL).group(1)\n\n        post_data = {\"host\": ip, \"port\": port, \"allow\": \"on\", \"token\": token, \"submit\": \"Scanning..\"}\n        data = self.requestUrl(url, post_data).read().decode(\"utf8\")\n\n        print(post_data, data)\n\n        message = re.match(r\".*<div class='formfield'>(.*?)</div>\", data, re.DOTALL).group(1)\n        message = re.sub(r\"<.*?>\", \"\", message.replace(\"<br>\", \" \").replace(\"&nbsp;\", \" \").strip())  # Strip http tags\n\n        if \"online\" in message:\n            return {\"ip\": ip, \"opened\": True}\n        elif \"closed\" in message:\n            return {\"ip\": ip, \"opened\": False}\n        else:\n            raise Exception(\"Invalid response: %s\" % message)\n"
  },
  {
    "path": "src/Peer/__init__.py",
    "content": "from .Peer import Peer\nfrom .PeerHashfield import PeerHashfield\n"
  },
  {
    "path": "src/Plugin/PluginManager.py",
    "content": "import logging\nimport os\nimport sys\nimport shutil\nimport time\nfrom collections import defaultdict\n\nimport importlib\nimport json\n\nfrom Debug import Debug\nfrom Config import config\nimport plugins\n\n\nclass PluginManager:\n    def __init__(self):\n        self.log = logging.getLogger(\"PluginManager\")\n        self.path_plugins = os.path.abspath(os.path.dirname(plugins.__file__))\n        self.path_installed_plugins = config.data_dir + \"/__plugins__\"\n        self.plugins = defaultdict(list)  # Registered plugins (key: class name, value: list of plugins for class)\n        self.subclass_order = {}  # Record the load order of the plugins, to keep it after reload\n        self.pluggable = {}\n        self.plugin_names = []  # Loaded plugin names\n        self.plugins_updated = {}  # List of updated plugins since restart\n        self.plugins_rev = {}  # Installed plugins revision numbers\n        self.after_load = []   # Execute functions after loaded plugins\n        self.function_flags = {}  # Flag function for permissions\n        self.reloading = False\n        self.config_path = config.data_dir + \"/plugins.json\"\n        self.loadConfig()\n\n        self.config.setdefault(\"builtin\", {})\n\n        sys.path.append(os.path.join(os.getcwd(), self.path_plugins))\n        self.migratePlugins()\n\n        if config.debug:  # Auto reload Plugins on file change\n            from Debug import DebugReloader\n            DebugReloader.watcher.addCallback(self.reloadPlugins)\n\n    def loadConfig(self):\n        if os.path.isfile(self.config_path):\n            try:\n                self.config = json.load(open(self.config_path, encoding=\"utf8\"))\n            except Exception as err:\n                self.log.error(\"Error loading %s: %s\" % (self.config_path, err))\n                self.config = {}\n        else:\n            self.config = {}\n\n    def saveConfig(self):\n        f = open(self.config_path, \"w\", encoding=\"utf8\")\n        json.dump(self.config, f, ensure_ascii=False, sort_keys=True, indent=2)\n\n    def migratePlugins(self):\n        for dir_name in os.listdir(self.path_plugins):\n            if dir_name == \"Mute\":\n                self.log.info(\"Deleting deprecated/renamed plugin: %s\" % dir_name)\n                shutil.rmtree(\"%s/%s\" % (self.path_plugins, dir_name))\n\n    # -- Load / Unload --\n\n    def listPlugins(self, list_disabled=False):\n        plugins = []\n        for dir_name in sorted(os.listdir(self.path_plugins)):\n            dir_path = os.path.join(self.path_plugins, dir_name)\n            plugin_name = dir_name.replace(\"disabled-\", \"\")\n            if dir_name.startswith(\"disabled\"):\n                is_enabled = False\n            else:\n                is_enabled = True\n\n            plugin_config = self.config[\"builtin\"].get(plugin_name, {})\n            if \"enabled\" in plugin_config:\n                is_enabled = plugin_config[\"enabled\"]\n\n            if dir_name == \"__pycache__\" or not os.path.isdir(dir_path):\n                continue  # skip\n            if dir_name.startswith(\"Debug\") and not config.debug:\n                continue  # Only load in debug mode if module name starts with Debug\n            if not is_enabled and not list_disabled:\n                continue  # Dont load if disabled\n\n            plugin = {}\n            plugin[\"source\"] = \"builtin\"\n            plugin[\"name\"] = plugin_name\n            plugin[\"dir_name\"] = dir_name\n            plugin[\"dir_path\"] = dir_path\n            plugin[\"inner_path\"] = plugin_name\n            plugin[\"enabled\"] = is_enabled\n            plugin[\"rev\"] = config.rev\n            plugin[\"loaded\"] = plugin_name in self.plugin_names\n            plugins.append(plugin)\n\n        plugins += self.listInstalledPlugins(list_disabled)\n        return plugins\n\n    def listInstalledPlugins(self, list_disabled=False):\n        plugins = []\n\n        for address, site_plugins in sorted(self.config.items()):\n            if address == \"builtin\":\n                continue\n            for plugin_inner_path, plugin_config in sorted(site_plugins.items()):\n                is_enabled = plugin_config.get(\"enabled\", False)\n                if not is_enabled and not list_disabled:\n                    continue\n                plugin_name = os.path.basename(plugin_inner_path)\n\n                dir_path = \"%s/%s/%s\" % (self.path_installed_plugins, address, plugin_inner_path)\n\n                plugin = {}\n                plugin[\"source\"] = address\n                plugin[\"name\"] = plugin_name\n                plugin[\"dir_name\"] = plugin_name\n                plugin[\"dir_path\"] = dir_path\n                plugin[\"inner_path\"] = plugin_inner_path\n                plugin[\"enabled\"] = is_enabled\n                plugin[\"rev\"] = plugin_config.get(\"rev\", 0)\n                plugin[\"loaded\"] = plugin_name in self.plugin_names\n                plugins.append(plugin)\n\n        return plugins\n\n    # Load all plugin\n    def loadPlugins(self):\n        all_loaded = True\n        s = time.time()\n        for plugin in self.listPlugins():\n            self.log.debug(\"Loading plugin: %s (%s)\" % (plugin[\"name\"], plugin[\"source\"]))\n            if plugin[\"source\"] != \"builtin\":\n                self.plugins_rev[plugin[\"name\"]] = plugin[\"rev\"]\n                site_plugin_dir = os.path.dirname(plugin[\"dir_path\"])\n                if site_plugin_dir not in sys.path:\n                    sys.path.append(site_plugin_dir)\n            try:\n                sys.modules[plugin[\"name\"]] = __import__(plugin[\"dir_name\"])\n            except Exception as err:\n                self.log.error(\"Plugin %s load error: %s\" % (plugin[\"name\"], Debug.formatException(err)))\n                all_loaded = False\n            if plugin[\"name\"] not in self.plugin_names:\n                self.plugin_names.append(plugin[\"name\"])\n\n        self.log.debug(\"Plugins loaded in %.3fs\" % (time.time() - s))\n        for func in self.after_load:\n            func()\n        return all_loaded\n\n    # Reload all plugins\n    def reloadPlugins(self):\n        self.reloading = True\n        self.after_load = []\n        self.plugins_before = self.plugins\n        self.plugins = defaultdict(list)  # Reset registered plugins\n        for module_name, module in list(sys.modules.items()):\n            if not module or not getattr(module, \"__file__\", None):\n                continue\n            if self.path_plugins not in module.__file__ and self.path_installed_plugins not in module.__file__:\n                continue\n\n            if \"allow_reload\" in dir(module) and not module.allow_reload:  # Reload disabled\n                # Re-add non-reloadable plugins\n                for class_name, classes in self.plugins_before.items():\n                    for c in classes:\n                        if c.__module__ != module.__name__:\n                            continue\n                        self.plugins[class_name].append(c)\n            else:\n                try:\n                    importlib.reload(module)\n                except Exception as err:\n                    self.log.error(\"Plugin %s reload error: %s\" % (module_name, Debug.formatException(err)))\n\n        self.loadPlugins()  # Load new plugins\n\n        # Change current classes in memory\n        import gc\n        patched = {}\n        for class_name, classes in self.plugins.items():\n            classes = classes[:]  # Copy the current plugins\n            classes.reverse()\n            base_class = self.pluggable[class_name]  # Original class\n            classes.append(base_class)  # Add the class itself to end of inherience line\n            plugined_class = type(class_name, tuple(classes), dict())  # Create the plugined class\n            for obj in gc.get_objects():\n                if type(obj).__name__ == class_name:\n                    obj.__class__ = plugined_class\n                    patched[class_name] = patched.get(class_name, 0) + 1\n        self.log.debug(\"Patched objects: %s\" % patched)\n\n        # Change classes in modules\n        patched = {}\n        for class_name, classes in self.plugins.items():\n            for module_name, module in list(sys.modules.items()):\n                if class_name in dir(module):\n                    if \"__class__\" not in dir(getattr(module, class_name)):  # Not a class\n                        continue\n                    base_class = self.pluggable[class_name]\n                    classes = self.plugins[class_name][:]\n                    classes.reverse()\n                    classes.append(base_class)\n                    plugined_class = type(class_name, tuple(classes), dict())\n                    setattr(module, class_name, plugined_class)\n                    patched[class_name] = patched.get(class_name, 0) + 1\n\n        self.log.debug(\"Patched modules: %s\" % patched)\n        self.reloading = False\n\n\nplugin_manager = PluginManager()  # Singletone\n\n# -- Decorators --\n\n# Accept plugin to class decorator\n\n\ndef acceptPlugins(base_class):\n    class_name = base_class.__name__\n    plugin_manager.pluggable[class_name] = base_class\n    if class_name in plugin_manager.plugins:  # Has plugins\n        classes = plugin_manager.plugins[class_name][:]  # Copy the current plugins\n\n        # Restore the subclass order after reload\n        if class_name in plugin_manager.subclass_order:\n            classes = sorted(\n                classes,\n                key=lambda key:\n                    plugin_manager.subclass_order[class_name].index(str(key))\n                    if str(key) in plugin_manager.subclass_order[class_name]\n                    else 9999\n            )\n        plugin_manager.subclass_order[class_name] = list(map(str, classes))\n\n        classes.reverse()\n        classes.append(base_class)  # Add the class itself to end of inherience line\n        plugined_class = type(class_name, tuple(classes), dict())  # Create the plugined class\n        plugin_manager.log.debug(\"New class accepts plugins: %s (Loaded plugins: %s)\" % (class_name, classes))\n    else:  # No plugins just use the original\n        plugined_class = base_class\n    return plugined_class\n\n\n# Register plugin to class name decorator\ndef registerTo(class_name):\n    if config.debug and not plugin_manager.reloading:\n        import gc\n        for obj in gc.get_objects():\n            if type(obj).__name__ == class_name:\n                raise Exception(\"Class %s instances already present in memory\" % class_name)\n                break\n\n    plugin_manager.log.debug(\"New plugin registered to: %s\" % class_name)\n    if class_name not in plugin_manager.plugins:\n        plugin_manager.plugins[class_name] = []\n\n    def classDecorator(self):\n        plugin_manager.plugins[class_name].append(self)\n        return self\n    return classDecorator\n\n\ndef afterLoad(func):\n    plugin_manager.after_load.append(func)\n    return func\n\n\n# - Example usage -\n\nif __name__ == \"__main__\":\n    @registerTo(\"Request\")\n    class RequestPlugin(object):\n\n        def actionMainPage(self, path):\n            return \"Hello MainPage!\"\n\n    @acceptPlugins\n    class Request(object):\n\n        def route(self, path):\n            func = getattr(self, \"action\" + path, None)\n            if func:\n                return func(path)\n            else:\n                return \"Can't route to\", path\n\n    print(Request().route(\"MainPage\"))\n"
  },
  {
    "path": "src/Plugin/__init__.py",
    "content": ""
  },
  {
    "path": "src/Site/Site.py",
    "content": "import os\nimport json\nimport logging\nimport re\nimport time\nimport random\nimport sys\nimport hashlib\nimport collections\nimport base64\n\nimport gevent\nimport gevent.pool\n\nimport util\nfrom Config import config\nfrom Peer import Peer\nfrom Worker import WorkerManager\nfrom Debug import Debug\nfrom Content import ContentManager\nfrom .SiteStorage import SiteStorage\nfrom Crypt import CryptHash\nfrom util import helper\nfrom util import Diff\nfrom util import GreenletManager\nfrom Plugin import PluginManager\nfrom File import FileServer\nfrom .SiteAnnouncer import SiteAnnouncer\nfrom . import SiteManager\n\n\n@PluginManager.acceptPlugins\nclass Site(object):\n\n    def __init__(self, address, allow_create=True, settings=None):\n        self.address = str(re.sub(\"[^A-Za-z0-9]\", \"\", address))  # Make sure its correct address\n        self.address_hash = hashlib.sha256(self.address.encode(\"ascii\")).digest()\n        self.address_sha1 = hashlib.sha1(self.address.encode(\"ascii\")).digest()\n        self.address_short = \"%s..%s\" % (self.address[:6], self.address[-4:])  # Short address for logging\n        self.log = logging.getLogger(\"Site:%s\" % self.address_short)\n        self.addEventListeners()\n\n        self.content = None  # Load content.json\n        self.peers = {}  # Key: ip:port, Value: Peer.Peer\n        self.peers_recent = collections.deque(maxlen=150)\n        self.peer_blacklist = SiteManager.peer_blacklist  # Ignore this peers (eg. myself)\n        self.greenlet_manager = GreenletManager.GreenletManager()  # Running greenlets\n        self.worker_manager = WorkerManager(self)  # Handle site download from other peers\n        self.bad_files = {}  # SHA check failed files, need to redownload {\"inner.content\": 1} (key: file, value: failed accept)\n        self.content_updated = None  # Content.js update time\n        self.notifications = []  # Pending notifications displayed once on page load [error|ok|info, message, timeout]\n        self.page_requested = False  # Page viewed in browser\n        self.websockets = []  # Active site websocket connections\n\n        self.connection_server = None\n        self.loadSettings(settings)  # Load settings from sites.json\n        self.storage = SiteStorage(self, allow_create=allow_create)  # Save and load site files\n        self.content_manager = ContentManager(self)\n        self.content_manager.loadContents()  # Load content.json files\n        if \"main\" in sys.modules:  # import main has side-effects, breaks tests\n            import main\n            if \"file_server\" in dir(main):  # Use global file server by default if possible\n                self.connection_server = main.file_server\n            else:\n                main.file_server = FileServer()\n                self.connection_server = main.file_server\n        else:\n            self.connection_server = FileServer()\n\n        self.announcer = SiteAnnouncer(self)  # Announce and get peer list from other nodes\n\n        if not self.settings.get(\"wrapper_key\"):  # To auth websocket permissions\n            self.settings[\"wrapper_key\"] = CryptHash.random()\n            self.log.debug(\"New wrapper key: %s\" % self.settings[\"wrapper_key\"])\n\n        if not self.settings.get(\"ajax_key\"):  # To auth websocket permissions\n            self.settings[\"ajax_key\"] = CryptHash.random()\n            self.log.debug(\"New ajax key: %s\" % self.settings[\"ajax_key\"])\n\n    def __str__(self):\n        return \"Site %s\" % self.address_short\n\n    def __repr__(self):\n        return \"<%s>\" % self.__str__()\n\n    # Load site settings from data/sites.json\n    def loadSettings(self, settings=None):\n        if not settings:\n            settings = json.load(open(\"%s/sites.json\" % config.data_dir)).get(self.address)\n        if settings:\n            self.settings = settings\n            if \"cache\" not in settings:\n                settings[\"cache\"] = {}\n            if \"size_files_optional\" not in settings:\n                settings[\"size_optional\"] = 0\n            if \"optional_downloaded\" not in settings:\n                settings[\"optional_downloaded\"] = 0\n            if \"downloaded\" not in settings:\n                settings[\"downloaded\"] = settings.get(\"added\")\n            self.bad_files = settings[\"cache\"].get(\"bad_files\", {})\n            settings[\"cache\"][\"bad_files\"] = {}\n            # Give it minimum 10 tries after restart\n            for inner_path in self.bad_files:\n                self.bad_files[inner_path] = min(self.bad_files[inner_path], 20)\n        else:\n            self.settings = {\n                \"own\": False, \"serving\": True, \"permissions\": [], \"cache\": {\"bad_files\": {}}, \"size_files_optional\": 0,\n                \"added\": int(time.time()), \"downloaded\": None, \"optional_downloaded\": 0, \"size_optional\": 0\n            }  # Default\n            if config.download_optional == \"auto\":\n                self.settings[\"autodownloadoptional\"] = True\n\n        # Add admin permissions to homepage\n        if self.address in (config.homepage, config.updatesite) and \"ADMIN\" not in self.settings[\"permissions\"]:\n            self.settings[\"permissions\"].append(\"ADMIN\")\n\n        return\n\n    # Save site settings to data/sites.json\n    def saveSettings(self):\n        if not SiteManager.site_manager.sites:\n            SiteManager.site_manager.sites = {}\n        if not SiteManager.site_manager.sites.get(self.address):\n            SiteManager.site_manager.sites[self.address] = self\n            SiteManager.site_manager.load(False)\n        SiteManager.site_manager.saveDelayed()\n\n    def isServing(self):\n        if config.offline:\n            return False\n        else:\n            return self.settings[\"serving\"]\n\n    def getSettingsCache(self):\n        back = {}\n        back[\"bad_files\"] = self.bad_files\n        back[\"hashfield\"] = base64.b64encode(self.content_manager.hashfield.tobytes()).decode(\"ascii\")\n        return back\n\n    # Max site size in MB\n    def getSizeLimit(self):\n        return self.settings.get(\"size_limit\", int(config.size_limit))\n\n    # Next size limit based on current size\n    def getNextSizeLimit(self):\n        size_limits = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000]\n        size = self.settings.get(\"size\", 0)\n        for size_limit in size_limits:\n            if size * 1.2 < size_limit * 1024 * 1024:\n                return size_limit\n        return 999999\n\n    def isAddedRecently(self):\n        return time.time() - self.settings.get(\"added\", 0) < 60 * 60 * 24\n\n    # Download all file from content.json\n    def downloadContent(self, inner_path, download_files=True, peer=None, check_modifications=False, diffs={}):\n        s = time.time()\n        if config.verbose:\n            self.log.debug(\n                \"DownloadContent %s: Started. (download_files: %s, check_modifications: %s, diffs: %s)...\" %\n                (inner_path, download_files, check_modifications, diffs.keys())\n            )\n\n        if not inner_path.endswith(\"content.json\"):\n            return False\n\n        found = self.needFile(inner_path, update=self.bad_files.get(inner_path))\n        content_inner_dir = helper.getDirname(inner_path)\n        if not found:\n            self.log.debug(\"DownloadContent %s: Download failed, check_modifications: %s\" % (inner_path, check_modifications))\n            if check_modifications:  # Download failed, but check modifications if its succed later\n                self.onFileDone.once(lambda file_name: self.checkModifications(0), \"check_modifications\")\n            return False  # Could not download content.json\n\n        if config.verbose:\n            self.log.debug(\"DownloadContent got %s\" % inner_path)\n            sub_s = time.time()\n\n        changed, deleted = self.content_manager.loadContent(inner_path, load_includes=False)\n\n        if config.verbose:\n            self.log.debug(\"DownloadContent %s: loadContent done in %.3fs\" % (inner_path, time.time() - sub_s))\n\n        if inner_path == \"content.json\":\n            self.saveSettings()\n\n        if peer:  # Update last received update from peer to prevent re-sending the same update to it\n            peer.last_content_json_update = self.content_manager.contents[inner_path][\"modified\"]\n\n        # Verify size limit\n        if inner_path == \"content.json\":\n            site_size_limit = self.getSizeLimit() * 1024 * 1024\n            content_size = len(json.dumps(self.content_manager.contents[inner_path], indent=1)) + sum([file[\"size\"] for file in list(self.content_manager.contents[inner_path].get(\"files\", {}).values()) if file[\"size\"] >= 0])  # Size of new content\n            if site_size_limit < content_size:\n                # Not enought don't download anything\n                self.log.debug(\"DownloadContent Size limit reached (site too big please increase limit): %.2f MB > %.2f MB\" % (content_size / 1024 / 1024, site_size_limit / 1024 / 1024))\n                return False\n\n        # Start download files\n        file_threads = []\n        if download_files:\n            for file_relative_path in list(self.content_manager.contents[inner_path].get(\"files\", {}).keys()):\n                file_inner_path = content_inner_dir + file_relative_path\n\n                # Try to diff first\n                diff_success = False\n                diff_actions = diffs.get(file_relative_path)\n                if diff_actions and self.bad_files.get(file_inner_path):\n                    try:\n                        s = time.time()\n                        new_file = Diff.patch(self.storage.open(file_inner_path, \"rb\"), diff_actions)\n                        new_file.seek(0)\n                        time_diff = time.time() - s\n\n                        s = time.time()\n                        diff_success = self.content_manager.verifyFile(file_inner_path, new_file)\n                        time_verify = time.time() - s\n\n                        if diff_success:\n                            s = time.time()\n                            new_file.seek(0)\n                            self.storage.write(file_inner_path, new_file)\n                            time_write = time.time() - s\n\n                            s = time.time()\n                            self.onFileDone(file_inner_path)\n                            time_on_done = time.time() - s\n\n                            self.log.debug(\n                                \"DownloadContent Patched successfully: %s (diff: %.3fs, verify: %.3fs, write: %.3fs, on_done: %.3fs)\" %\n                                (file_inner_path, time_diff, time_verify, time_write, time_on_done)\n                            )\n                    except Exception as err:\n                        self.log.debug(\"DownloadContent Failed to patch %s: %s\" % (file_inner_path, err))\n                        diff_success = False\n\n                if not diff_success:\n                    # Start download and dont wait for finish, return the event\n                    res = self.needFile(file_inner_path, blocking=False, update=self.bad_files.get(file_inner_path), peer=peer)\n                    if res is not True and res is not False:  # Need downloading and file is allowed\n                        file_threads.append(res)  # Append evt\n\n            # Optionals files\n            if inner_path == \"content.json\":\n                gevent.spawn(self.updateHashfield)\n\n            for file_relative_path in list(self.content_manager.contents[inner_path].get(\"files_optional\", {}).keys()):\n                file_inner_path = content_inner_dir + file_relative_path\n                if file_inner_path not in changed and not self.bad_files.get(file_inner_path):\n                    continue\n                if not self.isDownloadable(file_inner_path):\n                    continue\n                # Start download and dont wait for finish, return the event\n                res = self.pooledNeedFile(\n                    file_inner_path, blocking=False, update=self.bad_files.get(file_inner_path), peer=peer\n                )\n                if res is not True and res is not False:  # Need downloading and file is allowed\n                    file_threads.append(res)  # Append evt\n\n        # Wait for includes download\n        include_threads = []\n        for file_relative_path in list(self.content_manager.contents[inner_path].get(\"includes\", {}).keys()):\n            file_inner_path = content_inner_dir + file_relative_path\n            include_thread = gevent.spawn(self.downloadContent, file_inner_path, download_files=download_files, peer=peer)\n            include_threads.append(include_thread)\n\n        if config.verbose:\n            self.log.debug(\"DownloadContent %s: Downloading %s includes...\" % (inner_path, len(include_threads)))\n        gevent.joinall(include_threads)\n        if config.verbose:\n            self.log.debug(\"DownloadContent %s: Includes download ended\" % inner_path)\n\n        if check_modifications:  # Check if every file is up-to-date\n            self.checkModifications(0)\n\n        if config.verbose:\n            self.log.debug(\"DownloadContent %s: Downloading %s files, changed: %s...\" % (inner_path, len(file_threads), len(changed)))\n        gevent.joinall(file_threads)\n        if config.verbose:\n            self.log.debug(\"DownloadContent %s: ended in %.3fs (tasks left: %s)\" % (\n                inner_path, time.time() - s, len(self.worker_manager.tasks)\n            ))\n\n        return True\n\n    # Return bad files with less than 3 retry\n    def getReachableBadFiles(self):\n        if not self.bad_files:\n            return False\n        return [bad_file for bad_file, retry in self.bad_files.items() if retry < 3]\n\n    # Retry download bad files\n    def retryBadFiles(self, force=False):\n        self.checkBadFiles()\n\n        self.log.debug(\"Retry %s bad files\" % len(self.bad_files))\n        content_inner_paths = []\n        file_inner_paths = []\n\n        for bad_file, tries in list(self.bad_files.items()):\n            if force or random.randint(0, min(40, tries)) < 4:  # Larger number tries = less likely to check every 15min\n                if bad_file.endswith(\"content.json\"):\n                    content_inner_paths.append(bad_file)\n                else:\n                    file_inner_paths.append(bad_file)\n\n        if content_inner_paths:\n            self.pooledDownloadContent(content_inner_paths, only_if_bad=True)\n\n        if file_inner_paths:\n            self.pooledDownloadFile(file_inner_paths, only_if_bad=True)\n\n    def checkBadFiles(self):\n        for bad_file in list(self.bad_files.keys()):\n            file_info = self.content_manager.getFileInfo(bad_file)\n            if bad_file.endswith(\"content.json\"):\n                if file_info is False and bad_file != \"content.json\":\n                    del self.bad_files[bad_file]\n                    self.log.debug(\"No info for file: %s, removing from bad_files\" % bad_file)\n            else:\n                if file_info is False or not file_info.get(\"size\"):\n                    del self.bad_files[bad_file]\n                    self.log.debug(\"No info or size for file: %s, removing from bad_files\" % bad_file)\n\n    # Download all files of the site\n    @util.Noparallel(blocking=False)\n    def download(self, check_size=False, blind_includes=False, retry_bad_files=True):\n        if not self.connection_server:\n            self.log.debug(\"No connection server found, skipping download\")\n            return False\n\n        s = time.time()\n        self.log.debug(\n            \"Start downloading, bad_files: %s, check_size: %s, blind_includes: %s, isAddedRecently: %s\" %\n            (self.bad_files, check_size, blind_includes, self.isAddedRecently())\n        )\n\n        if self.isAddedRecently():\n            gevent.spawn(self.announce, mode=\"start\", force=True)\n        else:\n            gevent.spawn(self.announce, mode=\"update\")\n\n        if check_size:  # Check the size first\n            valid = self.downloadContent(\"content.json\", download_files=False)  # Just download content.json files\n            if not valid:\n                return False  # Cant download content.jsons or size is not fits\n\n        # Download everything\n        valid = self.downloadContent(\"content.json\", check_modifications=blind_includes)\n\n        if retry_bad_files:\n            self.onComplete.once(lambda: self.retryBadFiles(force=True))\n        self.log.debug(\"Download done in %.3fs\" % (time.time() - s))\n\n        return valid\n\n    def pooledDownloadContent(self, inner_paths, pool_size=100, only_if_bad=False):\n        self.log.debug(\"New downloadContent pool: len: %s, only if bad: %s\" % (len(inner_paths), only_if_bad))\n        self.worker_manager.started_task_num += len(inner_paths)\n        pool = gevent.pool.Pool(pool_size)\n        num_skipped = 0\n        site_size_limit = self.getSizeLimit() * 1024 * 1024\n        for inner_path in inner_paths:\n            if not only_if_bad or inner_path in self.bad_files:\n                pool.spawn(self.downloadContent, inner_path)\n            else:\n                num_skipped += 1\n            self.worker_manager.started_task_num -= 1\n            if self.settings[\"size\"] > site_size_limit * 0.95:\n                self.log.warning(\"Site size limit almost reached, aborting downloadContent pool\")\n                for aborted_inner_path in inner_paths:\n                    if aborted_inner_path in self.bad_files:\n                        del self.bad_files[aborted_inner_path]\n                self.worker_manager.removeSolvedFileTasks(mark_as_good=False)\n                break\n        pool.join()\n        self.log.debug(\"Ended downloadContent pool len: %s, skipped: %s\" % (len(inner_paths), num_skipped))\n\n    def pooledDownloadFile(self, inner_paths, pool_size=100, only_if_bad=False):\n        self.log.debug(\"New downloadFile pool: len: %s, only if bad: %s\" % (len(inner_paths), only_if_bad))\n        self.worker_manager.started_task_num += len(inner_paths)\n        pool = gevent.pool.Pool(pool_size)\n        num_skipped = 0\n        for inner_path in inner_paths:\n            if not only_if_bad or inner_path in self.bad_files:\n                pool.spawn(self.needFile, inner_path, update=True)\n            else:\n                num_skipped += 1\n            self.worker_manager.started_task_num -= 1\n        self.log.debug(\"Ended downloadFile pool len: %s, skipped: %s\" % (len(inner_paths), num_skipped))\n\n    # Update worker, try to find client that supports listModifications command\n    def updater(self, peers_try, queried, since):\n        threads = []\n        while 1:\n            if not peers_try or len(queried) >= 3:  # Stop after 3 successful query\n                break\n            peer = peers_try.pop(0)\n            if config.verbose:\n                self.log.debug(\"CheckModifications: Try to get updates from: %s Left: %s\" % (peer, peers_try))\n\n            res = None\n            with gevent.Timeout(20, exception=False):\n                res = peer.listModified(since)\n\n            if not res or \"modified_files\" not in res:\n                continue  # Failed query\n\n            queried.append(peer)\n            modified_contents = []\n            my_modified = self.content_manager.listModified(since)\n            num_old_files = 0\n            for inner_path, modified in res[\"modified_files\"].items():  # Check if the peer has newer files than we\n                has_newer = int(modified) > my_modified.get(inner_path, 0)\n                has_older = int(modified) < my_modified.get(inner_path, 0)\n                if inner_path not in self.bad_files and not self.content_manager.isArchived(inner_path, modified):\n                    if has_newer:\n                        # We dont have this file or we have older\n                        modified_contents.append(inner_path)\n                        self.bad_files[inner_path] = self.bad_files.get(inner_path, 0) + 1\n                    if has_older and num_old_files < 5:\n                        num_old_files += 1\n                        self.log.debug(\"CheckModifications: %s client has older version of %s, publishing there (%s/5)...\" % (peer, inner_path, num_old_files))\n                        gevent.spawn(self.publisher, inner_path, [peer], [], 1)\n            if modified_contents:\n                self.log.debug(\"CheckModifications: %s new modified file from %s\" % (len(modified_contents), peer))\n                modified_contents.sort(key=lambda inner_path: 0 - res[\"modified_files\"][inner_path])  # Download newest first\n                t = gevent.spawn(self.pooledDownloadContent, modified_contents, only_if_bad=True)\n                threads.append(t)\n        if config.verbose:\n            self.log.debug(\"CheckModifications: Waiting for %s pooledDownloadContent\" % len(threads))\n        gevent.joinall(threads)\n\n    # Check modified content.json files from peers and add modified files to bad_files\n    # Return: Successfully queried peers [Peer, Peer...]\n    def checkModifications(self, since=None):\n        s = time.time()\n        peers_try = []  # Try these peers\n        queried = []  # Successfully queried from these peers\n        limit = 5\n\n        # Wait for peers\n        if not self.peers:\n            self.announce(mode=\"update\")\n            for wait in range(10):\n                time.sleep(5 + wait)\n                self.log.debug(\"CheckModifications: Waiting for peers...\")\n                if self.peers:\n                    break\n\n        peers_try = self.getConnectedPeers()\n        peers_connected_num = len(peers_try)\n        if peers_connected_num < limit * 2:  # Add more, non-connected peers if necessary\n            peers_try += self.getRecentPeers(limit * 5)\n\n        if since is None:  # No since defined, download from last modification time-1day\n            since = self.settings.get(\"modified\", 60 * 60 * 24) - 60 * 60 * 24\n\n        if config.verbose:\n            self.log.debug(\n                \"CheckModifications: Try to get listModifications from peers: %s, connected: %s, since: %s\" %\n                (peers_try, peers_connected_num, since)\n            )\n\n        updaters = []\n        for i in range(3):\n            updaters.append(gevent.spawn(self.updater, peers_try, queried, since))\n\n        gevent.joinall(updaters, timeout=10)  # Wait 10 sec to workers done query modifications\n\n        if not queried:  # Start another 3 thread if first 3 is stuck\n            peers_try[0:0] = [peer for peer in self.getConnectedPeers() if peer.connection.connected]  # Add connected peers\n            for _ in range(10):\n                gevent.joinall(updaters, timeout=10)  # Wait another 10 sec if none of updaters finished\n                if queried:\n                    break\n\n        self.log.debug(\"CheckModifications: Queried listModifications from: %s in %.3fs since %s\" % (queried, time.time() - s, since))\n        time.sleep(0.1)\n        return queried\n\n    # Update content.json from peers and download changed files\n    # Return: None\n    @util.Noparallel()\n    def update(self, announce=False, check_files=False, since=None):\n        self.content_manager.loadContent(\"content.json\", load_includes=False)  # Reload content.json\n        self.content_updated = None  # Reset content updated time\n\n        if check_files:\n            self.storage.updateBadFiles(quick_check=True)  # Quick check and mark bad files based on file size\n\n        if not self.isServing():\n            return False\n\n        self.updateWebsocket(updating=True)\n\n        # Remove files that no longer in content.json\n        self.checkBadFiles()\n\n        if announce:\n            self.announce(mode=\"update\", force=True)\n\n        # Full update, we can reset bad files\n        if check_files and since == 0:\n            self.bad_files = {}\n\n        queried = self.checkModifications(since)\n\n        changed, deleted = self.content_manager.loadContent(\"content.json\", load_includes=False)\n\n        if self.bad_files:\n            self.log.debug(\"Bad files: %s\" % self.bad_files)\n            gevent.spawn(self.retryBadFiles, force=True)\n\n        if len(queried) == 0:\n            # Failed to query modifications\n            self.content_updated = False\n        else:\n            self.content_updated = time.time()\n\n        self.updateWebsocket(updated=True)\n\n    # Update site by redownload all content.json\n    def redownloadContents(self):\n        # Download all content.json again\n        content_threads = []\n        for inner_path in list(self.content_manager.contents.keys()):\n            content_threads.append(self.needFile(inner_path, update=True, blocking=False))\n\n        self.log.debug(\"Waiting %s content.json to finish...\" % len(content_threads))\n        gevent.joinall(content_threads)\n\n    # Publish worker\n    def publisher(self, inner_path, peers, published, limit, diffs={}, event_done=None, cb_progress=None):\n        file_size = self.storage.getSize(inner_path)\n        content_json_modified = self.content_manager.contents[inner_path][\"modified\"]\n        body = self.storage.read(inner_path)\n\n        while 1:\n            if not peers or len(published) >= limit:\n                if event_done:\n                    event_done.set(True)\n                break  # All peers done, or published engouht\n            peer = peers.pop()\n            if peer in published:\n                continue\n            if peer.last_content_json_update == content_json_modified:\n                self.log.debug(\"%s already received this update for %s, skipping\" % (peer, inner_path))\n                continue\n\n            if peer.connection and peer.connection.last_ping_delay:  # Peer connected\n                # Timeout: 5sec + size in kb + last_ping\n                timeout = 5 + int(file_size / 1024) + peer.connection.last_ping_delay\n            else:  # Peer not connected\n                # Timeout: 10sec + size in kb\n                timeout = 10 + int(file_size / 1024)\n            result = {\"exception\": \"Timeout\"}\n\n            for retry in range(2):\n                try:\n                    with gevent.Timeout(timeout, False):\n                        result = peer.publish(self.address, inner_path, body, content_json_modified, diffs)\n                    if result:\n                        break\n                except Exception as err:\n                    self.log.error(\"Publish error: %s\" % Debug.formatException(err))\n                    result = {\"exception\": Debug.formatException(err)}\n\n            if result and \"ok\" in result:\n                published.append(peer)\n                if cb_progress and len(published) <= limit:\n                    cb_progress(len(published), limit)\n                self.log.info(\"[OK] %s: %s %s/%s\" % (peer.key, result[\"ok\"], len(published), limit))\n            else:\n                if result == {\"exception\": \"Timeout\"}:\n                    peer.onConnectionError(\"Publish timeout\")\n                self.log.info(\"[FAILED] %s: %s\" % (peer.key, result))\n            time.sleep(0.01)\n\n    # Update content.json on peers\n    @util.Noparallel()\n    def publish(self, limit=\"default\", inner_path=\"content.json\", diffs={}, cb_progress=None):\n        published = []  # Successfully published (Peer)\n        publishers = []  # Publisher threads\n\n        if not self.peers:\n            self.announce(mode=\"more\")\n\n        if limit == \"default\":\n            limit = 5\n        threads = limit\n\n        peers = self.getConnectedPeers()\n        num_connected_peers = len(peers)\n\n        random.shuffle(peers)\n        peers = sorted(peers, key=lambda peer: peer.connection.handshake.get(\"rev\", 0) < config.rev - 100)  # Prefer newer clients\n\n        if len(peers) < limit * 2 and len(self.peers) > len(peers):  # Add more, non-connected peers if necessary\n            peers += self.getRecentPeers(limit * 2)\n\n        peers = set(peers)\n\n        self.log.info(\"Publishing %s to %s/%s peers (connected: %s) diffs: %s (%.2fk)...\" % (\n            inner_path, limit, len(self.peers), num_connected_peers, list(diffs.keys()), float(len(str(diffs))) / 1024\n        ))\n\n        if not peers:\n            return 0  # No peers found\n\n        event_done = gevent.event.AsyncResult()\n        for i in range(min(len(peers), limit, threads)):\n            publisher = gevent.spawn(self.publisher, inner_path, peers, published, limit, diffs, event_done, cb_progress)\n            publishers.append(publisher)\n\n        event_done.get()  # Wait for done\n        if len(published) < min(len(self.peers), limit):\n            time.sleep(0.2)  # If less than we need sleep a bit\n        if len(published) == 0:\n            gevent.joinall(publishers)  # No successful publish, wait for all publisher\n\n        # Publish more peers in the backgroup\n        self.log.info(\n            \"Published %s to %s peers, publishing to %s more peers in the background\" %\n            (inner_path, len(published), limit)\n        )\n\n        for thread in range(2):\n            gevent.spawn(self.publisher, inner_path, peers, published, limit=limit * 2, diffs=diffs)\n\n        # Send my hashfield to every connected peer if changed\n        gevent.spawn(self.sendMyHashfield, 100)\n\n        return len(published)\n\n    # Copy this site\n    @util.Noparallel()\n    def clone(self, address, privatekey=None, address_index=None, root_inner_path=\"\", overwrite=False):\n        import shutil\n        new_site = SiteManager.site_manager.need(address, all_file=False)\n        default_dirs = []  # Dont copy these directories (has -default version)\n        for dir_name in os.listdir(self.storage.directory):\n            if \"-default\" in dir_name:\n                default_dirs.append(dir_name.replace(\"-default\", \"\"))\n\n        self.log.debug(\"Cloning to %s, ignore dirs: %s, root: %s\" % (address, default_dirs, root_inner_path))\n\n        # Copy root content.json\n        if not new_site.storage.isFile(\"content.json\") and not overwrite:\n            # New site: Content.json not exist yet, create a new one from source site\n            if \"size_limit\" in self.settings:\n                new_site.settings[\"size_limit\"] = self.settings[\"size_limit\"]\n\n            # Use content.json-default is specified\n            if self.storage.isFile(root_inner_path + \"/content.json-default\"):\n                content_json = self.storage.loadJson(root_inner_path + \"/content.json-default\")\n            else:\n                content_json = self.storage.loadJson(\"content.json\")\n\n            if \"domain\" in content_json:\n                del content_json[\"domain\"]\n            content_json[\"title\"] = \"my\" + content_json[\"title\"]\n            content_json[\"cloned_from\"] = self.address\n            content_json[\"clone_root\"] = root_inner_path\n            content_json[\"files\"] = {}\n            if address_index:\n                content_json[\"address_index\"] = address_index  # Site owner's BIP32 index\n            new_site.storage.writeJson(\"content.json\", content_json)\n            new_site.content_manager.loadContent(\n                \"content.json\", add_bad_files=False, delete_removed_files=False, load_includes=False\n            )\n\n        # Copy files\n        for content_inner_path, content in list(self.content_manager.contents.items()):\n            file_relative_paths = list(content.get(\"files\", {}).keys())\n\n            # Sign content.json at the end to make sure every file is included\n            file_relative_paths.sort()\n            file_relative_paths.sort(key=lambda key: key.replace(\"-default\", \"\").endswith(\"content.json\"))\n\n            for file_relative_path in file_relative_paths:\n                file_inner_path = helper.getDirname(content_inner_path) + file_relative_path  # Relative to content.json\n                file_inner_path = file_inner_path.strip(\"/\")  # Strip leading /\n                if not file_inner_path.startswith(root_inner_path):\n                    self.log.debug(\"[SKIP] %s (not in clone root)\" % file_inner_path)\n                    continue\n                if file_inner_path.split(\"/\")[0] in default_dirs:  # Dont copy directories that has -default postfixed alternative\n                    self.log.debug(\"[SKIP] %s (has default alternative)\" % file_inner_path)\n                    continue\n                file_path = self.storage.getPath(file_inner_path)\n\n                # Copy the file normally to keep the -default postfixed dir and file to allow cloning later\n                if root_inner_path:\n                    file_inner_path_dest = re.sub(\"^%s/\" % re.escape(root_inner_path), \"\", file_inner_path)\n                    file_path_dest = new_site.storage.getPath(file_inner_path_dest)\n                else:\n                    file_inner_path_dest = file_inner_path\n                    file_path_dest = new_site.storage.getPath(file_inner_path)\n\n                self.log.debug(\"[COPY] %s to %s...\" % (file_inner_path, file_path_dest))\n                dest_dir = os.path.dirname(file_path_dest)\n                if not os.path.isdir(dest_dir):\n                    os.makedirs(dest_dir)\n                if file_inner_path_dest.replace(\"-default\", \"\") == \"content.json\":  # Don't copy root content.json-default\n                    continue\n\n                shutil.copy(file_path, file_path_dest)\n\n                # If -default in path, create a -default less copy of the file\n                if \"-default\" in file_inner_path_dest:\n                    file_path_dest = new_site.storage.getPath(file_inner_path_dest.replace(\"-default\", \"\"))\n                    if new_site.storage.isFile(file_inner_path_dest.replace(\"-default\", \"\")) and not overwrite:\n                        # Don't overwrite site files with default ones\n                        self.log.debug(\"[SKIP] Default file: %s (already exist)\" % file_inner_path)\n                        continue\n                    self.log.debug(\"[COPY] Default file: %s to %s...\" % (file_inner_path, file_path_dest))\n                    dest_dir = os.path.dirname(file_path_dest)\n                    if not os.path.isdir(dest_dir):\n                        os.makedirs(dest_dir)\n                    shutil.copy(file_path, file_path_dest)\n                    # Sign if content json\n                    if file_path_dest.endswith(\"/content.json\"):\n                        new_site.storage.onUpdated(file_inner_path_dest.replace(\"-default\", \"\"))\n                        new_site.content_manager.loadContent(\n                            file_inner_path_dest.replace(\"-default\", \"\"), add_bad_files=False,\n                            delete_removed_files=False, load_includes=False\n                        )\n                        if privatekey:\n                            new_site.content_manager.sign(file_inner_path_dest.replace(\"-default\", \"\"), privatekey, remove_missing_optional=True)\n                            new_site.content_manager.loadContent(\n                                file_inner_path_dest, add_bad_files=False, delete_removed_files=False, load_includes=False\n                            )\n\n        if privatekey:\n            new_site.content_manager.sign(\"content.json\", privatekey, remove_missing_optional=True)\n            new_site.content_manager.loadContent(\n                \"content.json\", add_bad_files=False, delete_removed_files=False, load_includes=False\n            )\n\n        # Rebuild DB\n        if new_site.storage.isFile(\"dbschema.json\"):\n            new_site.storage.closeDb()\n            try:\n                new_site.storage.rebuildDb()\n            except Exception as err:\n                self.log.error(err)\n\n        return new_site\n\n    @util.Pooled(100)\n    def pooledNeedFile(self, *args, **kwargs):\n        return self.needFile(*args, **kwargs)\n\n    def isFileDownloadAllowed(self, inner_path, file_info):\n        # Verify space for all site\n        if self.settings[\"size\"] > self.getSizeLimit() * 1024 * 1024:\n            return False\n        # Verify space for file\n        if file_info.get(\"size\", 0) > config.file_size_limit * 1024 * 1024:\n            self.log.debug(\n                \"File size %s too large: %sMB > %sMB, skipping...\" %\n                (inner_path, file_info.get(\"size\", 0) / 1024 / 1024, config.file_size_limit)\n            )\n            return False\n        else:\n            return True\n\n    def needFileInfo(self, inner_path):\n        file_info = self.content_manager.getFileInfo(inner_path)\n        if not file_info:\n            # No info for file, download all content.json first\n            self.log.debug(\"No info for %s, waiting for all content.json\" % inner_path)\n            success = self.downloadContent(\"content.json\", download_files=False)\n            if not success:\n                return False\n            file_info = self.content_manager.getFileInfo(inner_path)\n        return file_info\n\n    # Check and download if file not exist\n    def needFile(self, inner_path, update=False, blocking=True, peer=None, priority=0):\n        if self.worker_manager.tasks.findTask(inner_path):\n            task = self.worker_manager.addTask(inner_path, peer, priority=priority)\n            if blocking:\n                return task[\"evt\"].get()\n            else:\n                return task[\"evt\"]\n        elif self.storage.isFile(inner_path) and not update:  # File exist, no need to do anything\n            return True\n        elif not self.isServing():  # Site not serving\n            return False\n        else:  # Wait until file downloaded\n            if not self.content_manager.contents.get(\"content.json\"):  # No content.json, download it first!\n                self.log.debug(\"Need content.json first (inner_path: %s, priority: %s)\" % (inner_path, priority))\n                if priority > 0:\n                    gevent.spawn(self.announce)\n                if inner_path != \"content.json\":  # Prevent double download\n                    task = self.worker_manager.addTask(\"content.json\", peer)\n                    task[\"evt\"].get()\n                    self.content_manager.loadContent()\n                    if not self.content_manager.contents.get(\"content.json\"):\n                        return False  # Content.json download failed\n\n            file_info = None\n            if not inner_path.endswith(\"content.json\"):\n                file_info = self.needFileInfo(inner_path)\n                if not file_info:\n                    return False\n                if \"cert_signers\" in file_info and not file_info[\"content_inner_path\"] in self.content_manager.contents:\n                    self.log.debug(\"Missing content.json for requested user file: %s\" % inner_path)\n                    if self.bad_files.get(file_info[\"content_inner_path\"], 0) > 5:\n                        self.log.debug(\"File %s not reachable: retry %s\" % (\n                            inner_path, self.bad_files.get(file_info[\"content_inner_path\"], 0)\n                        ))\n                        return False\n                    self.downloadContent(file_info[\"content_inner_path\"])\n\n                if not self.isFileDownloadAllowed(inner_path, file_info):\n                    self.log.debug(\"%s: Download not allowed\" % inner_path)\n                    return False\n\n            self.bad_files[inner_path] = self.bad_files.get(inner_path, 0) + 1  # Mark as bad file\n\n            task = self.worker_manager.addTask(inner_path, peer, priority=priority, file_info=file_info)\n            if blocking:\n                return task[\"evt\"].get()\n            else:\n                return task[\"evt\"]\n\n    # Add or update a peer to site\n    # return_peer: Always return the peer even if it was already present\n    def addPeer(self, ip, port, return_peer=False, connection=None, source=\"other\"):\n        if not ip or ip == \"0.0.0.0\":\n            return False\n\n        key = \"%s:%s\" % (ip, port)\n        peer = self.peers.get(key)\n        if peer:  # Already has this ip\n            peer.found(source)\n            if return_peer:  # Always return peer\n                return peer\n            else:\n                return False\n        else:  # New peer\n            if (ip, port) in self.peer_blacklist:\n                return False  # Ignore blacklist (eg. myself)\n            peer = Peer(ip, port, self)\n            self.peers[key] = peer\n            peer.found(source)\n            return peer\n\n    def announce(self, *args, **kwargs):\n        if self.isServing():\n            self.announcer.announce(*args, **kwargs)\n\n    # Keep connections to get the updates\n    def needConnections(self, num=None, check_site_on_reconnect=False):\n        if num is None:\n            if len(self.peers) < 50:\n                num = 3\n            else:\n                num = 6\n        need = min(len(self.peers), num, config.connected_limit)  # Need 5 peer, but max total peers\n\n        connected = len(self.getConnectedPeers())\n\n        connected_before = connected\n\n        self.log.debug(\"Need connections: %s, Current: %s, Total: %s\" % (need, connected, len(self.peers)))\n\n        if connected < need:  # Need more than we have\n            for peer in self.getRecentPeers(30):\n                if not peer.connection or not peer.connection.connected:  # No peer connection or disconnected\n                    peer.pex()  # Initiate peer exchange\n                    if peer.connection and peer.connection.connected:\n                        connected += 1  # Successfully connected\n                if connected >= need:\n                    break\n            self.log.debug(\n                \"Connected before: %s, after: %s. Check site: %s.\" %\n                (connected_before, connected, check_site_on_reconnect)\n            )\n\n        if check_site_on_reconnect and connected_before == 0 and connected > 0 and self.connection_server.has_internet:\n            gevent.spawn(self.update, check_files=False)\n\n        return connected\n\n    # Return: Probably peers verified to be connectable recently\n    def getConnectablePeers(self, need_num=5, ignore=[], allow_private=True):\n        peers = list(self.peers.values())\n        found = []\n        for peer in peers:\n            if peer.key.endswith(\":0\"):\n                continue  # Not connectable\n            if not peer.connection:\n                continue  # No connection\n            if peer.ip.endswith(\".onion\") and not self.connection_server.tor_manager.enabled:\n                continue  # Onion not supported\n            if peer.key in ignore:\n                continue  # The requester has this peer\n            if time.time() - peer.connection.last_recv_time > 60 * 60 * 2:  # Last message more than 2 hours ago\n                peer.connection = None  # Cleanup: Dead connection\n                continue\n            if not allow_private and helper.isPrivateIp(peer.ip):\n                continue\n            found.append(peer)\n            if len(found) >= need_num:\n                break  # Found requested number of peers\n\n        if len(found) < need_num:  # Return not that good peers\n            found += [\n                peer for peer in peers\n                if not peer.key.endswith(\":0\") and\n                peer.key not in ignore and\n                (allow_private or not helper.isPrivateIp(peer.ip))\n            ][0:need_num - len(found)]\n\n        return found\n\n    # Return: Recently found peers\n    def getRecentPeers(self, need_num):\n        found = list(set(self.peers_recent))\n        self.log.debug(\n            \"Recent peers %s of %s (need: %s)\" %\n            (len(found), len(self.peers), need_num)\n        )\n\n        if len(found) >= need_num or len(found) >= len(self.peers):\n            return sorted(\n                found,\n                key=lambda peer: peer.reputation,\n                reverse=True\n            )[0:need_num]\n\n        # Add random peers\n        need_more = need_num - len(found)\n        if not self.connection_server.tor_manager.enabled:\n            peers = [peer for peer in self.peers.values() if not peer.ip.endswith(\".onion\")]\n        else:\n            peers = list(self.peers.values())\n\n        found_more = sorted(\n            peers[0:need_more * 50],\n            key=lambda peer: peer.reputation,\n            reverse=True\n        )[0:need_more * 2]\n\n        found += found_more\n\n        return found[0:need_num]\n\n    def getConnectedPeers(self):\n        back = []\n        if not self.connection_server:\n            return []\n\n        tor_manager = self.connection_server.tor_manager\n        for connection in self.connection_server.connections:\n            if not connection.connected and time.time() - connection.start_time > 20:  # Still not connected after 20s\n                continue\n            peer = self.peers.get(\"%s:%s\" % (connection.ip, connection.port))\n            if peer:\n                if connection.ip.endswith(\".onion\") and connection.target_onion and tor_manager.start_onions:\n                    # Check if the connection is made with the onion address created for the site\n                    valid_target_onions = (tor_manager.getOnion(self.address), tor_manager.getOnion(\"global\"))\n                    if connection.target_onion not in valid_target_onions:\n                        continue\n                if not peer.connection:\n                    peer.connect(connection)\n                back.append(peer)\n        return back\n\n    # Cleanup probably dead peers and close connection if too much\n    def cleanupPeers(self, peers_protected=[]):\n        peers = list(self.peers.values())\n        if len(peers) > 20:\n            # Cleanup old peers\n            removed = 0\n            if len(peers) > 1000:\n                ttl = 60 * 60 * 1\n            else:\n                ttl = 60 * 60 * 4\n\n            for peer in peers:\n                if peer.connection and peer.connection.connected:\n                    continue\n                if peer.connection and not peer.connection.connected:\n                    peer.connection = None  # Dead connection\n                if time.time() - peer.time_found > ttl:  # Not found on tracker or via pex in last 4 hour\n                    peer.remove(\"Time found expired\")\n                    removed += 1\n                if removed > len(peers) * 0.1:  # Don't remove too much at once\n                    break\n\n            if removed:\n                self.log.debug(\"Cleanup peers result: Removed %s, left: %s\" % (removed, len(self.peers)))\n\n        # Close peers over the limit\n        closed = 0\n        connected_peers = [peer for peer in self.getConnectedPeers() if peer.connection.connected]  # Only fully connected peers\n        need_to_close = len(connected_peers) - config.connected_limit\n\n        if closed < need_to_close:\n            # Try to keep connections with more sites\n            for peer in sorted(connected_peers, key=lambda peer: min(peer.connection.sites, 5)):\n                if not peer.connection:\n                    continue\n                if peer.key in peers_protected:\n                    continue\n                if peer.connection.sites > 5:\n                    break\n                peer.connection.close(\"Cleanup peers\")\n                peer.connection = None\n                closed += 1\n                if closed >= need_to_close:\n                    break\n\n        if need_to_close > 0:\n            self.log.debug(\"Connected: %s, Need to close: %s, Closed: %s\" % (len(connected_peers), need_to_close, closed))\n\n    # Send hashfield to peers\n    def sendMyHashfield(self, limit=5):\n        if not self.content_manager.hashfield:  # No optional files\n            return False\n\n        sent = 0\n        connected_peers = self.getConnectedPeers()\n        for peer in connected_peers:\n            if peer.sendMyHashfield():\n                sent += 1\n                if sent >= limit:\n                    break\n        if sent:\n            my_hashfield_changed = self.content_manager.hashfield.time_changed\n            self.log.debug(\"Sent my hashfield (chaged %.3fs ago) to %s peers\" % (time.time() - my_hashfield_changed, sent))\n        return sent\n\n    # Update hashfield\n    def updateHashfield(self, limit=5):\n        # Return if no optional files\n        if not self.content_manager.hashfield and not self.content_manager.has_optional_files:\n            return False\n\n        s = time.time()\n        queried = 0\n        connected_peers = self.getConnectedPeers()\n        for peer in connected_peers:\n            if peer.time_hashfield:\n                continue\n            if peer.updateHashfield():\n                queried += 1\n            if queried >= limit:\n                break\n        if queried:\n            self.log.debug(\"Queried hashfield from %s peers in %.3fs\" % (queried, time.time() - s))\n        return queried\n\n    # Returns if the optional file is need to be downloaded or not\n    def isDownloadable(self, inner_path):\n        return self.settings.get(\"autodownloadoptional\")\n\n    def delete(self):\n        self.log.info(\"Deleting site...\")\n        s = time.time()\n        self.settings[\"serving\"] = False\n        self.settings[\"deleting\"] = True\n        self.saveSettings()\n        num_greenlets = self.greenlet_manager.stopGreenlets(\"Site %s deleted\" % self.address)\n        self.worker_manager.running = False\n        num_workers = self.worker_manager.stopWorkers()\n        SiteManager.site_manager.delete(self.address)\n        self.content_manager.contents.db.deleteSite(self)\n        self.updateWebsocket(deleted=True)\n        self.storage.deleteFiles()\n        self.log.info(\n            \"Deleted site in %.3fs (greenlets: %s, workers: %s)\" %\n            (time.time() - s, num_greenlets, num_workers)\n        )\n\n    # - Events -\n\n    # Add event listeners\n    def addEventListeners(self):\n        self.onFileStart = util.Event()  # If WorkerManager added new task\n        self.onFileDone = util.Event()  # If WorkerManager successfully downloaded a file\n        self.onFileFail = util.Event()  # If WorkerManager failed to download a file\n        self.onComplete = util.Event()  # All file finished\n\n        self.onFileStart.append(lambda inner_path: self.fileStarted())  # No parameters to make Noparallel batching working\n        self.onFileDone.append(lambda inner_path: self.fileDone(inner_path))\n        self.onFileFail.append(lambda inner_path: self.fileFailed(inner_path))\n\n    # Send site status update to websocket clients\n    def updateWebsocket(self, **kwargs):\n        if kwargs:\n            param = {\"event\": list(kwargs.items())[0]}\n        else:\n            param = None\n        for ws in self.websockets:\n            ws.event(\"siteChanged\", self, param)\n\n    def messageWebsocket(self, message, type=\"info\", progress=None):\n        for ws in self.websockets:\n            if progress is None:\n                ws.cmd(\"notification\", [type, message])\n            else:\n                ws.cmd(\"progress\", [type, message, progress])\n\n    # File download started\n    @util.Noparallel(blocking=False)\n    def fileStarted(self):\n        time.sleep(0.001)  # Wait for other files adds\n        self.updateWebsocket(file_started=True)\n\n    # File downloaded successful\n    def fileDone(self, inner_path):\n        # File downloaded, remove it from bad files\n        if inner_path in self.bad_files:\n            if config.verbose:\n                self.log.debug(\"Bad file solved: %s\" % inner_path)\n            del(self.bad_files[inner_path])\n\n        # Update content.json last downlad time\n        if inner_path == \"content.json\":\n            if not self.settings.get(\"downloaded\"):\n                self.settings[\"downloaded\"] = int(time.time())\n            self.content_updated = time.time()\n\n        self.updateWebsocket(file_done=inner_path)\n\n    # File download failed\n    def fileFailed(self, inner_path):\n        if inner_path == \"content.json\":\n            self.content_updated = False\n            self.log.debug(\"Can't update content.json\")\n        if inner_path in self.bad_files and self.connection_server.has_internet:\n            self.bad_files[inner_path] = self.bad_files.get(inner_path, 0) + 1\n\n        self.updateWebsocket(file_failed=inner_path)\n\n        if self.bad_files.get(inner_path, 0) > 30:\n            self.fileForgot(inner_path)\n\n    def fileForgot(self, inner_path):\n        self.log.debug(\"Giving up on %s\" % inner_path)\n        del self.bad_files[inner_path]  # Give up after 30 tries\n"
  },
  {
    "path": "src/Site/SiteAnnouncer.py",
    "content": "import random\nimport time\nimport hashlib\nimport re\nimport collections\n\nimport gevent\n\nfrom Plugin import PluginManager\nfrom Config import config\nfrom Debug import Debug\nfrom util import helper\nfrom greenlet import GreenletExit\nimport util\n\n\nclass AnnounceError(Exception):\n    pass\n\nglobal_stats = collections.defaultdict(lambda: collections.defaultdict(int))\n\n\n@PluginManager.acceptPlugins\nclass SiteAnnouncer(object):\n    def __init__(self, site):\n        self.site = site\n        self.stats = {}\n        self.fileserver_port = config.fileserver_port\n        self.peer_id = self.site.connection_server.peer_id\n        self.last_tracker_id = random.randint(0, 10)\n        self.time_last_announce = 0\n\n    def getTrackers(self):\n        return config.trackers\n\n    def getSupportedTrackers(self):\n        trackers = self.getTrackers()\n\n        if not self.site.connection_server.tor_manager.enabled:\n            trackers = [tracker for tracker in trackers if \".onion\" not in tracker]\n\n        trackers = [tracker for tracker in trackers if self.getAddressParts(tracker)]  # Remove trackers with unknown address\n\n        if \"ipv6\" not in self.site.connection_server.supported_ip_types:\n            trackers = [tracker for tracker in trackers if helper.getIpType(self.getAddressParts(tracker)[\"ip\"]) != \"ipv6\"]\n\n        return trackers\n\n    def getAnnouncingTrackers(self, mode):\n        trackers = self.getSupportedTrackers()\n\n        if trackers and (mode == \"update\" or mode == \"more\"):  # Only announce on one tracker, increment the queried tracker id\n            self.last_tracker_id += 1\n            self.last_tracker_id = self.last_tracker_id % len(trackers)\n            trackers_announcing = [trackers[self.last_tracker_id]]  # We only going to use this one\n        else:\n            trackers_announcing = trackers\n\n        return trackers_announcing\n\n    def getOpenedServiceTypes(self):\n        back = []\n        # Type of addresses they can reach me\n        if config.trackers_proxy == \"disable\" and config.tor != \"always\":\n            for ip_type, opened in list(self.site.connection_server.port_opened.items()):\n                if opened:\n                    back.append(ip_type)\n        if self.site.connection_server.tor_manager.start_onions:\n            back.append(\"onion\")\n        return back\n\n    @util.Noparallel(blocking=False)\n    def announce(self, force=False, mode=\"start\", pex=True):\n        if time.time() - self.time_last_announce < 30 and not force:\n            return  # No reannouncing within 30 secs\n        if force:\n            self.site.log.debug(\"Force reannounce in mode %s\" % mode)\n\n        self.fileserver_port = config.fileserver_port\n        self.time_last_announce = time.time()\n\n        trackers = self.getAnnouncingTrackers(mode)\n\n        if config.verbose:\n            self.site.log.debug(\"Tracker announcing, trackers: %s\" % trackers)\n\n        errors = []\n        slow = []\n        s = time.time()\n        threads = []\n        num_announced = 0\n\n        for tracker in trackers:  # Start announce threads\n            tracker_stats = global_stats[tracker]\n            # Reduce the announce time for trackers that looks unreliable\n            time_announce_allowed = time.time() - 60 * min(30, tracker_stats[\"num_error\"])\n            if tracker_stats[\"num_error\"] > 5 and tracker_stats[\"time_request\"] > time_announce_allowed and not force:\n                if config.verbose:\n                    self.site.log.debug(\"Tracker %s looks unreliable, announce skipped (error: %s)\" % (tracker, tracker_stats[\"num_error\"]))\n                continue\n            thread = self.site.greenlet_manager.spawn(self.announceTracker, tracker, mode=mode)\n            threads.append(thread)\n            thread.tracker = tracker\n\n        time.sleep(0.01)\n        self.updateWebsocket(trackers=\"announcing\")\n\n        gevent.joinall(threads, timeout=20)  # Wait for announce finish\n\n        for thread in threads:\n            if thread.value is None:\n                continue\n            if thread.value is not False:\n                if thread.value > 1.0:  # Takes more than 1 second to announce\n                    slow.append(\"%.2fs %s\" % (thread.value, thread.tracker))\n                num_announced += 1\n            else:\n                if thread.ready():\n                    errors.append(thread.tracker)\n                else:  # Still running\n                    slow.append(\"30s+ %s\" % thread.tracker)\n\n        # Save peers num\n        self.site.settings[\"peers\"] = len(self.site.peers)\n\n        if len(errors) < len(threads):  # At least one tracker finished\n            if len(trackers) == 1:\n                announced_to = trackers[0]\n            else:\n                announced_to = \"%s/%s trackers\" % (num_announced, len(threads))\n            if mode != \"update\" or config.verbose:\n                self.site.log.debug(\n                    \"Announced in mode %s to %s in %.3fs, errors: %s, slow: %s\" %\n                    (mode, announced_to, time.time() - s, errors, slow)\n                )\n        else:\n            if len(threads) > 1:\n                self.site.log.error(\"Announce to %s trackers in %.3fs, failed\" % (len(threads), time.time() - s))\n            if len(threads) == 1 and mode != \"start\":  # Move to next tracker\n                self.site.log.debug(\"Tracker failed, skipping to next one...\")\n                self.site.greenlet_manager.spawnLater(1.0, self.announce, force=force, mode=mode, pex=pex)\n\n        self.updateWebsocket(trackers=\"announced\")\n\n        if pex:\n            self.updateWebsocket(pex=\"announcing\")\n            if mode == \"more\":  # Need more peers\n                self.announcePex(need_num=10)\n            else:\n                self.announcePex()\n\n            self.updateWebsocket(pex=\"announced\")\n\n    def getTrackerHandler(self, protocol):\n        return None\n\n    def getAddressParts(self, tracker):\n        if \"://\" not in tracker or not re.match(\"^[A-Za-z0-9:/\\\\.#-]+$\", tracker):\n            return None\n        protocol, address = tracker.split(\"://\", 1)\n        if \":\" in address:\n            ip, port = address.rsplit(\":\", 1)\n        else:\n            ip = address\n            if protocol.startswith(\"https\"):\n                port = 443\n            else:\n                port = 80\n        back = {}\n        back[\"protocol\"] = protocol\n        back[\"address\"] = address\n        back[\"ip\"] = ip\n        back[\"port\"] = port\n        return back\n\n    def announceTracker(self, tracker, mode=\"start\", num_want=10):\n        s = time.time()\n        address_parts = self.getAddressParts(tracker)\n        if not address_parts:\n            self.site.log.warning(\"Tracker %s error: Invalid address\" % tracker)\n            return False\n\n        if tracker not in self.stats:\n            self.stats[tracker] = {\"status\": \"\", \"num_request\": 0, \"num_success\": 0, \"num_error\": 0, \"time_request\": 0, \"time_last_error\": 0}\n\n        last_status = self.stats[tracker][\"status\"]\n        self.stats[tracker][\"status\"] = \"announcing\"\n        self.stats[tracker][\"time_request\"] = time.time()\n        global_stats[tracker][\"time_request\"] = time.time()\n        if config.verbose:\n            self.site.log.debug(\"Tracker announcing to %s (mode: %s)\" % (tracker, mode))\n        if mode == \"update\":\n            num_want = 10\n        else:\n            num_want = 30\n\n        handler = self.getTrackerHandler(address_parts[\"protocol\"])\n        error = None\n        try:\n            if handler:\n                peers = handler(address_parts[\"address\"], mode=mode, num_want=num_want)\n            else:\n                raise AnnounceError(\"Unknown protocol: %s\" % address_parts[\"protocol\"])\n        except Exception as err:\n            self.site.log.warning(\"Tracker %s announce failed: %s in mode %s\" % (tracker, Debug.formatException(err), mode))\n            error = err\n\n        if error:\n            self.stats[tracker][\"status\"] = \"error\"\n            self.stats[tracker][\"time_status\"] = time.time()\n            self.stats[tracker][\"last_error\"] = str(error)\n            self.stats[tracker][\"time_last_error\"] = time.time()\n            if self.site.connection_server.has_internet:\n                self.stats[tracker][\"num_error\"] += 1\n            self.stats[tracker][\"num_request\"] += 1\n            global_stats[tracker][\"num_request\"] += 1\n            if self.site.connection_server.has_internet:\n                global_stats[tracker][\"num_error\"] += 1\n            self.updateWebsocket(tracker=\"error\")\n            return False\n\n        if peers is None:  # Announce skipped\n            self.stats[tracker][\"time_status\"] = time.time()\n            self.stats[tracker][\"status\"] = last_status\n            return None\n\n        self.stats[tracker][\"status\"] = \"announced\"\n        self.stats[tracker][\"time_status\"] = time.time()\n        self.stats[tracker][\"num_success\"] += 1\n        self.stats[tracker][\"num_request\"] += 1\n        global_stats[tracker][\"num_request\"] += 1\n        global_stats[tracker][\"num_error\"] = 0\n\n        if peers is True:  # Announce success, but no peers returned\n            return time.time() - s\n\n        # Adding peers\n        added = 0\n        for peer in peers:\n            if peer[\"port\"] == 1:  # Some trackers does not accept port 0, so we send port 1 as not-connectable\n                peer[\"port\"] = 0\n            if not peer[\"port\"]:\n                continue  # Dont add peers with port 0\n            if self.site.addPeer(peer[\"addr\"], peer[\"port\"], source=\"tracker\"):\n                added += 1\n\n        if added:\n            self.site.worker_manager.onPeers()\n            self.site.updateWebsocket(peers_added=added)\n\n        if config.verbose:\n            self.site.log.debug(\n                \"Tracker result: %s://%s (found %s peers, new: %s, total: %s)\" %\n                (address_parts[\"protocol\"], address_parts[\"address\"], len(peers), added, len(self.site.peers))\n            )\n        return time.time() - s\n\n    @util.Noparallel(blocking=False)\n    def announcePex(self, query_num=2, need_num=5):\n        peers = self.site.getConnectedPeers()\n        if len(peers) == 0:  # Wait 3s for connections\n            time.sleep(3)\n            peers = self.site.getConnectedPeers()\n\n        if len(peers) == 0:  # Small number of connected peers for this site, connect to any\n            peers = list(self.site.getRecentPeers(20))\n            need_num = 10\n\n        random.shuffle(peers)\n        done = 0\n        total_added = 0\n        for peer in peers:\n            num_added = peer.pex(need_num=need_num)\n            if num_added is not False:\n                done += 1\n                total_added += num_added\n                if num_added:\n                    self.site.worker_manager.onPeers()\n                    self.site.updateWebsocket(peers_added=num_added)\n            else:\n                time.sleep(0.1)\n            if done == query_num:\n                break\n        self.site.log.debug(\"Pex result: from %s peers got %s new peers.\" % (done, total_added))\n\n    def updateWebsocket(self, **kwargs):\n        if kwargs:\n            param = {\"event\": list(kwargs.items())[0]}\n        else:\n            param = None\n\n        for ws in self.site.websockets:\n            ws.event(\"announcerChanged\", self.site, param)\n"
  },
  {
    "path": "src/Site/SiteManager.py",
    "content": "import json\nimport logging\nimport re\nimport os\nimport time\nimport atexit\n\nimport gevent\n\nimport util\nfrom Plugin import PluginManager\nfrom Content import ContentDb\nfrom Config import config\nfrom util import helper\nfrom util import RateLimit\nfrom util import Cached\n\n\n@PluginManager.acceptPlugins\nclass SiteManager(object):\n    def __init__(self):\n        self.log = logging.getLogger(\"SiteManager\")\n        self.log.debug(\"SiteManager created.\")\n        self.sites = {}\n        self.sites_changed = int(time.time())\n        self.loaded = False\n        gevent.spawn(self.saveTimer)\n        atexit.register(lambda: self.save(recalculate_size=True))\n\n    # Load all sites from data/sites.json\n    @util.Noparallel()\n    def load(self, cleanup=True, startup=False):\n        from Debug import Debug\n        self.log.info(\"Loading sites... (cleanup: %s, startup: %s)\" % (cleanup, startup))\n        self.loaded = False\n        from .Site import Site\n        address_found = []\n        added = 0\n        load_s = time.time()\n        # Load new adresses\n        try:\n            json_path = \"%s/sites.json\" % config.data_dir\n            data = json.load(open(json_path))\n        except Exception as err:\n            raise Exception(\"Unable to load %s: %s\" % (json_path, err))\n\n        sites_need = []\n\n        for address, settings in data.items():\n            if address not in self.sites:\n                if os.path.isfile(\"%s/%s/content.json\" % (config.data_dir, address)):\n                    # Root content.json exists, try load site\n                    s = time.time()\n                    try:\n                        site = Site(address, settings=settings)\n                        site.content_manager.contents.get(\"content.json\")\n                    except Exception as err:\n                        self.log.debug(\"Error loading site %s: %s\" % (address, err))\n                        continue\n                    self.sites[address] = site\n                    self.log.debug(\"Loaded site %s in %.3fs\" % (address, time.time() - s))\n                    added += 1\n                elif startup:\n                    # No site directory, start download\n                    self.log.debug(\"Found new site in sites.json: %s\" % address)\n                    sites_need.append([address, settings])\n                    added += 1\n\n            address_found.append(address)\n\n        # Remove deleted adresses\n        if cleanup:\n            for address in list(self.sites.keys()):\n                if address not in address_found:\n                    del(self.sites[address])\n                    self.log.debug(\"Removed site: %s\" % address)\n\n            # Remove orpan sites from contentdb\n            content_db = ContentDb.getContentDb()\n            for row in content_db.execute(\"SELECT * FROM site\").fetchall():\n                address = row[\"address\"]\n                if address not in self.sites and address not in address_found:\n                    self.log.info(\"Deleting orphan site from content.db: %s\" % address)\n\n                    try:\n                        content_db.execute(\"DELETE FROM site WHERE ?\", {\"address\": address})\n                    except Exception as err:\n                        self.log.error(\"Can't delete site %s from content_db: %s\" % (address, err))\n\n                    if address in content_db.site_ids:\n                        del content_db.site_ids[address]\n                    if address in content_db.sites:\n                        del content_db.sites[address]\n\n        self.loaded = True\n        for address, settings in sites_need:\n            gevent.spawn(self.need, address, settings=settings)\n        if added:\n            self.log.info(\"Added %s sites in %.3fs\" % (added, time.time() - load_s))\n\n    def saveDelayed(self):\n        RateLimit.callAsync(\"Save sites.json\", allowed_again=5, func=self.save)\n\n    def save(self, recalculate_size=False):\n        if not self.sites:\n            self.log.debug(\"Save skipped: No sites found\")\n            return\n        if not self.loaded:\n            self.log.debug(\"Save skipped: Not loaded\")\n            return\n        s = time.time()\n        data = {}\n        # Generate data file\n        s = time.time()\n        for address, site in list(self.list().items()):\n            if recalculate_size:\n                site.settings[\"size\"], site.settings[\"size_optional\"] = site.content_manager.getTotalSize()  # Update site size\n            data[address] = site.settings\n            data[address][\"cache\"] = site.getSettingsCache()\n        time_generate = time.time() - s\n\n        s = time.time()\n        if data:\n            helper.atomicWrite(\"%s/sites.json\" % config.data_dir, helper.jsonDumps(data).encode(\"utf8\"))\n        else:\n            self.log.debug(\"Save error: No data\")\n        time_write = time.time() - s\n\n        # Remove cache from site settings\n        for address, site in self.list().items():\n            site.settings[\"cache\"] = {}\n\n        self.log.debug(\"Saved sites in %.2fs (generate: %.2fs, write: %.2fs)\" % (time.time() - s, time_generate, time_write))\n\n    def saveTimer(self):\n        while 1:\n            time.sleep(60 * 10)\n            self.save(recalculate_size=True)\n\n    # Checks if its a valid address\n    def isAddress(self, address):\n        return re.match(\"^[A-Za-z0-9]{26,35}$\", address)\n\n    def isDomain(self, address):\n        return False\n\n    @Cached(timeout=10)\n    def isDomainCached(self, address):\n        return self.isDomain(address)\n\n    def resolveDomain(self, domain):\n        return False\n\n    @Cached(timeout=10)\n    def resolveDomainCached(self, domain):\n        return self.resolveDomain(domain)\n\n    # Return: Site object or None if not found\n    def get(self, address):\n        if self.isDomainCached(address):\n            address_resolved = self.resolveDomainCached(address)\n            if address_resolved:\n                address = address_resolved\n\n        if not self.loaded:  # Not loaded yet\n            self.log.debug(\"Loading site: %s)...\" % address)\n            self.load()\n        site = self.sites.get(address)\n\n        return site\n\n    def add(self, address, all_file=True, settings=None, **kwargs):\n        from .Site import Site\n        self.sites_changed = int(time.time())\n        # Try to find site with differect case\n        for recover_address, recover_site in list(self.sites.items()):\n            if recover_address.lower() == address.lower():\n                return recover_site\n\n        if not self.isAddress(address):\n            return False  # Not address: %s % address\n        self.log.debug(\"Added new site: %s\" % address)\n        config.loadTrackersFile()\n        site = Site(address, settings=settings)\n        self.sites[address] = site\n        if not site.settings[\"serving\"]:  # Maybe it was deleted before\n            site.settings[\"serving\"] = True\n        site.saveSettings()\n        if all_file:  # Also download user files on first sync\n            site.download(check_size=True, blind_includes=True)\n        return site\n\n    # Return or create site and start download site files\n    def need(self, address, *args, **kwargs):\n        if self.isDomainCached(address):\n            address_resolved = self.resolveDomainCached(address)\n            if address_resolved:\n                address = address_resolved\n\n        site = self.get(address)\n        if not site:  # Site not exist yet\n            site = self.add(address, *args, **kwargs)\n        return site\n\n    def delete(self, address):\n        self.sites_changed = int(time.time())\n        self.log.debug(\"Deleted site: %s\" % address)\n        del(self.sites[address])\n        # Delete from sites.json\n        self.save()\n\n    # Lazy load sites\n    def list(self):\n        if not self.loaded:  # Not loaded yet\n            self.log.debug(\"Sites not loaded yet...\")\n            self.load(startup=True)\n        return self.sites\n\n\nsite_manager = SiteManager()  # Singletone\n\nif config.action == \"main\":  # Don't connect / add myself to peerlist\n    peer_blacklist = [(\"127.0.0.1\", config.fileserver_port), (\"::1\", config.fileserver_port)]\nelse:\n    peer_blacklist = []\n\n"
  },
  {
    "path": "src/Site/SiteStorage.py",
    "content": "import os\nimport re\nimport shutil\nimport json\nimport time\nimport errno\nfrom collections import defaultdict\n\nimport sqlite3\nimport gevent.event\n\nimport util\nfrom util import SafeRe\nfrom Db.Db import Db\nfrom Debug import Debug\nfrom Config import config\nfrom util import helper\nfrom util import ThreadPool\nfrom Plugin import PluginManager\nfrom Translate import translate as _\n\n\nthread_pool_fs_read = ThreadPool.ThreadPool(config.threads_fs_read, name=\"FS read\")\nthread_pool_fs_write = ThreadPool.ThreadPool(config.threads_fs_write, name=\"FS write\")\nthread_pool_fs_batch = ThreadPool.ThreadPool(1, name=\"FS batch\")\n\n\n@PluginManager.acceptPlugins\nclass SiteStorage(object):\n    def __init__(self, site, allow_create=True):\n        self.site = site\n        self.directory = \"%s/%s\" % (config.data_dir, self.site.address)  # Site data diretory\n        self.allowed_dir = os.path.abspath(self.directory)  # Only serve file within this dir\n        self.log = site.log\n        self.db = None  # Db class\n        self.db_checked = False  # Checked db tables since startup\n        self.event_db_busy = None  # Gevent AsyncResult if db is working on rebuild\n        self.has_db = self.isFile(\"dbschema.json\")  # The site has schema\n\n        if not os.path.isdir(self.directory):\n            if allow_create:\n                os.mkdir(self.directory)  # Create directory if not found\n            else:\n                raise Exception(\"Directory not exists: %s\" % self.directory)\n\n    def getDbFile(self):\n        if self.db:\n            return self.db.schema[\"db_file\"]\n        else:\n            if self.isFile(\"dbschema.json\"):\n                schema = self.loadJson(\"dbschema.json\")\n                return schema[\"db_file\"]\n            else:\n                return False\n\n    # Create new databaseobject  with the site's schema\n    def openDb(self, close_idle=False):\n        schema = self.getDbSchema()\n        db_path = self.getPath(schema[\"db_file\"])\n        return Db(schema, db_path, close_idle=close_idle)\n\n    def closeDb(self, reason=\"Unknown (SiteStorage)\"):\n        if self.db:\n            self.db.close(reason)\n        self.event_db_busy = None\n        self.db = None\n\n    def getDbSchema(self):\n        try:\n            self.site.needFile(\"dbschema.json\")\n            schema = self.loadJson(\"dbschema.json\")\n        except Exception as err:\n            raise Exception(\"dbschema.json is not a valid JSON: %s\" % err)\n        return schema\n\n    def loadDb(self):\n        self.log.debug(\"No database, waiting for dbschema.json...\")\n        self.site.needFile(\"dbschema.json\", priority=3)\n        self.log.debug(\"Got dbschema.json\")\n        self.has_db = self.isFile(\"dbschema.json\")  # Recheck if dbschema exist\n        if self.has_db:\n            schema = self.getDbSchema()\n            db_path = self.getPath(schema[\"db_file\"])\n            if not os.path.isfile(db_path) or os.path.getsize(db_path) == 0:\n                try:\n                    self.rebuildDb(reason=\"Missing database\")\n                except Exception as err:\n                    self.log.error(err)\n                    pass\n\n            if self.db:\n                self.db.close(\"Gettig new db for SiteStorage\")\n            self.db = self.openDb(close_idle=True)\n            try:\n                changed_tables = self.db.checkTables()\n                if changed_tables:\n                    self.rebuildDb(delete_db=False, reason=\"Changed tables\")  # TODO: only update the changed table datas\n            except sqlite3.OperationalError:\n                pass\n\n    # Return db class\n    @util.Noparallel()\n    def getDb(self):\n        if self.event_db_busy:  # Db not ready for queries\n            self.log.debug(\"Wating for db...\")\n            self.event_db_busy.get()  # Wait for event\n        if not self.db:\n            self.loadDb()\n        return self.db\n\n    def updateDbFile(self, inner_path, file=None, cur=None):\n        path = self.getPath(inner_path)\n        if cur:\n            db = cur.db\n        else:\n            db = self.getDb()\n        return db.updateJson(path, file, cur)\n\n    # Return possible db files for the site\n    @thread_pool_fs_read.wrap\n    def getDbFiles(self):\n        found = 0\n        for content_inner_path, content in self.site.content_manager.contents.items():\n            # content.json file itself\n            if self.isFile(content_inner_path):\n                yield content_inner_path, self.getPath(content_inner_path)\n            else:\n                self.log.debug(\"[MISSING] %s\" % content_inner_path)\n            # Data files in content.json\n            content_inner_path_dir = helper.getDirname(content_inner_path)  # Content.json dir relative to site\n            for file_relative_path in list(content.get(\"files\", {}).keys()) + list(content.get(\"files_optional\", {}).keys()):\n                if not file_relative_path.endswith(\".json\") and not file_relative_path.endswith(\"json.gz\"):\n                    continue  # We only interesed in json files\n                file_inner_path = content_inner_path_dir + file_relative_path  # File Relative to site dir\n                file_inner_path = file_inner_path.strip(\"/\")  # Strip leading /\n                if self.isFile(file_inner_path):\n                    yield file_inner_path, self.getPath(file_inner_path)\n                else:\n                    self.log.debug(\"[MISSING] %s\" % file_inner_path)\n                found += 1\n                if found % 100 == 0:\n                    time.sleep(0.001)  # Context switch to avoid UI block\n\n    # Rebuild sql cache\n    @util.Noparallel()\n    @thread_pool_fs_batch.wrap\n    def rebuildDb(self, delete_db=True, reason=\"Unknown\"):\n        self.log.info(\"Rebuilding db (reason: %s)...\" % reason)\n        self.has_db = self.isFile(\"dbschema.json\")\n        if not self.has_db:\n            return False\n\n        schema = self.loadJson(\"dbschema.json\")\n        db_path = self.getPath(schema[\"db_file\"])\n        if os.path.isfile(db_path) and delete_db:\n            if self.db:\n                self.closeDb(\"rebuilding\")  # Close db if open\n                time.sleep(0.5)\n            self.log.info(\"Deleting %s\" % db_path)\n            try:\n                os.unlink(db_path)\n            except Exception as err:\n                self.log.error(\"Delete error: %s\" % err)\n\n        if not self.db:\n            self.db = self.openDb()\n        self.event_db_busy = gevent.event.AsyncResult()\n\n        self.log.info(\"Rebuild: Creating tables...\")\n\n        # raise DbTableError if not valid\n        self.db.checkTables()\n\n        cur = self.db.getCursor()\n        cur.logging = False\n        s = time.time()\n        self.log.info(\"Rebuild: Getting db files...\")\n        db_files = list(self.getDbFiles())\n        num_imported = 0\n        num_total = len(db_files)\n        num_error = 0\n\n        self.log.info(\"Rebuild: Importing data...\")\n        try:\n            if num_total > 100:\n                self.site.messageWebsocket(\n                    _[\"Database rebuilding...<br>Imported {0} of {1} files (error: {2})...\"].format(\n                        \"0000\", num_total, num_error\n                    ), \"rebuild\", 0\n                )\n            for file_inner_path, file_path in db_files:\n                try:\n                    if self.updateDbFile(file_inner_path, file=open(file_path, \"rb\"), cur=cur):\n                        num_imported += 1\n                except Exception as err:\n                    self.log.error(\"Error importing %s: %s\" % (file_inner_path, Debug.formatException(err)))\n                    num_error += 1\n\n                if num_imported and num_imported % 100 == 0:\n                    self.site.messageWebsocket(\n                        _[\"Database rebuilding...<br>Imported {0} of {1} files (error: {2})...\"].format(\n                            num_imported, num_total, num_error\n                        ),\n                        \"rebuild\", int(float(num_imported) / num_total * 100)\n                    )\n                    time.sleep(0.001)  # Context switch to avoid UI block\n\n        finally:\n            cur.close()\n            if num_total > 100:\n                self.site.messageWebsocket(\n                    _[\"Database rebuilding...<br>Imported {0} of {1} files (error: {2})...\"].format(\n                        num_imported, num_total, num_error\n                    ), \"rebuild\", 100\n                )\n            self.log.info(\"Rebuild: Imported %s data file in %.3fs\" % (num_imported, time.time() - s))\n            self.event_db_busy.set(True)  # Event done, notify waiters\n            self.event_db_busy = None  # Clear event\n            self.db.commit(\"Rebuilt\")\n\n        return True\n\n    # Execute sql query or rebuild on dberror\n    def query(self, query, params=None):\n        if not query.strip().upper().startswith(\"SELECT\"):\n            raise Exception(\"Only SELECT query supported\")\n\n        try:\n            res = self.getDb().execute(query, params)\n        except sqlite3.DatabaseError as err:\n            if err.__class__.__name__ == \"DatabaseError\":\n                self.log.error(\"Database error: %s, query: %s, try to rebuilding it...\" % (err, query))\n                try:\n                    self.rebuildDb(reason=\"Query error\")\n                except sqlite3.OperationalError:\n                    pass\n                res = self.db.cur.execute(query, params)\n            else:\n                raise err\n        return res\n\n    def ensureDir(self, inner_path):\n        try:\n            os.makedirs(self.getPath(inner_path))\n        except OSError as err:\n            if err.errno == errno.EEXIST:\n                return False\n            else:\n                raise err\n        return True\n\n    # Open file object\n    def open(self, inner_path, mode=\"rb\", create_dirs=False, **kwargs):\n        file_path = self.getPath(inner_path)\n        if create_dirs:\n            file_inner_dir = os.path.dirname(inner_path)\n            self.ensureDir(file_inner_dir)\n        return open(file_path, mode, **kwargs)\n\n    # Open file object\n    @thread_pool_fs_read.wrap\n    def read(self, inner_path, mode=\"rb\"):\n        return open(self.getPath(inner_path), mode).read()\n\n    @thread_pool_fs_write.wrap\n    def writeThread(self, inner_path, content):\n        file_path = self.getPath(inner_path)\n        # Create dir if not exist\n        self.ensureDir(os.path.dirname(inner_path))\n        # Write file\n        if hasattr(content, 'read'):  # File-like object\n\n            with open(file_path, \"wb\") as file:\n                shutil.copyfileobj(content, file)  # Write buff to disk\n        else:  # Simple string\n            if inner_path == \"content.json\" and os.path.isfile(file_path):\n                helper.atomicWrite(file_path, content)\n            else:\n                with open(file_path, \"wb\") as file:\n                    file.write(content)\n\n    # Write content to file\n    def write(self, inner_path, content):\n        self.writeThread(inner_path, content)\n        self.onUpdated(inner_path)\n\n    # Remove file from filesystem\n    def delete(self, inner_path):\n        file_path = self.getPath(inner_path)\n        os.unlink(file_path)\n        self.onUpdated(inner_path, file=False)\n\n    def deleteDir(self, inner_path):\n        dir_path = self.getPath(inner_path)\n        os.rmdir(dir_path)\n\n    def rename(self, inner_path_before, inner_path_after):\n        for retry in range(3):\n            rename_err = None\n            # To workaround \"The process cannot access the file beacause it is being used by another process.\" error\n            try:\n                os.rename(self.getPath(inner_path_before), self.getPath(inner_path_after))\n                break\n            except Exception as err:\n                rename_err = err\n                self.log.error(\"%s rename error: %s (retry #%s)\" % (inner_path_before, err, retry))\n                time.sleep(0.1 + retry)\n        if rename_err:\n            raise rename_err\n\n    # List files from a directory\n    @thread_pool_fs_read.wrap\n    def walk(self, dir_inner_path, ignore=None):\n        directory = self.getPath(dir_inner_path)\n        for root, dirs, files in os.walk(directory):\n            root = root.replace(\"\\\\\", \"/\")\n            root_relative_path = re.sub(\"^%s\" % re.escape(directory), \"\", root).lstrip(\"/\")\n            for file_name in files:\n                if root_relative_path:  # Not root dir\n                    file_relative_path = root_relative_path + \"/\" + file_name\n                else:\n                    file_relative_path = file_name\n\n                if ignore and SafeRe.match(ignore, file_relative_path):\n                    continue\n\n                yield file_relative_path\n\n            # Don't scan directory that is in the ignore pattern\n            if ignore:\n                dirs_filtered = []\n                for dir_name in dirs:\n                    if root_relative_path:\n                        dir_relative_path = root_relative_path + \"/\" + dir_name\n                    else:\n                        dir_relative_path = dir_name\n\n                    if ignore == \".*\" or re.match(\".*([|(]|^)%s([|)]|$)\" % re.escape(dir_relative_path + \"/.*\"), ignore):\n                        continue\n\n                    dirs_filtered.append(dir_name)\n                dirs[:] = dirs_filtered\n\n    # list directories in a directory\n    @thread_pool_fs_read.wrap\n    def list(self, dir_inner_path):\n        directory = self.getPath(dir_inner_path)\n        return os.listdir(directory)\n\n    # Site content updated\n    def onUpdated(self, inner_path, file=None):\n        # Update Sql cache\n        should_load_to_db = inner_path.endswith(\".json\") or inner_path.endswith(\".json.gz\")\n        if inner_path == \"dbschema.json\":\n            self.has_db = self.isFile(\"dbschema.json\")\n            # Reopen DB to check changes\n            if self.has_db:\n                self.closeDb(\"New dbschema\")\n                gevent.spawn(self.getDb)\n        elif not config.disable_db and should_load_to_db and self.has_db:  # Load json file to db\n            if config.verbose:\n                self.log.debug(\"Loading json file to db: %s (file: %s)\" % (inner_path, file))\n            try:\n                self.updateDbFile(inner_path, file)\n            except Exception as err:\n                self.log.error(\"Json %s load error: %s\" % (inner_path, Debug.formatException(err)))\n                self.closeDb(\"Json load error\")\n\n    # Load and parse json file\n    @thread_pool_fs_read.wrap\n    def loadJson(self, inner_path):\n        with self.open(inner_path, \"r\", encoding=\"utf8\") as file:\n            return json.load(file)\n\n    # Write formatted json file\n    def writeJson(self, inner_path, data):\n        # Write to disk\n        self.write(inner_path, helper.jsonDumps(data).encode(\"utf8\"))\n\n    # Get file size\n    def getSize(self, inner_path):\n        path = self.getPath(inner_path)\n        try:\n            return os.path.getsize(path)\n        except Exception:\n            return 0\n\n    # File exist\n    def isFile(self, inner_path):\n        return os.path.isfile(self.getPath(inner_path))\n\n    # File or directory exist\n    def isExists(self, inner_path):\n        return os.path.exists(self.getPath(inner_path))\n\n    # Dir exist\n    def isDir(self, inner_path):\n        return os.path.isdir(self.getPath(inner_path))\n\n    # Security check and return path of site's file\n    def getPath(self, inner_path):\n        inner_path = inner_path.replace(\"\\\\\", \"/\")  # Windows separator fix\n        if not inner_path:\n            return self.directory\n\n        if \"../\" in inner_path:\n            raise Exception(\"File not allowed: %s\" % inner_path)\n\n        return \"%s/%s\" % (self.directory, inner_path)\n\n    # Get site dir relative path\n    def getInnerPath(self, path):\n        if path == self.directory:\n            inner_path = \"\"\n        else:\n            if path.startswith(self.directory):\n                inner_path = path[len(self.directory) + 1:]\n            else:\n                raise Exception(\"File not allowed: %s\" % path)\n        return inner_path\n\n    # Verify all files sha512sum using content.json\n    def verifyFiles(self, quick_check=False, add_optional=False, add_changed=True):\n        bad_files = []\n        back = defaultdict(int)\n        back[\"bad_files\"] = bad_files\n        i = 0\n        self.log.debug(\"Verifing files...\")\n\n        if not self.site.content_manager.contents.get(\"content.json\"):  # No content.json, download it first\n            self.log.debug(\"VerifyFile content.json not exists\")\n            self.site.needFile(\"content.json\", update=True)  # Force update to fix corrupt file\n            self.site.content_manager.loadContent()  # Reload content.json\n        for content_inner_path, content in list(self.site.content_manager.contents.items()):\n            back[\"num_content\"] += 1\n            i += 1\n            if i % 50 == 0:\n                time.sleep(0.001)  # Context switch to avoid gevent hangs\n            if not os.path.isfile(self.getPath(content_inner_path)):  # Missing content.json file\n                back[\"num_content_missing\"] += 1\n                self.log.debug(\"[MISSING] %s\" % content_inner_path)\n                bad_files.append(content_inner_path)\n\n            for file_relative_path in list(content.get(\"files\", {}).keys()):\n                back[\"num_file\"] += 1\n                file_inner_path = helper.getDirname(content_inner_path) + file_relative_path  # Relative to site dir\n                file_inner_path = file_inner_path.strip(\"/\")  # Strip leading /\n                file_path = self.getPath(file_inner_path)\n                if not os.path.isfile(file_path):\n                    back[\"num_file_missing\"] += 1\n                    self.log.debug(\"[MISSING] %s\" % file_inner_path)\n                    bad_files.append(file_inner_path)\n                    continue\n\n                if quick_check:\n                    ok = os.path.getsize(file_path) == content[\"files\"][file_relative_path][\"size\"]\n                    if not ok:\n                        err = \"Invalid size\"\n                else:\n                    try:\n                        ok = self.site.content_manager.verifyFile(file_inner_path, open(file_path, \"rb\"))\n                    except Exception as err:\n                        ok = False\n\n                if not ok:\n                    back[\"num_file_invalid\"] += 1\n                    self.log.debug(\"[INVALID] %s: %s\" % (file_inner_path, err))\n                    if add_changed or content.get(\"cert_user_id\"):  # If updating own site only add changed user files\n                        bad_files.append(file_inner_path)\n\n            # Optional files\n            optional_added = 0\n            optional_removed = 0\n            for file_relative_path in list(content.get(\"files_optional\", {}).keys()):\n                back[\"num_optional\"] += 1\n                file_node = content[\"files_optional\"][file_relative_path]\n                file_inner_path = helper.getDirname(content_inner_path) + file_relative_path  # Relative to site dir\n                file_inner_path = file_inner_path.strip(\"/\")  # Strip leading /\n                file_path = self.getPath(file_inner_path)\n                hash_id = self.site.content_manager.hashfield.getHashId(file_node[\"sha512\"])\n                if not os.path.isfile(file_path):\n                    if self.site.content_manager.isDownloaded(file_inner_path, hash_id):\n                        back[\"num_optional_removed\"] += 1\n                        self.log.debug(\"[OPTIONAL MISSING] %s\" % file_inner_path)\n                        self.site.content_manager.optionalRemoved(file_inner_path, hash_id, file_node[\"size\"])\n                    if add_optional and self.site.isDownloadable(file_inner_path):\n                        self.log.debug(\"[OPTIONAL ADDING] %s\" % file_inner_path)\n                        bad_files.append(file_inner_path)\n                    continue\n\n                if quick_check:\n                    ok = os.path.getsize(file_path) == content[\"files_optional\"][file_relative_path][\"size\"]\n                else:\n                    try:\n                        ok = self.site.content_manager.verifyFile(file_inner_path, open(file_path, \"rb\"))\n                    except Exception as err:\n                        ok = False\n\n                if ok:\n                    if not self.site.content_manager.isDownloaded(file_inner_path, hash_id):\n                        back[\"num_optional_added\"] += 1\n                        self.site.content_manager.optionalDownloaded(file_inner_path, hash_id, file_node[\"size\"])\n                        optional_added += 1\n                        self.log.debug(\"[OPTIONAL FOUND] %s\" % file_inner_path)\n                else:\n                    if self.site.content_manager.isDownloaded(file_inner_path, hash_id):\n                        back[\"num_optional_removed\"] += 1\n                        self.site.content_manager.optionalRemoved(file_inner_path, hash_id, file_node[\"size\"])\n                        optional_removed += 1\n                    bad_files.append(file_inner_path)\n                    self.log.debug(\"[OPTIONAL CHANGED] %s\" % file_inner_path)\n\n            if config.verbose:\n                self.log.debug(\n                    \"%s verified: %s, quick: %s, optionals: +%s -%s\" %\n                    (content_inner_path, len(content[\"files\"]), quick_check, optional_added, optional_removed)\n                )\n\n        self.site.content_manager.contents.db.processDelayed()\n        time.sleep(0.001)  # Context switch to avoid gevent hangs\n        return back\n\n    # Check and try to fix site files integrity\n    def updateBadFiles(self, quick_check=True):\n        s = time.time()\n        res = self.verifyFiles(\n            quick_check,\n            add_optional=True,\n            add_changed=not self.site.settings.get(\"own\")  # Don't overwrite changed files if site owned\n        )\n        bad_files = res[\"bad_files\"]\n        self.site.bad_files = {}\n        if bad_files:\n            for bad_file in bad_files:\n                self.site.bad_files[bad_file] = 1\n        self.log.debug(\"Checked files in %.2fs... Found bad files: %s, Quick:%s\" % (time.time() - s, len(bad_files), quick_check))\n\n    # Delete site's all file\n    @thread_pool_fs_batch.wrap\n    def deleteFiles(self):\n        site_title = self.site.content_manager.contents.get(\"content.json\", {}).get(\"title\", self.site.address)\n        message_id = \"delete-%s\" % self.site.address\n        self.log.debug(\"Deleting files from content.json (title: %s)...\" % site_title)\n\n        files = []  # Get filenames\n        content_inner_paths = list(self.site.content_manager.contents.keys())\n        for i, content_inner_path in enumerate(content_inner_paths):\n            content = self.site.content_manager.contents.get(content_inner_path, {})\n            files.append(content_inner_path)\n            # Add normal files\n            for file_relative_path in list(content.get(\"files\", {}).keys()):\n                file_inner_path = helper.getDirname(content_inner_path) + file_relative_path  # Relative to site dir\n                files.append(file_inner_path)\n            # Add optional files\n            for file_relative_path in list(content.get(\"files_optional\", {}).keys()):\n                file_inner_path = helper.getDirname(content_inner_path) + file_relative_path  # Relative to site dir\n                files.append(file_inner_path)\n\n            if i % 100 == 0:\n                num_files = len(files)\n                self.site.messageWebsocket(\n                    _(\"Deleting site <b>{site_title}</b>...<br>Collected {num_files} files\"),\n                    message_id, (i / len(content_inner_paths)) * 25\n                )\n\n        if self.isFile(\"dbschema.json\"):\n            self.log.debug(\"Deleting db file...\")\n            self.closeDb(\"Deleting site\")\n            self.has_db = False\n            try:\n                schema = self.loadJson(\"dbschema.json\")\n                db_path = self.getPath(schema[\"db_file\"])\n                if os.path.isfile(db_path):\n                    os.unlink(db_path)\n            except Exception as err:\n                self.log.error(\"Db file delete error: %s\" % err)\n\n        num_files = len(files)\n        for i, inner_path in enumerate(files):\n            path = self.getPath(inner_path)\n            if os.path.isfile(path):\n                for retry in range(5):\n                    try:\n                        os.unlink(path)\n                        break\n                    except Exception as err:\n                        self.log.error(\"Error removing %s: %s, try #%s\" % (inner_path, err, retry))\n                    time.sleep(float(retry) / 10)\n            if i % 100 == 0:\n                self.site.messageWebsocket(\n                    _(\"Deleting site <b>{site_title}</b>...<br>Deleting file {i}/{num_files}\"),\n                    message_id, 25 + (i / num_files) * 50\n                )\n            self.onUpdated(inner_path, False)\n\n        self.log.debug(\"Deleting empty dirs...\")\n        i = 0\n        for root, dirs, files in os.walk(self.directory, topdown=False):\n            for dir in dirs:\n                path = os.path.join(root, dir)\n                if os.path.isdir(path):\n                    try:\n                        i += 1\n                        if i % 100 == 0:\n                            self.site.messageWebsocket(\n                                _(\"Deleting site <b>{site_title}</b>...<br>Deleting empty directories {i}\"),\n                                message_id, 85\n                            )\n                        os.rmdir(path)\n                    except OSError:  # Not empty\n                        pass\n\n        if os.path.isdir(self.directory) and os.listdir(self.directory) == []:\n            os.rmdir(self.directory)  # Remove sites directory if empty\n\n        if os.path.isdir(self.directory):\n            self.log.debug(\"Some unknown file remained in site data dir: %s...\" % self.directory)\n            self.site.messageWebsocket(\n                _(\"Deleting site <b>{site_title}</b>...<br>Site deleted, but some unknown files left in the directory\"),\n                message_id, 100\n            )\n            return False  # Some files not deleted\n        else:\n            self.log.debug(\"Site %s data directory deleted: %s...\" % (site_title, self.directory))\n\n            self.site.messageWebsocket(\n                _(\"Deleting site <b>{site_title}</b>...<br>All files deleted successfully\"),\n                message_id, 100\n            )\n\n            return True  # All clean\n"
  },
  {
    "path": "src/Site/__init__.py",
    "content": ""
  },
  {
    "path": "src/Test/BenchmarkSsl.py",
    "content": "#!/usr/bin/python2\nfrom gevent import monkey\nmonkey.patch_all()\nimport os\nimport time\nimport sys\nimport socket\nimport ssl\nsys.path.append(os.path.abspath(\"..\"))  # Imports relative to src dir\n\nimport io as StringIO\nimport gevent\n\nfrom gevent.server import StreamServer\nfrom gevent.pool import Pool\nfrom Config import config\nconfig.parse()\nfrom util import SslPatch\n\n# Server\nsocks = []\ndata = os.urandom(1024 * 100)\ndata += \"\\n\"\n\n\ndef handle(sock_raw, addr):\n    socks.append(sock_raw)\n    sock = sock_raw\n    # sock = ctx.wrap_socket(sock, server_side=True)\n    # if sock_raw.recv( 1, gevent.socket.MSG_PEEK ) == \"\\x16\":\n    #   sock = gevent.ssl.wrap_socket(sock_raw, server_side=True, keyfile='key-cz.pem',\n    #          certfile='cert-cz.pem', ciphers=ciphers, ssl_version=ssl.PROTOCOL_TLSv1)\n    # fp = os.fdopen(sock.fileno(), 'rb', 1024*512)\n    try:\n        while True:\n            line = sock.recv(16 * 1024)\n            if not line:\n                break\n            if line == \"bye\\n\":\n                break\n            elif line == \"gotssl\\n\":\n                sock.sendall(\"yes\\n\")\n                sock = gevent.ssl.wrap_socket(\n                    sock_raw, server_side=True, keyfile='../../data/key-rsa.pem', certfile='../../data/cert-rsa.pem',\n                    ciphers=ciphers, ssl_version=ssl.PROTOCOL_TLSv1\n                )\n            else:\n                sock.sendall(data)\n    except Exception as err:\n        print(err)\n    try:\n        sock.shutdown(gevent.socket.SHUT_WR)\n        sock.close()\n    except:\n        pass\n    socks.remove(sock_raw)\n\npool = Pool(1000)  # do not accept more than 10000 connections\nserver = StreamServer(('127.0.0.1', 1234), handle)\nserver.start()\n\n\n# Client\n\n\ntotal_num = 0\ntotal_bytes = 0\nclipher = None\nciphers = \"ECDHE-ECDSA-AES128-GCM-SHA256:ECDH+AES128:ECDHE-RSA-AES128-GCM-SHA256:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:HIGH:\" + \\\n    \"!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK\"\n\n# ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)\n\n\ndef getData():\n    global total_num, total_bytes, clipher\n    data = None\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    # sock = socket.ssl(s)\n    # sock = ssl.wrap_socket(sock)\n    sock.connect((\"127.0.0.1\", 1234))\n    # sock.do_handshake()\n    # clipher = sock.cipher()\n    sock.send(\"gotssl\\n\")\n    if sock.recv(128) == \"yes\\n\":\n        sock = ssl.wrap_socket(sock, ciphers=ciphers, ssl_version=ssl.PROTOCOL_TLSv1)\n        sock.do_handshake()\n        clipher = sock.cipher()\n\n    for req in range(20):\n        sock.sendall(\"req\\n\")\n        buff = StringIO.StringIO()\n        data = sock.recv(16 * 1024)\n        buff.write(data)\n        if not data:\n            break\n        while not data.endswith(\"\\n\"):\n            data = sock.recv(16 * 1024)\n            if not data:\n                break\n            buff.write(data)\n        total_num += 1\n        total_bytes += buff.tell()\n        if not data:\n            print(\"No data\")\n\n    sock.shutdown(gevent.socket.SHUT_WR)\n    sock.close()\n\ns = time.time()\n\n\ndef info():\n    import psutil\n    import os\n    process = psutil.Process(os.getpid())\n    if \"memory_info\" in dir(process):\n        memory_info = process.memory_info\n    else:\n        memory_info = process.get_memory_info\n    while 1:\n        print(total_num, \"req\", (total_bytes / 1024), \"kbytes\", \"transfered in\", time.time() - s, end=' ')\n        print(\"using\", clipher, \"Mem:\", memory_info()[0] / float(2 ** 20))\n        time.sleep(1)\n\ngevent.spawn(info)\n\nfor test in range(1):\n    clients = []\n    for i in range(500):  # Thread\n        clients.append(gevent.spawn(getData))\n    gevent.joinall(clients)\n\n\nprint(total_num, \"req\", (total_bytes / 1024), \"kbytes\", \"transfered in\", time.time() - s)\n\n# Separate client/server process:\n# 10*10*100:\n# Raw:      10000 req 1000009 kbytes transfered in 5.39999985695\n# RSA 2048: 10000 req 1000009 kbytes transfered in 27.7890000343 using ('ECDHE-RSA-AES256-SHA', 'TLSv1/SSLv3', 256)\n# ECC:      10000 req 1000009 kbytes transfered in 26.1959998608 using ('ECDHE-ECDSA-AES256-SHA', 'TLSv1/SSLv3', 256)\n# ECC:      10000 req 1000009 kbytes transfered in 28.2410001755 using ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 13.3828125\n#\n# 10*100*10:\n# Raw:      10000 req 1000009 kbytes transfered in 7.02700018883 Mem: 14.328125\n# RSA 2048: 10000 req 1000009 kbytes transfered in 44.8860001564 using ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 20.078125\n# ECC:      10000 req 1000009 kbytes transfered in 37.9430000782 using ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 20.0234375\n#\n# 1*100*100:\n# Raw:      10000 req 1000009 kbytes transfered in 4.64400005341 Mem: 14.06640625\n# RSA:      10000 req 1000009 kbytes transfered in 24.2300000191 using ('ECDHE-RSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 19.7734375\n# ECC:      10000 req 1000009 kbytes transfered in 22.8849999905 using ('ECDHE-ECDSA-AES256-GCM-SHA384', 'TLSv1/SSLv3', 256) Mem: 17.8125\n# AES128:   10000 req 1000009 kbytes transfered in 21.2839999199 using ('AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 14.1328125\n# ECC+128:  10000 req 1000009 kbytes transfered in 20.496999979  using ('ECDHE-ECDSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 14.40234375\n#\n#\n# Single process:\n# 1*100*100\n# RSA:      10000 req 1000009 kbytes transfered in 41.7899999619 using ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 26.91015625\n#\n# 10*10*100\n# RSA:      10000 req 1000009 kbytes transfered in 40.1640000343 using ('ECDHE-RSA-AES128-GCM-SHA256', 'TLSv1/SSLv3', 128) Mem: 14.94921875\n"
  },
  {
    "path": "src/Test/Spy.py",
    "content": "import logging\n\nclass Spy:\n    def __init__(self, obj, func_name):\n        self.obj = obj\n        self.__name__ = func_name\n        self.func_original = getattr(self.obj, func_name)\n        self.calls = []\n\n    def __enter__(self, *args, **kwargs):\n        logging.debug(\"Spy started\")\n        def loggedFunc(cls, *args, **kwargs):\n            call = dict(enumerate(args, 1))\n            call[0] = cls\n            call.update(kwargs)\n            logging.debug(\"Spy call: %s\" % call)\n            self.calls.append(call)\n            return self.func_original(cls, *args, **kwargs)\n        setattr(self.obj, self.__name__, loggedFunc)\n        return self.calls\n\n    def __exit__(self, *args, **kwargs):\n        setattr(self.obj, self.__name__, self.func_original)"
  },
  {
    "path": "src/Test/TestCached.py",
    "content": "import time\n\nfrom util import Cached\n\n\nclass CachedObject:\n    def __init__(self):\n        self.num_called_add = 0\n        self.num_called_multiply = 0\n        self.num_called_none = 0\n\n    @Cached(timeout=1)\n    def calcAdd(self, a, b):\n        self.num_called_add += 1\n        return a + b\n\n    @Cached(timeout=1)\n    def calcMultiply(self, a, b):\n        self.num_called_multiply += 1\n        return a * b\n\n    @Cached(timeout=1)\n    def none(self):\n        self.num_called_none += 1\n        return None\n\n\nclass TestCached:\n    def testNoneValue(self):\n        cached_object = CachedObject()\n        assert cached_object.none() is None\n        assert cached_object.none() is None\n        assert cached_object.num_called_none == 1\n        time.sleep(2)\n        assert cached_object.none() is None\n        assert cached_object.num_called_none == 2\n\n    def testCall(self):\n        cached_object = CachedObject()\n\n        assert cached_object.calcAdd(1, 2) == 3\n        assert cached_object.calcAdd(1, 2) == 3\n        assert cached_object.calcMultiply(1, 2) == 2\n        assert cached_object.calcMultiply(1, 2) == 2\n        assert cached_object.num_called_add == 1\n        assert cached_object.num_called_multiply == 1\n\n        assert cached_object.calcAdd(2, 3) == 5\n        assert cached_object.calcAdd(2, 3) == 5\n        assert cached_object.num_called_add == 2\n\n        assert cached_object.calcAdd(1, 2) == 3\n        assert cached_object.calcMultiply(2, 3) == 6\n        assert cached_object.num_called_add == 2\n        assert cached_object.num_called_multiply == 2\n\n        time.sleep(2)\n        assert cached_object.calcAdd(1, 2) == 3\n        assert cached_object.num_called_add == 3\n"
  },
  {
    "path": "src/Test/TestConfig.py",
    "content": "import pytest\n\nimport Config\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestConfig:\n    def testParse(self):\n        # Defaults\n        config_test = Config.Config(\"zeronet.py\".split(\" \"))\n        config_test.parse(silent=True, parse_config=False)\n        assert not config_test.debug\n        assert not config_test.debug_socket\n\n        # Test parse command line with unknown parameters (ui_password)\n        config_test = Config.Config(\"zeronet.py --debug --debug_socket --ui_password hello\".split(\" \"))\n        config_test.parse(silent=True, parse_config=False)\n        assert config_test.debug\n        assert config_test.debug_socket\n        with pytest.raises(AttributeError):\n            config_test.ui_password\n\n        # More complex test\n        args = \"zeronet.py --unknown_arg --debug --debug_socket --ui_restrict 127.0.0.1 1.2.3.4 \"\n        args += \"--another_unknown argument --use_openssl False siteSign address privatekey --inner_path users/content.json\"\n        config_test = Config.Config(args.split(\" \"))\n        config_test.parse(silent=True, parse_config=False)\n        assert config_test.debug\n        assert \"1.2.3.4\" in config_test.ui_restrict\n        assert not config_test.use_openssl\n        assert config_test.inner_path == \"users/content.json\"\n"
  },
  {
    "path": "src/Test/TestConnectionServer.py",
    "content": "import time\nimport socket\nimport gevent\n\nimport pytest\nimport mock\n\nfrom Crypt import CryptConnection\nfrom Connection import ConnectionServer\nfrom Config import config\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestConnection:\n    def testIpv6(self, file_server6):\n        assert \":\" in file_server6.ip\n\n        client = ConnectionServer(file_server6.ip, 1545)\n        connection = client.getConnection(file_server6.ip, 1544)\n\n        assert connection.ping()\n\n        # Close connection\n        connection.close()\n        client.stop()\n        time.sleep(0.01)\n        assert len(file_server6.connections) == 0\n\n        # Should not able to reach on ipv4 ip\n        with pytest.raises(socket.error) as err:\n            client = ConnectionServer(\"127.0.0.1\", 1545)\n            connection = client.getConnection(\"127.0.0.1\", 1544)\n\n    def testSslConnection(self, file_server):\n        client = ConnectionServer(file_server.ip, 1545)\n        assert file_server != client\n\n        # Connect to myself\n        with mock.patch('Config.config.ip_local', return_value=[]):  # SSL not used for local ips\n            connection = client.getConnection(file_server.ip, 1544)\n\n        assert len(file_server.connections) == 1\n        assert connection.handshake\n        assert connection.crypt\n\n\n        # Close connection\n        connection.close(\"Test ended\")\n        client.stop()\n        time.sleep(0.1)\n        assert len(file_server.connections) == 0\n        assert file_server.num_incoming == 2  # One for file_server fixture, one for the test\n\n    def testRawConnection(self, file_server):\n        client = ConnectionServer(file_server.ip, 1545)\n        assert file_server != client\n\n        # Remove all supported crypto\n        crypt_supported_bk = CryptConnection.manager.crypt_supported\n        CryptConnection.manager.crypt_supported = []\n\n        with mock.patch('Config.config.ip_local', return_value=[]):  # SSL not used for local ips\n            connection = client.getConnection(file_server.ip, 1544)\n        assert len(file_server.connections) == 1\n        assert not connection.crypt\n\n        # Close connection\n        connection.close()\n        client.stop()\n        time.sleep(0.01)\n        assert len(file_server.connections) == 0\n\n        # Reset supported crypts\n        CryptConnection.manager.crypt_supported = crypt_supported_bk\n\n    def testPing(self, file_server, site):\n        client = ConnectionServer(file_server.ip, 1545)\n        connection = client.getConnection(file_server.ip, 1544)\n\n        assert connection.ping()\n\n        connection.close()\n        client.stop()\n\n    def testGetConnection(self, file_server):\n        client = ConnectionServer(file_server.ip, 1545)\n        connection = client.getConnection(file_server.ip, 1544)\n\n        # Get connection by ip/port\n        connection2 = client.getConnection(file_server.ip, 1544)\n        assert connection == connection2\n\n        # Get connection by peerid\n        assert not client.getConnection(file_server.ip, 1544, peer_id=\"notexists\", create=False)\n        connection2 = client.getConnection(file_server.ip, 1544, peer_id=connection.handshake[\"peer_id\"], create=False)\n        assert connection2 == connection\n\n        connection.close()\n        client.stop()\n\n    def testFloodProtection(self, file_server):\n        whitelist = file_server.whitelist  # Save for reset\n        file_server.whitelist = []  # Disable 127.0.0.1 whitelist\n        client = ConnectionServer(file_server.ip, 1545)\n\n        # Only allow 6 connection in 1 minute\n        for reconnect in range(6):\n            connection = client.getConnection(file_server.ip, 1544)\n            assert connection.handshake\n            connection.close()\n\n        # The 7. one will timeout\n        with pytest.raises(gevent.Timeout):\n            with gevent.Timeout(0.1):\n                connection = client.getConnection(file_server.ip, 1544)\n\n        # Reset whitelist\n        file_server.whitelist = whitelist\n"
  },
  {
    "path": "src/Test/TestContent.py",
    "content": "import json\nimport time\nimport io\n\nimport pytest\n\nfrom Crypt import CryptBitcoin\nfrom Content.ContentManager import VerifyError, SignError\nfrom util.SafeRe import UnsafePatternError\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestContent:\n    privatekey = \"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\"\n\n    def testInclude(self, site):\n        # Rules defined in parent content.json\n        rules = site.content_manager.getRules(\"data/test_include/content.json\")\n\n        assert rules[\"signers\"] == [\"15ik6LeBWnACWfaika1xqGapRZ1zh3JpCo\"]  # Valid signer\n        assert rules[\"user_name\"] == \"test\"  # Extra data\n        assert rules[\"max_size\"] == 20000  # Max size of files\n        assert not rules[\"includes_allowed\"]  # Don't allow more includes\n        assert rules[\"files_allowed\"] == \"data.json\"  # Allowed file pattern\n\n        # Valid signers for \"data/test_include/content.json\"\n        valid_signers = site.content_manager.getValidSigners(\"data/test_include/content.json\")\n        assert \"15ik6LeBWnACWfaika1xqGapRZ1zh3JpCo\" in valid_signers  # Extra valid signer defined in parent content.json\n        assert \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\" in valid_signers  # The site itself\n        assert len(valid_signers) == 2  # No more\n\n        # Valid signers for \"data/users/content.json\"\n        valid_signers = site.content_manager.getValidSigners(\"data/users/content.json\")\n        assert \"1LSxsKfC9S9TVXGGNSM3vPHjyW82jgCX5f\" in valid_signers  # Extra valid signer defined in parent content.json\n        assert \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\" in valid_signers  # The site itself\n        assert len(valid_signers) == 2\n\n        # Valid signers for root content.json\n        assert site.content_manager.getValidSigners(\"content.json\") == [\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\"]\n\n    def testInlcudeLimits(self, site, crypt_bitcoin_lib):\n        # Data validation\n        res = []\n        data_dict = {\n            \"files\": {\n                \"data.json\": {\n                    \"sha512\": \"369d4e780cc80504285f13774ca327fe725eed2d813aad229e62356b07365906\",\n                    \"size\": 505\n                }\n            },\n            \"modified\": time.time()\n        }\n\n        # Normal data\n        data_dict[\"signs\"] = {\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)}\n        data_json = json.dumps(data_dict).encode()\n        data = io.BytesIO(data_json)\n        assert site.content_manager.verifyFile(\"data/test_include/content.json\", data, ignore_same=False)\n\n        # Reset\n        del data_dict[\"signs\"]\n\n        # Too large\n        data_dict[\"files\"][\"data.json\"][\"size\"] = 200000  # Emulate 2MB sized data.json\n        data_dict[\"signs\"] = {\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)}\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\"data/test_include/content.json\", data, ignore_same=False)\n        assert \"Include too large\" in str(err.value)\n\n        # Reset\n        data_dict[\"files\"][\"data.json\"][\"size\"] = 505\n        del data_dict[\"signs\"]\n\n        # Not allowed file\n        data_dict[\"files\"][\"notallowed.exe\"] = data_dict[\"files\"][\"data.json\"]\n        data_dict[\"signs\"] = {\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)}\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\"data/test_include/content.json\", data, ignore_same=False)\n        assert \"File not allowed\" in str(err.value)\n\n        # Reset\n        del data_dict[\"files\"][\"notallowed.exe\"]\n        del data_dict[\"signs\"]\n\n        # Should work again\n        data_dict[\"signs\"] = {\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)}\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        assert site.content_manager.verifyFile(\"data/test_include/content.json\", data, ignore_same=False)\n\n    @pytest.mark.parametrize(\"inner_path\", [\"content.json\", \"data/test_include/content.json\", \"data/users/content.json\"])\n    def testSign(self, site, inner_path):\n        # Bad privatekey\n        with pytest.raises(SignError) as err:\n            site.content_manager.sign(inner_path, privatekey=\"5aaa3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMnaa\", filewrite=False)\n        assert \"Private key invalid\" in str(err.value)\n\n        # Good privatekey\n        content = site.content_manager.sign(inner_path, privatekey=self.privatekey, filewrite=False)\n        content_old = site.content_manager.contents[inner_path]  # Content before the sign\n        assert not content_old == content  # Timestamp changed\n        assert site.address in content[\"signs\"]  # Used the site's private key to sign\n        if inner_path == \"content.json\":\n            assert len(content[\"files\"]) == 17\n        elif inner_path == \"data/test-include/content.json\":\n            assert len(content[\"files\"]) == 1\n        elif inner_path == \"data/users/content.json\":\n            assert len(content[\"files\"]) == 0\n\n        # Everything should be same as before except the modified timestamp and the signs\n        assert (\n            {key: val for key, val in content_old.items() if key not in [\"modified\", \"signs\", \"sign\", \"zeronet_version\"]}\n            ==\n            {key: val for key, val in content.items() if key not in [\"modified\", \"signs\", \"sign\", \"zeronet_version\"]}\n        )\n\n    def testSignOptionalFiles(self, site):\n        for hash in list(site.content_manager.hashfield):\n            site.content_manager.hashfield.remove(hash)\n\n        assert len(site.content_manager.hashfield) == 0\n\n        site.content_manager.contents[\"content.json\"][\"optional\"] = \"((data/img/zero.*))\"\n        content_optional = site.content_manager.sign(privatekey=self.privatekey, filewrite=False, remove_missing_optional=True)\n\n        del site.content_manager.contents[\"content.json\"][\"optional\"]\n        content_nooptional = site.content_manager.sign(privatekey=self.privatekey, filewrite=False, remove_missing_optional=True)\n\n        assert len(content_nooptional.get(\"files_optional\", {})) == 0  # No optional files if no pattern\n        assert len(content_optional[\"files_optional\"]) > 0\n        assert len(site.content_manager.hashfield) == len(content_optional[\"files_optional\"])  # Hashed optional files should be added to hashfield\n        assert len(content_nooptional[\"files\"]) > len(content_optional[\"files\"])\n\n    def testFileInfo(self, site):\n        assert \"sha512\" in site.content_manager.getFileInfo(\"index.html\")\n        assert site.content_manager.getFileInfo(\"data/img/domain.png\")[\"content_inner_path\"] == \"content.json\"\n        assert site.content_manager.getFileInfo(\"data/users/hello.png\")[\"content_inner_path\"] == \"data/users/content.json\"\n        assert site.content_manager.getFileInfo(\"data/users/content.json\")[\"content_inner_path\"] == \"data/users/content.json\"\n        assert not site.content_manager.getFileInfo(\"notexist\")\n\n        # Optional file\n        file_info_optional = site.content_manager.getFileInfo(\"data/optional.txt\")\n        assert \"sha512\" in file_info_optional\n        assert file_info_optional[\"optional\"] is True\n\n        # Not exists yet user content.json\n        assert \"cert_signers\" in site.content_manager.getFileInfo(\"data/users/unknown/content.json\")\n\n        # Optional user file\n        file_info_optional = site.content_manager.getFileInfo(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        assert \"sha512\" in file_info_optional\n        assert file_info_optional[\"optional\"] is True\n\n    def testVerify(self, site, crypt_bitcoin_lib):\n        inner_path = \"data/test_include/content.json\"\n        data_dict = site.storage.loadJson(inner_path)\n        data = io.BytesIO(json.dumps(data_dict).encode(\"utf8\"))\n\n        # Re-sign\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)\n        }\n        assert site.content_manager.verifyFile(inner_path, data, ignore_same=False)\n\n        # Wrong address\n        data_dict[\"address\"] = \"Othersite\"\n        del data_dict[\"signs\"]\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(inner_path, data, ignore_same=False)\n        assert \"Wrong site address\" in str(err.value)\n\n        # Wrong inner_path\n        data_dict[\"address\"] = \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\"\n        data_dict[\"inner_path\"] = \"content.json\"\n        del data_dict[\"signs\"]\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(inner_path, data, ignore_same=False)\n        assert \"Wrong inner_path\" in str(err.value)\n\n        # Everything right again\n        data_dict[\"address\"] = \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\"\n        data_dict[\"inner_path\"] = inner_path\n        del data_dict[\"signs\"]\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        assert site.content_manager.verifyFile(inner_path, data, ignore_same=False)\n\n    def testVerifyInnerPath(self, site, crypt_bitcoin_lib):\n        inner_path = \"content.json\"\n        data_dict = site.storage.loadJson(inner_path)\n\n        for good_relative_path in [\"data.json\", \"out/data.json\", \"Any File [by none] (1).jpg\", \"árvzítűrő/tükörfúrógép.txt\"]:\n            data_dict[\"files\"] = {good_relative_path: {\"sha512\": \"369d4e780cc80504285f13774ca327fe725eed2d813aad229e62356b07365906\", \"size\": 505}}\n\n            if \"sign\" in data_dict:\n                del data_dict[\"sign\"]\n            del data_dict[\"signs\"]\n            data_dict[\"signs\"] = {\n                \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)\n            }\n            data = io.BytesIO(json.dumps(data_dict).encode())\n            assert site.content_manager.verifyFile(inner_path, data, ignore_same=False)\n\n        for bad_relative_path in [\"../data.json\", \"data/\" * 100, \"invalid|file.jpg\", \"con.txt\", \"any/con.txt\"]:\n            data_dict[\"files\"] = {bad_relative_path: {\"sha512\": \"369d4e780cc80504285f13774ca327fe725eed2d813aad229e62356b07365906\", \"size\": 505}}\n\n            if \"sign\" in data_dict:\n                del data_dict[\"sign\"]\n            del data_dict[\"signs\"]\n            data_dict[\"signs\"] = {\n                \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), self.privatekey)\n            }\n            data = io.BytesIO(json.dumps(data_dict).encode())\n            with pytest.raises(VerifyError) as err:\n                site.content_manager.verifyFile(inner_path, data, ignore_same=False)\n            assert \"Invalid relative path\" in str(err.value)\n\n    @pytest.mark.parametrize(\"key\", [\"ignore\", \"optional\"])\n    def testSignUnsafePattern(self, site, key):\n        site.content_manager.contents[\"content.json\"][key] = \"([a-zA-Z]+)*\"\n        with pytest.raises(UnsafePatternError) as err:\n            site.content_manager.sign(\"content.json\", privatekey=self.privatekey, filewrite=False)\n        assert \"Potentially unsafe\" in str(err.value)\n\n\n    def testVerifyUnsafePattern(self, site, crypt_bitcoin_lib):\n        site.content_manager.contents[\"content.json\"][\"includes\"][\"data/test_include/content.json\"][\"files_allowed\"] = \"([a-zA-Z]+)*\"\n        with pytest.raises(UnsafePatternError) as err:\n            with site.storage.open(\"data/test_include/content.json\") as data:\n                site.content_manager.verifyFile(\"data/test_include/content.json\", data, ignore_same=False)\n        assert \"Potentially unsafe\" in str(err.value)\n\n        site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"permission_rules\"][\"([a-zA-Z]+)*\"] = {\"max_size\": 0}\n        with pytest.raises(UnsafePatternError) as err:\n            with site.storage.open(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\") as data:\n                site.content_manager.verifyFile(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", data, ignore_same=False)\n        assert \"Potentially unsafe\" in str(err.value)\n\n    def testPathValidation(self, site):\n        assert site.content_manager.isValidRelativePath(\"test.txt\")\n        assert site.content_manager.isValidRelativePath(\"test/!@#$%^&().txt\")\n        assert site.content_manager.isValidRelativePath(\"ÜøßÂŒƂÆÇ.txt\")\n        assert site.content_manager.isValidRelativePath(\"тест.текст\")\n        assert site.content_manager.isValidRelativePath(\"𝐮𝐧𝐢𝐜𝐨𝐝𝐞𝑖𝑠𝒂𝒘𝒆𝒔𝒐𝒎𝒆\")\n\n        # Test rules based on https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names\n\n        assert not site.content_manager.isValidRelativePath(\"any\\\\hello.txt\")  # \\ not allowed\n        assert not site.content_manager.isValidRelativePath(\"/hello.txt\")  # Cannot start with /\n        assert not site.content_manager.isValidRelativePath(\"\\\\hello.txt\")  # Cannot start with \\\n        assert not site.content_manager.isValidRelativePath(\"../hello.txt\")  # Not allowed .. in path\n        assert not site.content_manager.isValidRelativePath(\"\\0hello.txt\")  # NULL character\n        assert not site.content_manager.isValidRelativePath(\"\\31hello.txt\")  # 0-31 (ASCII control characters)\n        assert not site.content_manager.isValidRelativePath(\"any/hello.txt \")  # Cannot end with space\n        assert not site.content_manager.isValidRelativePath(\"any/hello.txt.\")  # Cannot end with dot\n        assert site.content_manager.isValidRelativePath(\".hello.txt\")  # Allow start with dot\n        assert not site.content_manager.isValidRelativePath(\"any/CON\")  # Protected names on Windows\n        assert not site.content_manager.isValidRelativePath(\"CON/any.txt\")\n        assert not site.content_manager.isValidRelativePath(\"any/lpt1.txt\")\n        assert site.content_manager.isValidRelativePath(\"any/CONAN\")\n        assert not site.content_manager.isValidRelativePath(\"any/CONOUT$\")\n        assert not site.content_manager.isValidRelativePath(\"a\" * 256)  # Max 255 characters allowed\n"
  },
  {
    "path": "src/Test/TestContentUser.py",
    "content": "import json\nimport io\n\nimport pytest\n\nfrom Crypt import CryptBitcoin\nfrom Content.ContentManager import VerifyError, SignError\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestContentUser:\n    def testSigners(self, site):\n        # File info for not existing user file\n        file_info = site.content_manager.getFileInfo(\"data/users/notexist/data.json\")\n        assert file_info[\"content_inner_path\"] == \"data/users/notexist/content.json\"\n        file_info = site.content_manager.getFileInfo(\"data/users/notexist/a/b/data.json\")\n        assert file_info[\"content_inner_path\"] == \"data/users/notexist/content.json\"\n        valid_signers = site.content_manager.getValidSigners(\"data/users/notexist/content.json\")\n        assert valid_signers == [\"14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\", \"notexist\", \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\"]\n\n        # File info for exsitsing user file\n        valid_signers = site.content_manager.getValidSigners(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\")\n        assert '1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT' in valid_signers  # The site address\n        assert '14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet' in valid_signers  # Admin user defined in data/users/content.json\n        assert '1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C' in valid_signers  # The user itself\n        assert len(valid_signers) == 3  # No more valid signers\n\n        # Valid signer for banned user\n        user_content = site.storage.loadJson(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\")\n        user_content[\"cert_user_id\"] = \"bad@zeroid.bit\"\n\n        valid_signers = site.content_manager.getValidSigners(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n        assert '1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT' in valid_signers  # The site address\n        assert '14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet' in valid_signers  # Admin user defined in data/users/content.json\n        assert '1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C' not in valid_signers  # The user itself\n\n    def testRules(self, site):\n        # We going to manipulate it this test rules based on data/users/content.json\n        user_content = site.storage.loadJson(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\")\n\n        # Known user\n        user_content[\"cert_auth_type\"] = \"web\"\n        user_content[\"cert_user_id\"] = \"nofish@zeroid.bit\"\n        rules = site.content_manager.getRules(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n        assert rules[\"max_size\"] == 100000\n        assert \"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\" in rules[\"signers\"]\n\n        # Unknown user\n        user_content[\"cert_auth_type\"] = \"web\"\n        user_content[\"cert_user_id\"] = \"noone@zeroid.bit\"\n        rules = site.content_manager.getRules(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n        assert rules[\"max_size\"] == 10000\n        assert \"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\" in rules[\"signers\"]\n\n        # User with more size limit based on auth type\n        user_content[\"cert_auth_type\"] = \"bitmsg\"\n        user_content[\"cert_user_id\"] = \"noone@zeroid.bit\"\n        rules = site.content_manager.getRules(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n        assert rules[\"max_size\"] == 15000\n        assert \"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\" in rules[\"signers\"]\n\n        # Banned user\n        user_content[\"cert_auth_type\"] = \"web\"\n        user_content[\"cert_user_id\"] = \"bad@zeroid.bit\"\n        rules = site.content_manager.getRules(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n        assert \"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\" not in rules[\"signers\"]\n\n    def testRulesAddress(self, site):\n        user_inner_path = \"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/content.json\"\n        user_content = site.storage.loadJson(user_inner_path)\n\n        rules = site.content_manager.getRules(user_inner_path, user_content)\n        assert rules[\"max_size\"] == 10000\n        assert \"1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9\" in rules[\"signers\"]\n\n        users_content = site.content_manager.contents[\"data/users/content.json\"]\n\n        # Ban user based on address\n        users_content[\"user_contents\"][\"permissions\"][\"1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9\"] = False\n        rules = site.content_manager.getRules(user_inner_path, user_content)\n        assert \"1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9\" not in rules[\"signers\"]\n\n        # Change max allowed size\n        users_content[\"user_contents\"][\"permissions\"][\"1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9\"] = {\"max_size\": 20000}\n        rules = site.content_manager.getRules(user_inner_path, user_content)\n        assert rules[\"max_size\"] == 20000\n\n    def testVerifyAddress(self, site):\n        privatekey = \"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\"  # For 1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\n        user_inner_path = \"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/content.json\"\n        data_dict = site.storage.loadJson(user_inner_path)\n        users_content = site.content_manager.contents[\"data/users/content.json\"]\n\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        assert site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n\n        # Test error on 15k data.json\n        data_dict[\"files\"][\"data.json\"][\"size\"] = 1024 * 15\n        del data_dict[\"signs\"]  # Remove signs before signing\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n        assert \"Include too large\" in str(err.value)\n\n        # Give more space based on address\n        users_content[\"user_contents\"][\"permissions\"][\"1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9\"] = {\"max_size\": 20000}\n        del data_dict[\"signs\"]  # Remove signs before signing\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        assert site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n\n    def testVerify(self, site):\n        privatekey = \"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\"  # For 1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\n        user_inner_path = \"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/content.json\"\n        data_dict = site.storage.loadJson(user_inner_path)\n        users_content = site.content_manager.contents[\"data/users/content.json\"]\n\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        assert site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n\n        # Test max size exception by setting allowed to 0\n        rules = site.content_manager.getRules(user_inner_path, data_dict)\n        assert rules[\"max_size\"] == 10000\n        assert users_content[\"user_contents\"][\"permission_rules\"][\".*\"][\"max_size\"] == 10000\n\n        users_content[\"user_contents\"][\"permission_rules\"][\".*\"][\"max_size\"] = 0\n        rules = site.content_manager.getRules(user_inner_path, data_dict)\n        assert rules[\"max_size\"] == 0\n        data = io.BytesIO(json.dumps(data_dict).encode())\n\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n        assert \"Include too large\" in str(err.value)\n        users_content[\"user_contents\"][\"permission_rules\"][\".*\"][\"max_size\"] = 10000  # Reset\n\n        # Test max optional size exception\n        # 1 MB gif = Allowed\n        data_dict[\"files_optional\"][\"peanut-butter-jelly-time.gif\"][\"size\"] = 1024 * 1024\n        del data_dict[\"signs\"]  # Remove signs before signing\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        assert site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n\n        # 100 MB gif = Not allowed\n        data_dict[\"files_optional\"][\"peanut-butter-jelly-time.gif\"][\"size\"] = 100 * 1024 * 1024\n        del data_dict[\"signs\"]  # Remove signs before signing\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n        assert \"Include optional files too large\" in str(err.value)\n        data_dict[\"files_optional\"][\"peanut-butter-jelly-time.gif\"][\"size\"] = 1024 * 1024  # Reset\n\n        # hello.exe = Not allowed\n        data_dict[\"files_optional\"][\"hello.exe\"] = data_dict[\"files_optional\"][\"peanut-butter-jelly-time.gif\"]\n        del data_dict[\"signs\"]  # Remove signs before signing\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n        assert \"Optional file not allowed\" in str(err.value)\n        del data_dict[\"files_optional\"][\"hello.exe\"]  # Reset\n\n        # Includes not allowed in user content\n        data_dict[\"includes\"] = {\"other.json\": {}}\n        del data_dict[\"signs\"]  # Remove signs before signing\n        data_dict[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(data_dict, sort_keys=True), privatekey)\n        }\n        data = io.BytesIO(json.dumps(data_dict).encode())\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(user_inner_path, data, ignore_same=False)\n        assert \"Includes not allowed\" in str(err.value)\n\n    def testCert(self, site):\n        # user_addr = \"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C\"\n        user_priv = \"5Kk7FSA63FC2ViKmKLuBxk9gQkaQ5713hKq8LmFAf4cVeXh6K6A\"\n        # cert_addr = \"14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\"\n        cert_priv = \"5JusJDSjHaMHwUjDT3o6eQ54pA6poo8La5fAgn1wNc3iK59jxjA\"\n\n        # Check if the user file is loaded\n        assert \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\" in site.content_manager.contents\n        user_content = site.content_manager.contents[\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\"]\n        rules_content = site.content_manager.contents[\"data/users/content.json\"]\n\n        # Override valid cert signers for the test\n        rules_content[\"user_contents\"][\"cert_signers\"][\"zeroid.bit\"] = [\n            \"14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\",\n            \"1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz\"\n        ]\n\n        # Check valid cert signers\n        rules = site.content_manager.getRules(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n        assert rules[\"cert_signers\"] == {\"zeroid.bit\": [\n            \"14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\",\n            \"1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz\"\n        ]}\n\n        # Sign a valid cert\n        user_content[\"cert_sign\"] = CryptBitcoin.sign(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C#%s/%s\" % (\n            user_content[\"cert_auth_type\"],\n            user_content[\"cert_user_id\"].split(\"@\")[0]\n        ), cert_priv)\n\n        # Verify cert\n        assert site.content_manager.verifyCert(\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_content)\n\n        # Verify if the cert is valid for other address\n        assert not site.content_manager.verifyCert(\"data/users/badaddress/content.json\", user_content)\n\n        # Sign user content\n        signed_content = site.content_manager.sign(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_priv, filewrite=False\n        )\n\n        # Test user cert\n        assert site.content_manager.verifyFile(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n            io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n        )\n\n        # Test banned user\n        cert_user_id = user_content[\"cert_user_id\"]  # My username\n        site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"permissions\"][cert_user_id] = False\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\n                \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n                io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n            )\n        assert \"Valid signs: 0/1\" in str(err.value)\n        del site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"permissions\"][cert_user_id]  # Reset\n\n        # Test invalid cert\n        user_content[\"cert_sign\"] = CryptBitcoin.sign(\n            \"badaddress#%s/%s\" % (user_content[\"cert_auth_type\"], user_content[\"cert_user_id\"]), cert_priv\n        )\n        signed_content = site.content_manager.sign(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_priv, filewrite=False\n        )\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\n                \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n                io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n            )\n        assert \"Invalid cert\" in str(err.value)\n\n        # Test banned user, signed by the site owner\n        user_content[\"cert_sign\"] = CryptBitcoin.sign(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C#%s/%s\" % (\n            user_content[\"cert_auth_type\"],\n            user_content[\"cert_user_id\"].split(\"@\")[0]\n        ), cert_priv)\n        cert_user_id = user_content[\"cert_user_id\"]  # My username\n        site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"permissions\"][cert_user_id] = False\n\n        site_privatekey = \"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\"  # For 1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\n        del user_content[\"signs\"]  # Remove signs before signing\n        user_content[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(user_content, sort_keys=True), site_privatekey)\n        }\n        assert site.content_manager.verifyFile(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n            io.BytesIO(json.dumps(user_content).encode()), ignore_same=False\n        )\n\n    def testMissingCert(self, site):\n        user_priv = \"5Kk7FSA63FC2ViKmKLuBxk9gQkaQ5713hKq8LmFAf4cVeXh6K6A\"\n        cert_priv = \"5JusJDSjHaMHwUjDT3o6eQ54pA6poo8La5fAgn1wNc3iK59jxjA\"\n\n        user_content = site.content_manager.contents[\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\"]\n        rules_content = site.content_manager.contents[\"data/users/content.json\"]\n\n        # Override valid cert signers for the test\n        rules_content[\"user_contents\"][\"cert_signers\"][\"zeroid.bit\"] = [\n            \"14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\",\n            \"1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz\"\n        ]\n\n        # Sign a valid cert\n        user_content[\"cert_sign\"] = CryptBitcoin.sign(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C#%s/%s\" % (\n            user_content[\"cert_auth_type\"],\n            user_content[\"cert_user_id\"].split(\"@\")[0]\n        ), cert_priv)\n        signed_content = site.content_manager.sign(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_priv, filewrite=False\n        )\n\n        assert site.content_manager.verifyFile(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n            io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n        )\n\n        # Test invalid cert_user_id\n        user_content[\"cert_user_id\"] = \"nodomain\"\n        user_content[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(user_content, sort_keys=True), user_priv)\n        }\n        signed_content = site.content_manager.sign(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_priv, filewrite=False\n        )\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\n                \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n                io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n            )\n        assert \"Invalid domain in cert_user_id\" in str(err.value)\n\n        # Test removed cert\n        del user_content[\"cert_user_id\"]\n        del user_content[\"cert_auth_type\"]\n        del user_content[\"signs\"]  # Remove signs before signing\n        user_content[\"signs\"] = {\n            \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": CryptBitcoin.sign(json.dumps(user_content, sort_keys=True), user_priv)\n        }\n        signed_content = site.content_manager.sign(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_priv, filewrite=False\n        )\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\n                \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n                io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n            )\n        assert \"Missing cert_user_id\" in str(err.value)\n\n\n    def testCertSignersPattern(self, site):\n        user_priv = \"5Kk7FSA63FC2ViKmKLuBxk9gQkaQ5713hKq8LmFAf4cVeXh6K6A\"\n        cert_priv = \"5JusJDSjHaMHwUjDT3o6eQ54pA6poo8La5fAgn1wNc3iK59jxjA\"  # For 14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\n\n        user_content = site.content_manager.contents[\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\"]\n        rules_content = site.content_manager.contents[\"data/users/content.json\"]\n\n        # Override valid cert signers for the test\n        rules_content[\"user_contents\"][\"cert_signers_pattern\"] = \"14wgQ[0-9][A-Z]\"\n\n        # Sign a valid cert\n        user_content[\"cert_user_id\"] = \"certuser@14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\"\n        user_content[\"cert_sign\"] = CryptBitcoin.sign(\"1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C#%s/%s\" % (\n            user_content[\"cert_auth_type\"],\n            \"certuser\"\n        ), cert_priv)\n        signed_content = site.content_manager.sign(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\", user_priv, filewrite=False\n        )\n\n        assert site.content_manager.verifyFile(\n            \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n            io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n        )\n\n        # Cert does not matches the pattern\n        rules_content[\"user_contents\"][\"cert_signers_pattern\"] = \"14wgX[0-9][A-Z]\"\n\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\n                \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n                io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n            )\n        assert \"Invalid cert signer: 14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\" in str(err.value)\n\n        # Removed cert_signers_pattern\n        del rules_content[\"user_contents\"][\"cert_signers_pattern\"]\n\n        with pytest.raises(VerifyError) as err:\n            site.content_manager.verifyFile(\n                \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n                io.BytesIO(json.dumps(signed_content).encode()), ignore_same=False\n            )\n        assert \"Invalid cert signer: 14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\" in str(err.value)\n\n\n    def testNewFile(self, site):\n        privatekey = \"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\"  # For 1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\n        inner_path = \"data/users/1NEWrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\"\n\n        site.storage.writeJson(inner_path, {\"test\": \"data\"})\n        site.content_manager.sign(inner_path, privatekey)\n        assert \"test\" in site.storage.loadJson(inner_path)\n\n        site.storage.delete(inner_path)\n"
  },
  {
    "path": "src/Test/TestCryptBitcoin.py",
    "content": "from Crypt import CryptBitcoin\n\n\nclass TestCryptBitcoin:\n    def testSign(self, crypt_bitcoin_lib):\n        privatekey = \"5K9S6dVpufGnroRgFrT6wsKiz2mJRYsC73eWDmajaHserAp3F1C\"\n        privatekey_bad = \"5Jbm9rrusXyApAoM8YoM4Rja337zMMoBUMRJ1uijiguU2aZRnwC\"\n\n        # Get address by privatekey\n        address = crypt_bitcoin_lib.privatekeyToAddress(privatekey)\n        assert address == \"1MpDMxFeDUkiHohxx9tbGLeEGEuR4ZNsJz\"\n\n        address_bad = crypt_bitcoin_lib.privatekeyToAddress(privatekey_bad)\n        assert address_bad != \"1MpDMxFeDUkiHohxx9tbGLeEGEuR4ZNsJz\"\n\n        # Text signing\n        data_len_list = list(range(0, 300, 10))\n        data_len_list += [1024, 2048, 1024 * 128, 1024 * 1024, 1024 * 2048]\n        for data_len in data_len_list:\n            data = data_len * \"!\"\n            sign = crypt_bitcoin_lib.sign(data, privatekey)\n\n            assert crypt_bitcoin_lib.verify(data, address, sign)\n            assert not crypt_bitcoin_lib.verify(\"invalid\" + data, address, sign)\n\n        # Signed by bad privatekey\n        sign_bad = crypt_bitcoin_lib.sign(\"hello\", privatekey_bad)\n        assert not crypt_bitcoin_lib.verify(\"hello\", address, sign_bad)\n\n    def testVerify(self, crypt_bitcoin_lib):\n        sign_uncompressed = b'G6YkcFTuwKMVMHI2yycGQIFGbCZVNsZEZvSlOhKpHUt/BlADY94egmDAWdlrbbFrP9wH4aKcEfbLO8sa6f63VU0='\n        assert crypt_bitcoin_lib.verify(\"1NQUem2M4cAqWua6BVFBADtcSP55P4QobM#web/gitcenter\", \"19Bir5zRm1yo4pw9uuxQL8xwf9b7jqMpR\", sign_uncompressed)\n\n        sign_compressed = b'H6YkcFTuwKMVMHI2yycGQIFGbCZVNsZEZvSlOhKpHUt/BlADY94egmDAWdlrbbFrP9wH4aKcEfbLO8sa6f63VU0='\n        assert crypt_bitcoin_lib.verify(\"1NQUem2M4cAqWua6BVFBADtcSP55P4QobM#web/gitcenter\", \"1KH5BdNnqxh2KRWMMT8wUXzUgz4vVQ4S8p\", sign_compressed)\n\n    def testNewPrivatekey(self):\n        assert CryptBitcoin.newPrivatekey() != CryptBitcoin.newPrivatekey()\n        assert CryptBitcoin.privatekeyToAddress(CryptBitcoin.newPrivatekey())\n\n    def testNewSeed(self):\n        assert CryptBitcoin.newSeed() != CryptBitcoin.newSeed()\n        assert CryptBitcoin.privatekeyToAddress(\n            CryptBitcoin.hdPrivatekey(CryptBitcoin.newSeed(), 0)\n        )\n        assert CryptBitcoin.privatekeyToAddress(\n            CryptBitcoin.hdPrivatekey(CryptBitcoin.newSeed(), 2**256)\n        )\n"
  },
  {
    "path": "src/Test/TestCryptConnection.py",
    "content": "import os\n\nfrom Config import config\nfrom Crypt import CryptConnection\n\n\nclass TestCryptConnection:\n    def testSslCert(self):\n        # Remove old certs\n        if os.path.isfile(\"%s/cert-rsa.pem\" % config.data_dir):\n            os.unlink(\"%s/cert-rsa.pem\" % config.data_dir)\n        if os.path.isfile(\"%s/key-rsa.pem\" % config.data_dir):\n            os.unlink(\"%s/key-rsa.pem\" % config.data_dir)\n\n        # Generate certs\n        CryptConnection.manager.loadCerts()\n\n        assert \"tls-rsa\" in CryptConnection.manager.crypt_supported\n        assert CryptConnection.manager.selectCrypt([\"tls-rsa\", \"unknown\"]) == \"tls-rsa\"  # It should choose the known crypt\n\n        # Check openssl cert generation\n        assert os.path.isfile(\"%s/cert-rsa.pem\" % config.data_dir)\n        assert os.path.isfile(\"%s/key-rsa.pem\" % config.data_dir)\n"
  },
  {
    "path": "src/Test/TestCryptHash.py",
    "content": "import base64\n\nfrom Crypt import CryptHash\n\nsha512t_sum_hex = \"2e9466d8aa1f340c91203b4ddbe9b6669879616a1b8e9571058a74195937598d\"\nsha512t_sum_bin = b\".\\x94f\\xd8\\xaa\\x1f4\\x0c\\x91 ;M\\xdb\\xe9\\xb6f\\x98yaj\\x1b\\x8e\\x95q\\x05\\x8at\\x19Y7Y\\x8d\"\nsha256_sum_hex = \"340cd04be7f530e3a7c1bc7b24f225ba5762ec7063a56e1ae01a30d56722e5c3\"\n\n\nclass TestCryptBitcoin:\n\n    def testSha(self, site):\n        file_path = site.storage.getPath(\"dbschema.json\")\n        assert CryptHash.sha512sum(file_path) == sha512t_sum_hex\n        assert CryptHash.sha512sum(open(file_path, \"rb\")) == sha512t_sum_hex\n        assert CryptHash.sha512sum(open(file_path, \"rb\"), format=\"digest\") == sha512t_sum_bin\n\n        assert CryptHash.sha256sum(file_path) == sha256_sum_hex\n        assert CryptHash.sha256sum(open(file_path, \"rb\")) == sha256_sum_hex\n\n        with open(file_path, \"rb\") as f:\n            hash = CryptHash.Sha512t(f.read(100))\n            hash.hexdigest() != sha512t_sum_hex\n            hash.update(f.read(1024 * 1024))\n            assert hash.hexdigest() == sha512t_sum_hex\n\n    def testRandom(self):\n        assert len(CryptHash.random(64)) == 64\n        assert CryptHash.random() != CryptHash.random()\n        assert bytes.fromhex(CryptHash.random(encoding=\"hex\"))\n        assert base64.b64decode(CryptHash.random(encoding=\"base64\"))\n"
  },
  {
    "path": "src/Test/TestDb.py",
    "content": "import io\n\n\nclass TestDb:\n    def testCheckTables(self, db):\n        tables = [row[\"name\"] for row in db.execute(\"SELECT name FROM sqlite_master WHERE type='table'\")]\n        assert \"keyvalue\" in tables  # To store simple key -> value\n        assert \"json\" in tables  # Json file path registry\n        assert \"test\" in tables  # The table defined in dbschema.json\n\n        # Verify test table\n        cols = [col[\"name\"] for col in db.execute(\"PRAGMA table_info(test)\")]\n        assert \"test_id\" in cols\n        assert \"title\" in cols\n\n        # Add new table\n        assert \"newtest\" not in tables\n        db.schema[\"tables\"][\"newtest\"] = {\n            \"cols\": [\n                [\"newtest_id\", \"INTEGER\"],\n                [\"newtitle\", \"TEXT\"],\n            ],\n            \"indexes\": [\"CREATE UNIQUE INDEX newtest_id ON newtest(newtest_id)\"],\n            \"schema_changed\": 1426195822\n        }\n        db.checkTables()\n        tables = [row[\"name\"] for row in db.execute(\"SELECT name FROM sqlite_master WHERE type='table'\")]\n        assert \"test\" in tables\n        assert \"newtest\" in tables\n\n    def testQueries(self, db):\n        # Test insert\n        for i in range(100):\n            db.execute(\"INSERT INTO test ?\", {\"test_id\": i, \"title\": \"Test #%s\" % i})\n\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test\").fetchone()[\"num\"] == 100\n\n        # Test single select\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test WHERE ?\", {\"test_id\": 1}).fetchone()[\"num\"] == 1\n\n        # Test multiple select\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test WHERE ?\", {\"test_id\": [1, 2, 3]}).fetchone()[\"num\"] == 3\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"test_id\": [1, 2, 3], \"title\": \"Test #2\"}\n        ).fetchone()[\"num\"] == 1\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"test_id\": [1, 2, 3], \"title\": [\"Test #2\", \"Test #3\", \"Test #4\"]}\n        ).fetchone()[\"num\"] == 2\n\n        # Test multiple select using named params\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test WHERE test_id IN :test_id\", {\"test_id\": [1, 2, 3]}).fetchone()[\"num\"] == 3\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE test_id IN :test_id AND title = :title\",\n            {\"test_id\": [1, 2, 3], \"title\": \"Test #2\"}\n        ).fetchone()[\"num\"] == 1\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE test_id IN :test_id AND title IN :title\",\n            {\"test_id\": [1, 2, 3], \"title\": [\"Test #2\", \"Test #3\", \"Test #4\"]}\n        ).fetchone()[\"num\"] == 2\n\n        # Large ammount of IN values\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"not__test_id\": list(range(2, 3000))}\n        ).fetchone()[\"num\"] == 2\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"test_id\": list(range(50, 3000))}\n        ).fetchone()[\"num\"] == 50\n\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"not__title\": [\"Test #%s\" % i for i in range(50, 3000)]}\n        ).fetchone()[\"num\"] == 50\n\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"title__like\": \"%20%\"}\n        ).fetchone()[\"num\"] == 1\n\n        # Test named parameter escaping\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE test_id = :test_id AND title LIKE :titlelike\",\n            {\"test_id\": 1, \"titlelike\": \"Test%\"}\n        ).fetchone()[\"num\"] == 1\n\n    def testEscaping(self, db):\n        # Test insert\n        for i in range(100):\n            db.execute(\"INSERT INTO test ?\", {\"test_id\": i, \"title\": \"Test '\\\" #%s\" % i})\n\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"title\": \"Test '\\\" #1\"}\n        ).fetchone()[\"num\"] == 1\n\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"title\": [\"Test '\\\" #%s\" % i for i in range(0, 50)]}\n        ).fetchone()[\"num\"] == 50\n\n        assert db.execute(\n            \"SELECT COUNT(*) AS num FROM test WHERE ?\",\n            {\"not__title\": [\"Test '\\\" #%s\" % i for i in range(50, 3000)]}\n        ).fetchone()[\"num\"] == 50\n\n\n    def testUpdateJson(self, db):\n        f = io.BytesIO()\n        f.write(\"\"\"\n            {\n                \"test\": [\n                    {\"test_id\": 1, \"title\": \"Test 1 title\", \"extra col\": \"Ignore it\"}\n                ]\n            }\n        \"\"\".encode())\n        f.seek(0)\n        assert db.updateJson(db.db_dir + \"data.json\", f) is True\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test_importfilter\").fetchone()[\"num\"] == 1\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test\").fetchone()[\"num\"] == 1\n\n    def testUnsafePattern(self, db):\n        db.schema[\"maps\"] = {\"[A-Za-z.]*\": db.schema[\"maps\"][\"data.json\"]}  # Only repetition of . supported\n        f = io.StringIO()\n        f.write(\"\"\"\n            {\n                \"test\": [\n                    {\"test_id\": 1, \"title\": \"Test 1 title\", \"extra col\": \"Ignore it\"}\n                ]\n            }\n        \"\"\")\n        f.seek(0)\n        assert db.updateJson(db.db_dir + \"data.json\", f) is False\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test_importfilter\").fetchone()[\"num\"] == 0\n        assert db.execute(\"SELECT COUNT(*) AS num FROM test\").fetchone()[\"num\"] == 0\n"
  },
  {
    "path": "src/Test/TestDbQuery.py",
    "content": "import re\n\nfrom Db.DbQuery import DbQuery\n\n\nclass TestDbQuery:\n    def testParse(self):\n        query_text = \"\"\"\n            SELECT\n             'comment' AS type,\n             date_added, post.title AS title,\n             keyvalue.value || ': ' || comment.body AS body,\n             '?Post:' || comment.post_id || '#Comments' AS url\n            FROM\n             comment\n             LEFT JOIN json USING (json_id)\n             LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json')\n             LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id')\n             LEFT JOIN post ON (comment.post_id = post.post_id)\n            WHERE\n             post.date_added > 123\n            ORDER BY\n             date_added DESC\n            LIMIT 20\n        \"\"\"\n        query = DbQuery(query_text)\n        assert query.parts[\"LIMIT\"] == \"20\"\n        assert query.fields[\"body\"] == \"keyvalue.value || ': ' || comment.body\"\n        assert re.sub(\"[ \\r\\n]\", \"\", str(query)) == re.sub(\"[ \\r\\n]\", \"\", query_text)\n        query.wheres.append(\"body LIKE '%hello%'\")\n        assert \"body LIKE '%hello%'\" in str(query)\n"
  },
  {
    "path": "src/Test/TestDebug.py",
    "content": "from Debug import Debug\nimport gevent\nimport os\nimport re\n\nimport pytest\n\n\nclass TestDebug:\n    @pytest.mark.parametrize(\"items,expected\", [\n        ([\"@/src/A/B/C.py:17\"], [\"A/B/C.py line 17\"]),  # basic test\n        ([\"@/src/Db/Db.py:17\"], [\"Db.py line 17\"]),  # path compression\n        ([\"%s:1\" % __file__], [\"TestDebug.py line 1\"]),\n        ([\"@/plugins/Chart/ChartDb.py:100\"], [\"ChartDb.py line 100\"]),  # plugins\n        ([\"@/main.py:17\"], [\"main.py line 17\"]),  # root\n        ([\"@\\\\src\\\\Db\\\\__init__.py:17\"], [\"Db/__init__.py line 17\"]),  # Windows paths\n        ([\"<frozen importlib._bootstrap>:1\"], []),  # importlib builtins\n        ([\"<frozen importlib._bootstrap_external>:1\"], []),  # importlib builtins\n        ([\"/home/ivanq/ZeroNet/src/main.py:13\"], [\"?/src/main.py line 13\"]),  # best-effort anonymization\n        ([\"C:\\\\ZeroNet\\\\core\\\\src\\\\main.py:13\"], [\"?/src/main.py line 13\"]),\n        ([\"/root/main.py:17\"], [\"/root/main.py line 17\"]),\n        ([\"{gevent}:13\"], [\"<gevent>/__init__.py line 13\"]),  # modules\n        ([\"{os}:13\"], [\"<os> line 13\"]),  # python builtin modules\n        ([\"src/gevent/event.py:17\"], [\"<gevent>/event.py line 17\"]),  # gevent-overriden __file__\n        ([\"@/src/Db/Db.py:17\", \"@/src/Db/DbQuery.py:1\"], [\"Db.py line 17\", \"DbQuery.py line 1\"]),  # mutliple args\n        ([\"@/src/Db/Db.py:17\", \"@/src/Db/Db.py:1\"], [\"Db.py line 17\", \"1\"]),  # same file\n        ([\"{os}:1\", \"@/src/Db/Db.py:17\"], [\"<os> line 1\", \"Db.py line 17\"]),  # builtins\n        ([\"{gevent}:1\"] + [\"{os}:3\"] * 4 + [\"@/src/Db/Db.py:17\"], [\"<gevent>/__init__.py line 1\", \"...\", \"Db.py line 17\"])\n    ])\n    def testFormatTraceback(self, items, expected):\n        q_items = []\n        for item in items:\n            file, line = item.rsplit(\":\", 1)\n            if file.startswith(\"@\"):\n                file = Debug.root_dir + file[1:]\n            file = file.replace(\"{os}\", os.__file__)\n            file = file.replace(\"{gevent}\", gevent.__file__)\n            q_items.append((file, int(line)))\n        assert Debug.formatTraceback(q_items) == expected\n\n    def testFormatException(self):\n        try:\n            raise ValueError(\"Test exception\")\n        except Exception:\n            assert re.match(r\"ValueError: Test exception in TestDebug.py line [0-9]+\", Debug.formatException())\n        try:\n            os.path.abspath(1)\n        except Exception:\n            assert re.search(r\"in TestDebug.py line [0-9]+ > <(posixpath|ntpath)> line \", Debug.formatException())\n\n    def testFormatStack(self):\n        assert re.match(r\"TestDebug.py line [0-9]+ > <_pytest>/python.py line [0-9]+\", Debug.formatStack())\n"
  },
  {
    "path": "src/Test/TestDiff.py",
    "content": "import io\n\nfrom util import Diff\n\n\nclass TestDiff:\n    def testDiff(self):\n        assert Diff.diff(\n            [],\n            [\"one\", \"two\", \"three\"]\n        ) == [(\"+\", [\"one\", \"two\",\"three\"])]\n\n        assert Diff.diff(\n            [\"one\", \"two\", \"three\"],\n            [\"one\", \"two\", \"three\", \"four\", \"five\"]\n        ) == [(\"=\", 11), (\"+\", [\"four\", \"five\"])]\n\n        assert Diff.diff(\n            [\"one\", \"two\", \"three\", \"six\"],\n            [\"one\", \"two\", \"three\", \"four\", \"five\", \"six\"]\n        ) == [(\"=\", 11), (\"+\", [\"four\", \"five\"]), (\"=\", 3)]\n\n        assert Diff.diff(\n            [\"one\", \"two\", \"three\", \"hmm\", \"six\"],\n            [\"one\", \"two\", \"three\", \"four\", \"five\", \"six\"]\n        ) == [(\"=\", 11), (\"-\", 3), (\"+\", [\"four\", \"five\"]), (\"=\", 3)]\n\n        assert Diff.diff(\n            [\"one\", \"two\", \"three\"],\n            []\n        ) == [(\"-\", 11)]\n\n    def testUtf8(self):\n        assert Diff.diff(\n            [\"one\", \"\\xe5\\xad\\xa6\\xe4\\xb9\\xa0\\xe4\\xb8\\x8b\", \"two\", \"three\"],\n            [\"one\", \"\\xe5\\xad\\xa6\\xe4\\xb9\\xa0\\xe4\\xb8\\x8b\", \"two\", \"three\", \"four\", \"five\"]\n        ) == [(\"=\", 20), (\"+\", [\"four\", \"five\"])]\n\n    def testDiffLimit(self):\n        old_f = io.BytesIO(b\"one\\ntwo\\nthree\\nhmm\\nsix\")\n        new_f = io.BytesIO(b\"one\\ntwo\\nthree\\nfour\\nfive\\nsix\")\n        actions = Diff.diff(list(old_f), list(new_f), limit=1024)\n        assert actions\n\n        old_f = io.BytesIO(b\"one\\ntwo\\nthree\\nhmm\\nsix\")\n        new_f = io.BytesIO(b\"one\\ntwo\\nthree\\nfour\\nfive\\nsix\"*1024)\n        actions = Diff.diff(list(old_f), list(new_f), limit=1024)\n        assert actions is False\n\n    def testPatch(self):\n        old_f = io.BytesIO(b\"one\\ntwo\\nthree\\nhmm\\nsix\")\n        new_f = io.BytesIO(b\"one\\ntwo\\nthree\\nfour\\nfive\\nsix\")\n        actions = Diff.diff(\n            list(old_f),\n            list(new_f)\n        )\n        old_f.seek(0)\n        assert Diff.patch(old_f, actions).getvalue() == new_f.getvalue()\n"
  },
  {
    "path": "src/Test/TestEvent.py",
    "content": "import util\n\n\nclass ExampleClass(object):\n    def __init__(self):\n        self.called = []\n        self.onChanged = util.Event()\n\n    def increment(self, title):\n        self.called.append(title)\n\n\nclass TestEvent:\n    def testEvent(self):\n        test_obj = ExampleClass()\n        test_obj.onChanged.append(lambda: test_obj.increment(\"Called #1\"))\n        test_obj.onChanged.append(lambda: test_obj.increment(\"Called #2\"))\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once\"))\n\n        assert test_obj.called == []\n        test_obj.onChanged()\n        assert test_obj.called == [\"Called #1\", \"Called #2\", \"Once\"]\n        test_obj.onChanged()\n        test_obj.onChanged()\n        assert test_obj.called == [\"Called #1\", \"Called #2\", \"Once\", \"Called #1\", \"Called #2\", \"Called #1\", \"Called #2\"]\n\n    def testOnce(self):\n        test_obj = ExampleClass()\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #1\"))\n\n        # It should be called only once\n        assert test_obj.called == []\n        test_obj.onChanged()\n        assert test_obj.called == [\"Once test #1\"]\n        test_obj.onChanged()\n        test_obj.onChanged()\n        assert test_obj.called == [\"Once test #1\"]\n\n    def testOnceMultiple(self):\n        test_obj = ExampleClass()\n        # Allow queue more than once\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #1\"))\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #2\"))\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #3\"))\n\n        assert test_obj.called == []\n        test_obj.onChanged()\n        assert test_obj.called == [\"Once test #1\", \"Once test #2\", \"Once test #3\"]\n        test_obj.onChanged()\n        test_obj.onChanged()\n        assert test_obj.called == [\"Once test #1\", \"Once test #2\", \"Once test #3\"]\n\n    def testOnceNamed(self):\n        test_obj = ExampleClass()\n        # Dont store more that one from same type\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #1/1\"), \"type 1\")\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #1/2\"), \"type 1\")\n        test_obj.onChanged.once(lambda: test_obj.increment(\"Once test #2\"), \"type 2\")\n\n        assert test_obj.called == []\n        test_obj.onChanged()\n        assert test_obj.called == [\"Once test #1/1\", \"Once test #2\"]\n        test_obj.onChanged()\n        test_obj.onChanged()\n        assert test_obj.called == [\"Once test #1/1\", \"Once test #2\"]\n"
  },
  {
    "path": "src/Test/TestFileRequest.py",
    "content": "import io\n\nimport pytest\nimport time\n\nfrom Connection import ConnectionServer\nfrom Connection import Connection\nfrom File import FileServer\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestFileRequest:\n    def testGetFile(self, file_server, site):\n        file_server.ip_incoming = {}  # Reset flood protection\n        client = ConnectionServer(file_server.ip, 1545)\n\n        connection = client.getConnection(file_server.ip, 1544)\n        file_server.sites[site.address] = site\n\n        # Normal request\n        response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 0})\n        assert b\"sign\" in response[\"body\"]\n\n        response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 0, \"file_size\": site.storage.getSize(\"content.json\")})\n        assert b\"sign\" in response[\"body\"]\n\n        # Invalid file\n        response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"invalid.file\", \"location\": 0})\n        assert \"File read error\" in response[\"error\"]\n\n        # Location over size\n        response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 1024 * 1024})\n        assert \"File read error\" in response[\"error\"]\n\n        # Stream from parent dir\n        response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"../users.json\", \"location\": 0})\n        assert \"File read exception\" in response[\"error\"]\n\n        # Invalid site\n        response = connection.request(\"getFile\", {\"site\": \"\", \"inner_path\": \"users.json\", \"location\": 0})\n        assert \"Unknown site\" in response[\"error\"]\n\n        response = connection.request(\"getFile\", {\"site\": \".\", \"inner_path\": \"users.json\", \"location\": 0})\n        assert \"Unknown site\" in response[\"error\"]\n\n        # Invalid size\n        response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 0, \"file_size\": 1234})\n        assert \"File size does not match\" in response[\"error\"]\n\n        # Invalid path\n        for path in [\"../users.json\", \"./../users.json\", \"data/../content.json\", \".../users.json\"]:\n            for sep in [\"/\", \"\\\\\"]:\n                response = connection.request(\"getFile\", {\"site\": site.address, \"inner_path\": path.replace(\"/\", sep), \"location\": 0})\n                assert response[\"error\"] == 'File read exception'\n\n        connection.close()\n        client.stop()\n\n    def testStreamFile(self, file_server, site):\n        file_server.ip_incoming = {}  # Reset flood protection\n        client = ConnectionServer(file_server.ip, 1545)\n        connection = client.getConnection(file_server.ip, 1544)\n        file_server.sites[site.address] = site\n\n        buff = io.BytesIO()\n        response = connection.request(\"streamFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 0}, buff)\n        assert \"stream_bytes\" in response\n        assert b\"sign\" in buff.getvalue()\n\n        # Invalid file\n        buff = io.BytesIO()\n        response = connection.request(\"streamFile\", {\"site\": site.address, \"inner_path\": \"invalid.file\", \"location\": 0}, buff)\n        assert \"File read error\" in response[\"error\"]\n\n        # Location over size\n        buff = io.BytesIO()\n        response = connection.request(\n            \"streamFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 1024 * 1024}, buff\n        )\n        assert \"File read error\" in response[\"error\"]\n\n        # Stream from parent dir\n        buff = io.BytesIO()\n        response = connection.request(\"streamFile\", {\"site\": site.address, \"inner_path\": \"../users.json\", \"location\": 0}, buff)\n        assert \"File read exception\" in response[\"error\"]\n\n        connection.close()\n        client.stop()\n\n    def testPex(self, file_server, site, site_temp):\n        file_server.sites[site.address] = site\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n        connection = client.getConnection(file_server.ip, 1544)\n\n        # Add new fake peer to site\n        fake_peer = site.addPeer(file_server.ip_external, 11337, return_peer=True)\n        # Add fake connection to it\n        fake_peer.connection = Connection(file_server, file_server.ip_external, 11337)\n        fake_peer.connection.last_recv_time = time.time()\n        assert fake_peer in site.getConnectablePeers()\n\n        # Add file_server as peer to client\n        peer_file_server = site_temp.addPeer(file_server.ip, 1544)\n\n        assert \"%s:11337\" % file_server.ip_external not in site_temp.peers\n        assert peer_file_server.pex()\n        assert \"%s:11337\" % file_server.ip_external in site_temp.peers\n\n        # Should not exchange private peers from local network\n        fake_peer_private = site.addPeer(\"192.168.0.1\", 11337, return_peer=True)\n        assert fake_peer_private not in site.getConnectablePeers(allow_private=False)\n        fake_peer_private.connection = Connection(file_server, \"192.168.0.1\", 11337)\n        fake_peer_private.connection.last_recv_time = time.time()\n\n        assert \"192.168.0.1:11337\" not in site_temp.peers\n        assert not peer_file_server.pex()\n        assert \"192.168.0.1:11337\" not in site_temp.peers\n\n\n        connection.close()\n        client.stop()\n"
  },
  {
    "path": "src/Test/TestFlag.py",
    "content": "import os\n\nimport pytest\n\nfrom util.Flag import Flag\n\nclass TestFlag:\n    def testFlagging(self):\n        flag = Flag()\n        @flag.admin\n        @flag.no_multiuser\n        def testFn(anything):\n            return anything\n\n        assert \"admin\" in flag.db[\"testFn\"]\n        assert \"no_multiuser\" in flag.db[\"testFn\"]\n\n    def testSubclassedFlagging(self):\n        flag = Flag()\n        class Test:\n            @flag.admin\n            @flag.no_multiuser\n            def testFn(anything):\n                return anything\n\n        class SubTest(Test):\n            pass\n\n        assert \"admin\" in flag.db[\"testFn\"]\n        assert \"no_multiuser\" in flag.db[\"testFn\"]\n\n    def testInvalidFlag(self):\n        flag = Flag()\n        with pytest.raises(Exception) as err:\n            @flag.no_multiuser\n            @flag.unknown_flag\n            def testFn(anything):\n                return anything\n        assert \"Invalid flag\" in str(err.value)\n"
  },
  {
    "path": "src/Test/TestHelper.py",
    "content": "import socket\nimport struct\nimport os\n\nimport pytest\nfrom util import helper\nfrom Config import config\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestHelper:\n    def testShellquote(self):\n        assert helper.shellquote(\"hel'lo\") == \"\\\"hel'lo\\\"\"  # Allow '\n        assert helper.shellquote('hel\"lo') == '\"hello\"'  # Remove \"\n        assert helper.shellquote(\"hel'lo\", 'hel\"lo') == ('\"hel\\'lo\"', '\"hello\"')\n\n    def testPackAddress(self):\n        for port in [1, 1000, 65535]:\n            for ip in [\"1.1.1.1\", \"127.0.0.1\", \"0.0.0.0\", \"255.255.255.255\", \"192.168.1.1\"]:\n                assert len(helper.packAddress(ip, port)) == 6\n                assert helper.unpackAddress(helper.packAddress(ip, port)) == (ip, port)\n\n            for ip in [\"1:2:3:4:5:6:7:8\", \"::1\", \"2001:19f0:6c01:e76:5400:1ff:fed6:3eca\", \"2001:4860:4860::8888\"]:\n                assert len(helper.packAddress(ip, port)) == 18\n                assert helper.unpackAddress(helper.packAddress(ip, port)) == (ip, port)\n\n            assert len(helper.packOnionAddress(\"boot3rdez4rzn36x.onion\", port)) == 12\n            assert helper.unpackOnionAddress(helper.packOnionAddress(\"boot3rdez4rzn36x.onion\", port)) == (\"boot3rdez4rzn36x.onion\", port)\n\n        with pytest.raises(struct.error):\n            helper.packAddress(\"1.1.1.1\", 100000)\n\n        with pytest.raises(socket.error):\n            helper.packAddress(\"999.1.1.1\", 1)\n\n        with pytest.raises(Exception):\n            helper.unpackAddress(\"X\")\n\n    def testGetDirname(self):\n        assert helper.getDirname(\"data/users/content.json\") == \"data/users/\"\n        assert helper.getDirname(\"data/users\") == \"data/\"\n        assert helper.getDirname(\"\") == \"\"\n        assert helper.getDirname(\"content.json\") == \"\"\n        assert helper.getDirname(\"data/users/\") == \"data/users/\"\n        assert helper.getDirname(\"/data/users/content.json\") == \"data/users/\"\n\n    def testGetFilename(self):\n        assert helper.getFilename(\"data/users/content.json\") == \"content.json\"\n        assert helper.getFilename(\"data/users\") == \"users\"\n        assert helper.getFilename(\"\") == \"\"\n        assert helper.getFilename(\"content.json\") == \"content.json\"\n        assert helper.getFilename(\"data/users/\") == \"\"\n        assert helper.getFilename(\"/data/users/content.json\") == \"content.json\"\n\n    def testIsIp(self):\n        assert helper.isIp(\"1.2.3.4\")\n        assert helper.isIp(\"255.255.255.255\")\n        assert not helper.isIp(\"any.host\")\n        assert not helper.isIp(\"1.2.3.4.com\")\n        assert not helper.isIp(\"1.2.3.4.any.host\")\n\n    def testIsPrivateIp(self):\n        assert helper.isPrivateIp(\"192.168.1.1\")\n        assert not helper.isPrivateIp(\"1.1.1.1\")\n        assert helper.isPrivateIp(\"fe80::44f0:3d0:4e6:637c\")\n        assert not helper.isPrivateIp(\"fca5:95d6:bfde:d902:8951:276e:1111:a22c\")  # cjdns\n\n    def testOpenLocked(self):\n        locked_f = helper.openLocked(config.data_dir + \"/locked.file\")\n        assert locked_f\n        with pytest.raises(BlockingIOError):\n            locked_f_again = helper.openLocked(config.data_dir + \"/locked.file\")\n        locked_f_different = helper.openLocked(config.data_dir + \"/locked_different.file\")\n\n        locked_f.close()\n        locked_f_different.close()\n\n        os.unlink(locked_f.name)\n        os.unlink(locked_f_different.name)\n"
  },
  {
    "path": "src/Test/TestMsgpack.py",
    "content": "import io\nimport os\n\nimport msgpack\nimport pytest\n\nfrom Config import config\nfrom util import Msgpack\nfrom collections import OrderedDict\n\n\nclass TestMsgpack:\n    test_data = OrderedDict(\n        sorted({\"cmd\": \"fileGet\", \"bin\": b'p\\x81zDhL\\xf0O\\xd0\\xaf', \"params\": {\"site\": \"1Site\"}, \"utf8\": b'\\xc3\\xa1rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91'.decode(\"utf8\"), \"list\": [b'p\\x81zDhL\\xf0O\\xd0\\xaf', b'p\\x81zDhL\\xf0O\\xd0\\xaf']}.items())\n    )\n\n    def testPacking(self):\n        assert Msgpack.pack(self.test_data) == b'\\x85\\xa3bin\\xc4\\np\\x81zDhL\\xf0O\\xd0\\xaf\\xa3cmd\\xa7fileGet\\xa4list\\x92\\xc4\\np\\x81zDhL\\xf0O\\xd0\\xaf\\xc4\\np\\x81zDhL\\xf0O\\xd0\\xaf\\xa6params\\x81\\xa4site\\xa51Site\\xa4utf8\\xad\\xc3\\xa1rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91'\n        assert Msgpack.pack(self.test_data, use_bin_type=False) == b'\\x85\\xa3bin\\xaap\\x81zDhL\\xf0O\\xd0\\xaf\\xa3cmd\\xa7fileGet\\xa4list\\x92\\xaap\\x81zDhL\\xf0O\\xd0\\xaf\\xaap\\x81zDhL\\xf0O\\xd0\\xaf\\xa6params\\x81\\xa4site\\xa51Site\\xa4utf8\\xad\\xc3\\xa1rv\\xc3\\xadzt\\xc5\\xb1r\\xc5\\x91'\n\n    def testUnpackinkg(self):\n        assert Msgpack.unpack(Msgpack.pack(self.test_data)) == self.test_data\n\n    @pytest.mark.parametrize(\"unpacker_class\", [msgpack.Unpacker, msgpack.fallback.Unpacker])\n    def testUnpacker(self, unpacker_class):\n        unpacker = unpacker_class(raw=False)\n\n        data = msgpack.packb(self.test_data, use_bin_type=True)\n        data += msgpack.packb(self.test_data, use_bin_type=True)\n\n        messages = []\n        for char in data:\n            unpacker.feed(bytes([char]))\n            for message in unpacker:\n                messages.append(message)\n\n        assert len(messages) == 2\n        assert messages[0] == self.test_data\n        assert messages[0] == messages[1]\n\n    def testStreaming(self):\n        bin_data = os.urandom(20)\n        f = Msgpack.FilePart(\"%s/users.json\" % config.data_dir, \"rb\")\n        f.read_bytes = 30\n\n        data = {\"cmd\": \"response\", \"body\": f, \"bin\": bin_data}\n\n        out_buff = io.BytesIO()\n        Msgpack.stream(data, out_buff.write)\n        out_buff.seek(0)\n\n        data_packb = {\n            \"cmd\": \"response\",\n            \"body\": open(\"%s/users.json\" % config.data_dir, \"rb\").read(30),\n            \"bin\": bin_data\n        }\n\n        out_buff.seek(0)\n        data_unpacked = Msgpack.unpack(out_buff.read())\n        assert data_unpacked == data_packb\n        assert data_unpacked[\"cmd\"] == \"response\"\n        assert type(data_unpacked[\"body\"]) == bytes\n\n    def testBackwardCompatibility(self):\n        packed = {}\n        packed[\"py3\"] = Msgpack.pack(self.test_data, use_bin_type=False)\n        packed[\"py3_bin\"] = Msgpack.pack(self.test_data, use_bin_type=True)\n        for key, val in packed.items():\n            unpacked = Msgpack.unpack(val)\n            type(unpacked[\"utf8\"]) == str\n            type(unpacked[\"bin\"]) == bytes\n\n        # Packed with use_bin_type=False (pre-ZeroNet 0.7.0)\n        unpacked = Msgpack.unpack(packed[\"py3\"], decode=True)\n        type(unpacked[\"utf8\"]) == str\n        type(unpacked[\"bin\"]) == bytes\n        assert len(unpacked[\"utf8\"]) == 9\n        assert len(unpacked[\"bin\"]) == 10\n        with pytest.raises(UnicodeDecodeError) as err:  # Try to decode binary as utf-8\n            unpacked = Msgpack.unpack(packed[\"py3\"], decode=False)\n\n        # Packed with use_bin_type=True\n        unpacked = Msgpack.unpack(packed[\"py3_bin\"], decode=False)\n        type(unpacked[\"utf8\"]) == str\n        type(unpacked[\"bin\"]) == bytes\n        assert len(unpacked[\"utf8\"]) == 9\n        assert len(unpacked[\"bin\"]) == 10\n\n"
  },
  {
    "path": "src/Test/TestNoparallel.py",
    "content": "import time\n\nimport gevent\nimport pytest\n\nimport util\nfrom util import ThreadPool\n\n\n@pytest.fixture(params=['gevent.spawn', 'thread_pool.spawn'])\ndef queue_spawn(request):\n    thread_pool = ThreadPool.ThreadPool(10)\n    if request.param == \"gevent.spawn\":\n        return gevent.spawn\n    else:\n        return thread_pool.spawn\n\n\nclass ExampleClass(object):\n    def __init__(self):\n        self.counted = 0\n\n    @util.Noparallel()\n    def countBlocking(self, num=5):\n        for i in range(1, num + 1):\n            time.sleep(0.1)\n            self.counted += 1\n        return \"counted:%s\" % i\n\n    @util.Noparallel(queue=True, ignore_class=True)\n    def countQueue(self, num=5):\n        for i in range(1, num + 1):\n            time.sleep(0.1)\n            self.counted += 1\n        return \"counted:%s\" % i\n\n    @util.Noparallel(blocking=False)\n    def countNoblocking(self, num=5):\n        for i in range(1, num + 1):\n            time.sleep(0.01)\n            self.counted += 1\n        return \"counted:%s\" % i\n\n\nclass TestNoparallel:\n    def testBlocking(self, queue_spawn):\n        obj1 = ExampleClass()\n        obj2 = ExampleClass()\n\n        # Dont allow to call again until its running and wait until its running\n        threads = [\n            queue_spawn(obj1.countBlocking),\n            queue_spawn(obj1.countBlocking),\n            queue_spawn(obj1.countBlocking),\n            queue_spawn(obj2.countBlocking)\n        ]\n        assert obj2.countBlocking() == \"counted:5\"  # The call is ignored as obj2.countBlocking already counting, but block until its finishes\n        gevent.joinall(threads)\n        assert [thread.value for thread in threads] == [\"counted:5\", \"counted:5\", \"counted:5\", \"counted:5\"]\n        obj2.countBlocking()  # Allow to call again as obj2.countBlocking finished\n\n        assert obj1.counted == 5\n        assert obj2.counted == 10\n\n    def testNoblocking(self):\n        obj1 = ExampleClass()\n\n        thread1 = obj1.countNoblocking()\n        thread2 = obj1.countNoblocking()  # Ignored\n\n        assert obj1.counted == 0\n        time.sleep(0.1)\n        assert thread1.value == \"counted:5\"\n        assert thread2.value == \"counted:5\"\n        assert obj1.counted == 5\n\n        obj1.countNoblocking().join()  # Allow again and wait until finishes\n        assert obj1.counted == 10\n\n    def testQueue(self, queue_spawn):\n        obj1 = ExampleClass()\n\n        queue_spawn(obj1.countQueue, num=1)\n        queue_spawn(obj1.countQueue, num=1)\n        queue_spawn(obj1.countQueue, num=1)\n\n        time.sleep(0.3)\n        assert obj1.counted == 2  # No multi-queue supported\n\n        obj2 = ExampleClass()\n        queue_spawn(obj2.countQueue, num=10)\n        queue_spawn(obj2.countQueue, num=10)\n\n        time.sleep(1.5)  # Call 1 finished, call 2 still working\n        assert 10 < obj2.counted < 20\n\n        queue_spawn(obj2.countQueue, num=10)\n        time.sleep(2.0)\n\n        assert obj2.counted == 30\n\n    def testQueueOverload(self):\n        obj1 = ExampleClass()\n\n        threads = []\n        for i in range(1000):\n            thread = gevent.spawn(obj1.countQueue, num=5)\n            threads.append(thread)\n\n        gevent.joinall(threads)\n        assert obj1.counted == 5 * 2  # Only called twice (no multi-queue allowed)\n\n    def testIgnoreClass(self, queue_spawn):\n        obj1 = ExampleClass()\n        obj2 = ExampleClass()\n\n        threads = [\n            queue_spawn(obj1.countQueue),\n            queue_spawn(obj1.countQueue),\n            queue_spawn(obj1.countQueue),\n            queue_spawn(obj2.countQueue),\n            queue_spawn(obj2.countQueue)\n        ]\n        s = time.time()\n        time.sleep(0.001)\n        gevent.joinall(threads)\n\n        # Queue limited to 2 calls (every call takes counts to 5 and takes 0.05 sec)\n        assert obj1.counted + obj2.counted == 10\n\n        taken = time.time() - s\n        assert 1.2 > taken >= 1.0  # 2 * 0.5s count = ~1s\n\n    def testException(self, queue_spawn):\n        class MyException(Exception):\n            pass\n\n        @util.Noparallel()\n        def raiseException():\n            raise MyException(\"Test error!\")\n\n        with pytest.raises(MyException) as err:\n            raiseException()\n        assert str(err.value) == \"Test error!\"\n\n        with pytest.raises(MyException) as err:\n            queue_spawn(raiseException).get()\n        assert str(err.value) == \"Test error!\"\n\n    def testMultithreadMix(self, queue_spawn):\n        obj1 = ExampleClass()\n        with ThreadPool.ThreadPool(10) as thread_pool:\n            s = time.time()\n            t1 = queue_spawn(obj1.countBlocking, 5)\n            time.sleep(0.01)\n            t2 = thread_pool.spawn(obj1.countBlocking, 5)\n            time.sleep(0.01)\n            t3 = thread_pool.spawn(obj1.countBlocking, 5)\n            time.sleep(0.3)\n            t4 = gevent.spawn(obj1.countBlocking, 5)\n            threads = [t1, t2, t3, t4]\n            for thread in threads:\n                assert thread.get() == \"counted:5\"\n\n            time_taken = time.time() - s\n            assert obj1.counted == 5\n            assert 0.5 < time_taken < 0.7\n"
  },
  {
    "path": "src/Test/TestPeer.py",
    "content": "import time\nimport io\n\nimport pytest\n\nfrom File import FileServer\nfrom File import FileRequest\nfrom Crypt import CryptHash\nfrom . import Spy\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestPeer:\n    def testPing(self, file_server, site, site_temp):\n        file_server.sites[site.address] = site\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n        connection = client.getConnection(file_server.ip, 1544)\n\n        # Add file_server as peer to client\n        peer_file_server = site_temp.addPeer(file_server.ip, 1544)\n\n        assert peer_file_server.ping() is not None\n\n        assert peer_file_server in site_temp.peers.values()\n        peer_file_server.remove()\n        assert peer_file_server not in site_temp.peers.values()\n\n        connection.close()\n        client.stop()\n\n    def testDownloadFile(self, file_server, site, site_temp):\n        file_server.sites[site.address] = site\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n        connection = client.getConnection(file_server.ip, 1544)\n\n        # Add file_server as peer to client\n        peer_file_server = site_temp.addPeer(file_server.ip, 1544)\n\n        # Testing streamFile\n        buff = peer_file_server.getFile(site_temp.address, \"content.json\", streaming=True)\n        assert b\"sign\" in buff.getvalue()\n\n        # Testing getFile\n        buff = peer_file_server.getFile(site_temp.address, \"content.json\")\n        assert b\"sign\" in buff.getvalue()\n\n        connection.close()\n        client.stop()\n\n    def testHashfield(self, site):\n        sample_hash = list(site.content_manager.contents[\"content.json\"][\"files_optional\"].values())[0][\"sha512\"]\n\n        site.storage.verifyFiles(quick_check=True)  # Find what optional files we have\n\n        # Check if hashfield has any files\n        assert site.content_manager.hashfield\n        assert len(site.content_manager.hashfield) > 0\n\n        # Check exsist hash\n        assert site.content_manager.hashfield.getHashId(sample_hash) in site.content_manager.hashfield\n\n        # Add new hash\n        new_hash = CryptHash.sha512sum(io.BytesIO(b\"hello\"))\n        assert site.content_manager.hashfield.getHashId(new_hash) not in site.content_manager.hashfield\n        assert site.content_manager.hashfield.appendHash(new_hash)\n        assert not site.content_manager.hashfield.appendHash(new_hash)  # Don't add second time\n        assert site.content_manager.hashfield.getHashId(new_hash) in site.content_manager.hashfield\n\n        # Remove new hash\n        assert site.content_manager.hashfield.removeHash(new_hash)\n        assert site.content_manager.hashfield.getHashId(new_hash) not in site.content_manager.hashfield\n\n    def testHashfieldExchange(self, file_server, site, site_temp):\n        server1 = file_server\n        server1.sites[site.address] = site\n        site.connection_server = server1\n\n        server2 = FileServer(file_server.ip, 1545)\n        server2.sites[site_temp.address] = site_temp\n        site_temp.connection_server = server2\n        site.storage.verifyFiles(quick_check=True)  # Find what optional files we have\n\n        # Add file_server as peer to client\n        server2_peer1 = site_temp.addPeer(file_server.ip, 1544)\n\n        # Check if hashfield has any files\n        assert len(site.content_manager.hashfield) > 0\n\n        # Testing hashfield sync\n        assert len(server2_peer1.hashfield) == 0\n        assert server2_peer1.updateHashfield()  # Query hashfield from peer\n        assert len(server2_peer1.hashfield) > 0\n\n        # Test force push new hashfield\n        site_temp.content_manager.hashfield.appendHash(\"AABB\")\n        server1_peer2 = site.addPeer(file_server.ip, 1545, return_peer=True)\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            assert len(server1_peer2.hashfield) == 0\n            server2_peer1.sendMyHashfield()\n            assert len(server1_peer2.hashfield) == 1\n            server2_peer1.sendMyHashfield()  # Hashfield not changed, should be ignored\n\n            assert len(requests) == 1\n\n            time.sleep(0.01)  # To make hashfield change date different\n\n            site_temp.content_manager.hashfield.appendHash(\"AACC\")\n            server2_peer1.sendMyHashfield()  # Push hashfield\n\n            assert len(server1_peer2.hashfield) == 2\n            assert len(requests) == 2\n\n            site_temp.content_manager.hashfield.appendHash(\"AADD\")\n\n            assert server1_peer2.updateHashfield(force=True)  # Request hashfield\n            assert len(server1_peer2.hashfield) == 3\n            assert len(requests) == 3\n\n            assert not server2_peer1.sendMyHashfield()  # Not changed, should be ignored\n            assert len(requests) == 3\n\n        server2.stop()\n\n    def testFindHash(self, file_server, site, site_temp):\n        file_server.sites[site.address] = site\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Add file_server as peer to client\n        peer_file_server = site_temp.addPeer(file_server.ip, 1544)\n\n        assert peer_file_server.findHashIds([1234]) == {}\n\n        # Add fake peer with requred hash\n        fake_peer_1 = site.addPeer(file_server.ip_external, 1544)\n        fake_peer_1.hashfield.append(1234)\n        fake_peer_2 = site.addPeer(\"1.2.3.5\", 1545)\n        fake_peer_2.hashfield.append(1234)\n        fake_peer_2.hashfield.append(1235)\n        fake_peer_3 = site.addPeer(\"1.2.3.6\", 1546)\n        fake_peer_3.hashfield.append(1235)\n        fake_peer_3.hashfield.append(1236)\n\n        res = peer_file_server.findHashIds([1234, 1235])\n        assert sorted(res[1234]) == sorted([(file_server.ip_external, 1544), (\"1.2.3.5\", 1545)])\n        assert sorted(res[1235]) == sorted([(\"1.2.3.5\", 1545), (\"1.2.3.6\", 1546)])\n\n        # Test my address adding\n        site.content_manager.hashfield.append(1234)\n\n        res = peer_file_server.findHashIds([1234, 1235])\n        assert sorted(res[1234]) == sorted([(file_server.ip_external, 1544), (\"1.2.3.5\", 1545), (file_server.ip, 1544)])\n        assert sorted(res[1235]) == sorted([(\"1.2.3.5\", 1545), (\"1.2.3.6\", 1546)])\n"
  },
  {
    "path": "src/Test/TestRateLimit.py",
    "content": "import time\n\nimport gevent\n\nfrom util import RateLimit\n\n\n# Time is around limit +/- 0.05 sec\ndef around(t, limit):\n    return t >= limit - 0.05 and t <= limit + 0.05\n\n\nclass ExampleClass(object):\n    def __init__(self):\n        self.counted = 0\n        self.last_called = None\n\n    def count(self, back=\"counted\"):\n        self.counted += 1\n        self.last_called = back\n        return back\n\n\nclass TestRateLimit:\n    def testCall(self):\n        obj1 = ExampleClass()\n        obj2 = ExampleClass()\n\n        s = time.time()\n        assert RateLimit.call(\"counting\", allowed_again=0.1, func=obj1.count) == \"counted\"\n        assert around(time.time() - s, 0.0)  # First allow to call instantly\n        assert obj1.counted == 1\n\n        # Call again\n        assert not RateLimit.isAllowed(\"counting\", 0.1)\n        assert RateLimit.isAllowed(\"something else\", 0.1)\n        assert RateLimit.call(\"counting\", allowed_again=0.1, func=obj1.count) == \"counted\"\n        assert around(time.time() - s, 0.1)  # Delays second call within interval\n        assert obj1.counted == 2\n        time.sleep(0.1)  # Wait the cooldown time\n\n        # Call 3 times async\n        s = time.time()\n        assert obj2.counted == 0\n        threads = [\n            gevent.spawn(lambda: RateLimit.call(\"counting\", allowed_again=0.1, func=obj2.count)),  # Instant\n            gevent.spawn(lambda: RateLimit.call(\"counting\", allowed_again=0.1, func=obj2.count)),  # 0.1s delay\n            gevent.spawn(lambda: RateLimit.call(\"counting\", allowed_again=0.1, func=obj2.count))   # 0.2s delay\n        ]\n        gevent.joinall(threads)\n        assert [thread.value for thread in threads] == [\"counted\", \"counted\", \"counted\"]\n        assert around(time.time() - s, 0.2)\n\n        # Wait 0.1s cooldown\n        assert not RateLimit.isAllowed(\"counting\", 0.1)\n        time.sleep(0.11)\n        assert RateLimit.isAllowed(\"counting\", 0.1)\n\n        # No queue = instant again\n        s = time.time()\n        assert RateLimit.isAllowed(\"counting\", 0.1)\n        assert RateLimit.call(\"counting\", allowed_again=0.1, func=obj2.count) == \"counted\"\n        assert around(time.time() - s, 0.0)\n\n        assert obj2.counted == 4\n\n    def testCallAsync(self):\n        obj1 = ExampleClass()\n        obj2 = ExampleClass()\n\n        s = time.time()\n        RateLimit.callAsync(\"counting async\", allowed_again=0.1, func=obj1.count, back=\"call #1\").join()\n        assert obj1.counted == 1  # First instant\n        assert around(time.time() - s, 0.0)\n\n        # After that the calls delayed\n        s = time.time()\n        t1 = RateLimit.callAsync(\"counting async\", allowed_again=0.1, func=obj1.count, back=\"call #2\")  # Dumped by the next call\n        time.sleep(0.03)\n        t2 = RateLimit.callAsync(\"counting async\", allowed_again=0.1, func=obj1.count, back=\"call #3\")  # Dumped by the next call\n        time.sleep(0.03)\n        t3 = RateLimit.callAsync(\"counting async\", allowed_again=0.1, func=obj1.count, back=\"call #4\")  # Will be called\n        assert obj1.counted == 1  # Delay still in progress: Not called yet\n        t3.join()\n        assert t3.value == \"call #4\"\n        assert around(time.time() - s, 0.1)\n\n        # Only the last one called\n        assert obj1.counted == 2\n        assert obj1.last_called == \"call #4\"\n\n        # Just called, not allowed again\n        assert not RateLimit.isAllowed(\"counting async\", 0.1)\n        s = time.time()\n        t4 = RateLimit.callAsync(\"counting async\", allowed_again=0.1, func=obj1.count, back=\"call #5\").join()\n        assert obj1.counted == 3\n        assert around(time.time() - s, 0.1)\n        assert not RateLimit.isAllowed(\"counting async\", 0.1)\n        time.sleep(0.11)\n        assert RateLimit.isAllowed(\"counting async\", 0.1)\n"
  },
  {
    "path": "src/Test/TestSafeRe.py",
    "content": "from util import SafeRe\n\nimport pytest\n\n\nclass TestSafeRe:\n    def testSafeMatch(self):\n        assert SafeRe.match(\n            \"((js|css)/(?!all.(js|css))|data/users/.*db|data/users/.*/.*|data/archived|.*.py)\",\n            \"js/ZeroTalk.coffee\"\n        )\n        assert SafeRe.match(\".+/data.json\", \"data/users/1J3rJ8ecnwH2EPYa6MrgZttBNc61ACFiCj/data.json\")\n\n    @pytest.mark.parametrize(\"pattern\", [\"([a-zA-Z]+)*\", \"(a|aa)+*\", \"(a|a?)+\", \"(.*a){10}\", \"((?!json).)*$\", r\"(\\w+\\d+)+C\"])\n    def testUnsafeMatch(self, pattern):\n        with pytest.raises(SafeRe.UnsafePatternError) as err:\n            SafeRe.match(pattern, \"aaaaaaaaaaaaaaaaaaaaaaaa!\")\n        assert \"Potentially unsafe\" in str(err.value)\n\n    @pytest.mark.parametrize(\"pattern\", [\"^(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)(.*a)$\"])\n    def testUnsafeRepetition(self, pattern):\n        with pytest.raises(SafeRe.UnsafePatternError) as err:\n            SafeRe.match(pattern, \"aaaaaaaaaaaaaaaaaaaaaaaa!\")\n        assert \"More than\" in str(err.value)\n"
  },
  {
    "path": "src/Test/TestSite.py",
    "content": "import shutil\nimport os\n\nimport pytest\nfrom Site import SiteManager\n\nTEST_DATA_PATH = \"src/Test/testdata\"\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestSite:\n    def testClone(self, site):\n        assert site.storage.directory == TEST_DATA_PATH + \"/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\"\n\n        # Remove old files\n        if os.path.isdir(TEST_DATA_PATH + \"/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL\"):\n            shutil.rmtree(TEST_DATA_PATH + \"/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL\")\n        assert not os.path.isfile(TEST_DATA_PATH + \"/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL/content.json\")\n\n        # Clone 1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT to 15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc\n        new_site = site.clone(\n            \"159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL\", \"5JU2p5h3R7B1WrbaEdEDNZR7YHqRLGcjNcqwqVQzX2H4SuNe2ee\", address_index=1\n        )\n\n        # Check if clone was successful\n        assert new_site.address == \"159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL\"\n        assert new_site.storage.isFile(\"content.json\")\n        assert new_site.storage.isFile(\"index.html\")\n        assert new_site.storage.isFile(\"data/users/content.json\")\n        assert new_site.storage.isFile(\"data/zeroblog.db\")\n        assert new_site.storage.verifyFiles()[\"bad_files\"] == []  # No bad files allowed\n        assert new_site.storage.query(\"SELECT * FROM keyvalue WHERE key = 'title'\").fetchone()[\"value\"] == \"MyZeroBlog\"\n\n        # Optional files should be removed\n\n        assert len(new_site.storage.loadJson(\"content.json\").get(\"files_optional\", {})) == 0\n\n        # Test re-cloning (updating)\n\n        # Changes in non-data files should be overwritten\n        new_site.storage.write(\"index.html\", b\"this will be overwritten\")\n        assert new_site.storage.read(\"index.html\") == b\"this will be overwritten\"\n\n        # Changes in data file should be kept after re-cloning\n        changed_contentjson = new_site.storage.loadJson(\"content.json\")\n        changed_contentjson[\"description\"] = \"Update Description Test\"\n        new_site.storage.writeJson(\"content.json\", changed_contentjson)\n\n        changed_data = new_site.storage.loadJson(\"data/data.json\")\n        changed_data[\"title\"] = \"UpdateTest\"\n        new_site.storage.writeJson(\"data/data.json\", changed_data)\n\n        # The update should be reflected to database\n        assert new_site.storage.query(\"SELECT * FROM keyvalue WHERE key = 'title'\").fetchone()[\"value\"] == \"UpdateTest\"\n\n        # Re-clone the site\n        site.log.debug(\"Re-cloning\")\n        site.clone(\"159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL\")\n\n        assert new_site.storage.loadJson(\"data/data.json\")[\"title\"] == \"UpdateTest\"\n        assert new_site.storage.loadJson(\"content.json\")[\"description\"] == \"Update Description Test\"\n        assert new_site.storage.read(\"index.html\") != \"this will be overwritten\"\n\n        # Delete created files\n        new_site.storage.deleteFiles()\n        assert not os.path.isdir(TEST_DATA_PATH + \"/159EGD5srUsMP97UpcLy8AtKQbQLK2AbbL\")\n\n        # Delete from site registry\n        assert new_site.address in SiteManager.site_manager.sites\n        SiteManager.site_manager.delete(new_site.address)\n        assert new_site.address not in SiteManager.site_manager.sites\n"
  },
  {
    "path": "src/Test/TestSiteDownload.py",
    "content": "import time\n\nimport pytest\nimport mock\nimport gevent\nimport gevent.event\nimport os\n\nfrom Connection import ConnectionServer\nfrom Config import config\nfrom File import FileRequest\nfrom File import FileServer\nfrom Site.Site import Site\nfrom . import Spy\n\n\n@pytest.mark.usefixtures(\"resetTempSettings\")\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestSiteDownload:\n    def testRename(self, file_server, site, site_temp):\n        assert site.storage.directory == config.data_dir + \"/\" + site.address\n        assert site_temp.storage.directory == config.data_dir + \"-temp/\" + site.address\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n        site_temp.announce = mock.MagicMock(return_value=True)  # Don't try to find peers from the net\n\n\n        site_temp.addPeer(file_server.ip, 1544)\n\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n\n        assert site_temp.storage.isFile(\"content.json\")\n\n        # Rename non-optional file\n        os.rename(site.storage.getPath(\"data/img/domain.png\"), site.storage.getPath(\"data/img/domain-new.png\"))\n\n        site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n\n        content = site.storage.loadJson(\"content.json\")\n        assert \"data/img/domain-new.png\" in content[\"files\"]\n        assert \"data/img/domain.png\" not in content[\"files\"]\n        assert not site_temp.storage.isFile(\"data/img/domain-new.png\")\n        assert site_temp.storage.isFile(\"data/img/domain.png\")\n        settings_before = site_temp.settings\n\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.publish()\n            time.sleep(0.1)\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)  # Wait for download\n            assert \"streamFile\" not in [req[1] for req in requests]\n\n        content = site_temp.storage.loadJson(\"content.json\")\n        assert \"data/img/domain-new.png\" in content[\"files\"]\n        assert \"data/img/domain.png\" not in content[\"files\"]\n        assert site_temp.storage.isFile(\"data/img/domain-new.png\")\n        assert not site_temp.storage.isFile(\"data/img/domain.png\")\n\n        assert site_temp.settings[\"size\"] == settings_before[\"size\"]\n        assert site_temp.settings[\"size_optional\"] == settings_before[\"size_optional\"]\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n\n    def testRenameOptional(self, file_server, site, site_temp):\n        assert site.storage.directory == config.data_dir + \"/\" + site.address\n        assert site_temp.storage.directory == config.data_dir + \"-temp/\" + site.address\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n        site_temp.announce = mock.MagicMock(return_value=True)  # Don't try to find peers from the net\n\n\n        site_temp.addPeer(file_server.ip, 1544)\n\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n\n        assert site_temp.settings[\"optional_downloaded\"] == 0\n\n        site_temp.needFile(\"data/optional.txt\")\n\n        assert site_temp.settings[\"optional_downloaded\"] > 0\n        settings_before = site_temp.settings\n        hashfield_before = site_temp.content_manager.hashfield.tobytes()\n\n        # Rename optional file\n        os.rename(site.storage.getPath(\"data/optional.txt\"), site.storage.getPath(\"data/optional-new.txt\"))\n\n        site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\", remove_missing_optional=True)\n\n        content = site.storage.loadJson(\"content.json\")\n        assert \"data/optional-new.txt\" in content[\"files_optional\"]\n        assert \"data/optional.txt\" not in content[\"files_optional\"]\n        assert not site_temp.storage.isFile(\"data/optional-new.txt\")\n        assert site_temp.storage.isFile(\"data/optional.txt\")\n\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.publish()\n            time.sleep(0.1)\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)  # Wait for download\n            assert \"streamFile\" not in [req[1] for req in requests]\n\n        content = site_temp.storage.loadJson(\"content.json\")\n        assert \"data/optional-new.txt\" in content[\"files_optional\"]\n        assert \"data/optional.txt\" not in content[\"files_optional\"]\n        assert site_temp.storage.isFile(\"data/optional-new.txt\")\n        assert not site_temp.storage.isFile(\"data/optional.txt\")\n\n        assert site_temp.settings[\"size\"] == settings_before[\"size\"]\n        assert site_temp.settings[\"size_optional\"] == settings_before[\"size_optional\"]\n        assert site_temp.settings[\"optional_downloaded\"] == settings_before[\"optional_downloaded\"]\n        assert site_temp.content_manager.hashfield.tobytes() == hashfield_before\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n\n\n    def testArchivedDownload(self, file_server, site, site_temp):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Download normally\n        site_temp.addPeer(file_server.ip, 1544)\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n        bad_files = site_temp.storage.verifyFiles(quick_check=True)[\"bad_files\"]\n\n        assert not bad_files\n        assert \"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\" in site_temp.content_manager.contents\n        assert site_temp.storage.isFile(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\")\n        assert len(list(site_temp.storage.query(\"SELECT * FROM comment\"))) == 2\n\n        # Add archived data\n        assert \"archived\" not in site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"]\n        assert not site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", time.time()-1)\n\n        site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"archived\"] = {\"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\": time.time()}\n        site.content_manager.sign(\"data/users/content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n\n        date_archived = site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"archived\"][\"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\"]\n        assert site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", date_archived-1)\n        assert site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", date_archived)\n        assert not site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", date_archived+1)  # Allow user to update archived data later\n\n        # Push archived update\n        assert not \"archived\" in site_temp.content_manager.contents[\"data/users/content.json\"][\"user_contents\"]\n        site.publish()\n        time.sleep(0.1)\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)  # Wait for download\n\n        # The archived content should disappear from remote client\n        assert \"archived\" in site_temp.content_manager.contents[\"data/users/content.json\"][\"user_contents\"]\n        assert \"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\" not in site_temp.content_manager.contents\n        assert not site_temp.storage.isDir(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\")\n        assert len(list(site_temp.storage.query(\"SELECT * FROM comment\"))) == 1\n        assert len(list(site_temp.storage.query(\"SELECT * FROM json WHERE directory LIKE '%1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q%'\"))) == 0\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n\n    def testArchivedBeforeDownload(self, file_server, site, site_temp):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Download normally\n        site_temp.addPeer(file_server.ip, 1544)\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n        bad_files = site_temp.storage.verifyFiles(quick_check=True)[\"bad_files\"]\n\n        assert not bad_files\n        assert \"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\" in site_temp.content_manager.contents\n        assert site_temp.storage.isFile(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\")\n        assert len(list(site_temp.storage.query(\"SELECT * FROM comment\"))) == 2\n\n        # Add archived data\n        assert not \"archived_before\" in site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"]\n        assert not site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", time.time()-1)\n\n        content_modification_time = site.content_manager.contents[\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\"][\"modified\"]\n        site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"archived_before\"] = content_modification_time\n        site.content_manager.sign(\"data/users/content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n\n        date_archived = site.content_manager.contents[\"data/users/content.json\"][\"user_contents\"][\"archived_before\"]\n        assert site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", date_archived-1)\n        assert site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", date_archived)\n        assert not site.content_manager.isArchived(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\", date_archived+1)  # Allow user to update archived data later\n\n        # Push archived update\n        assert not \"archived_before\" in site_temp.content_manager.contents[\"data/users/content.json\"][\"user_contents\"]\n        site.publish()\n        time.sleep(0.1)\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)  # Wait for download\n\n        # The archived content should disappear from remote client\n        assert \"archived_before\" in site_temp.content_manager.contents[\"data/users/content.json\"][\"user_contents\"]\n        assert \"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json\" not in site_temp.content_manager.contents\n        assert not site_temp.storage.isDir(\"data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\")\n        assert len(list(site_temp.storage.query(\"SELECT * FROM comment\"))) == 1\n        assert len(list(site_temp.storage.query(\"SELECT * FROM json WHERE directory LIKE '%1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q%'\"))) == 0\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n\n\n    # Test when connected peer has the optional file\n    def testOptionalDownload(self, file_server, site, site_temp):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = ConnectionServer(file_server.ip, 1545)\n        site_temp.connection_server = client\n        site_temp.announce = mock.MagicMock(return_value=True)  # Don't try to find peers from the net\n\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n\n        # Download optional data/optional.txt\n        site.storage.verifyFiles(quick_check=True)  # Find what optional files we have\n        optional_file_info = site_temp.content_manager.getFileInfo(\"data/optional.txt\")\n        assert site.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])\n        assert not site_temp.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])\n\n        assert not site_temp.storage.isFile(\"data/optional.txt\")\n        assert site.storage.isFile(\"data/optional.txt\")\n        site_temp.needFile(\"data/optional.txt\")\n        assert site_temp.storage.isFile(\"data/optional.txt\")\n\n        # Optional user file\n        assert not site_temp.storage.isFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        optional_file_info = site_temp.content_manager.getFileInfo(\n            \"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\"\n        )\n        assert site.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])\n        assert not site_temp.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])\n\n        site_temp.needFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        assert site_temp.storage.isFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        assert site_temp.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n\n    # Test when connected peer does not has the file, so ask him if he know someone who has it\n    def testFindOptional(self, file_server, site, site_temp):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init full source server (has optional files)\n        site_full = Site(\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\")\n        file_server_full = FileServer(file_server.ip, 1546)\n        site_full.connection_server = file_server_full\n\n        def listen():\n            ConnectionServer.start(file_server_full)\n            ConnectionServer.listen(file_server_full)\n\n        gevent.spawn(listen)\n        time.sleep(0.001)  # Port opening\n        file_server_full.sites[site_full.address] = site_full  # Add site\n        site_full.storage.verifyFiles(quick_check=True)  # Check optional files\n        site_full_peer = site.addPeer(file_server.ip, 1546)  # Add it to source server\n        hashfield = site_full_peer.updateHashfield()  # Update hashfield\n        assert len(site_full.content_manager.hashfield) == 8\n        assert hashfield\n        assert site_full.storage.isFile(\"data/optional.txt\")\n        assert site_full.storage.isFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        assert len(site_full_peer.hashfield) == 8\n\n        # Remove hashes from source server\n        for hash in list(site.content_manager.hashfield):\n            site.content_manager.hashfield.remove(hash)\n\n        # Init client server\n        site_temp.connection_server = ConnectionServer(file_server.ip, 1545)\n        site_temp.addPeer(file_server.ip, 1544)  # Add source server\n\n        # Download normal files\n        site_temp.log.info(\"Start Downloading site\")\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n\n        # Download optional data/optional.txt\n        optional_file_info = site_temp.content_manager.getFileInfo(\"data/optional.txt\")\n        optional_file_info2 = site_temp.content_manager.getFileInfo(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        assert not site_temp.storage.isFile(\"data/optional.txt\")\n        assert not site_temp.storage.isFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n        assert not site.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])  # Source server don't know he has the file\n        assert not site.content_manager.hashfield.hasHash(optional_file_info2[\"sha512\"])  # Source server don't know he has the file\n        assert site_full_peer.hashfield.hasHash(optional_file_info[\"sha512\"])  # Source full peer on source server has the file\n        assert site_full_peer.hashfield.hasHash(optional_file_info2[\"sha512\"])  # Source full peer on source server has the file\n        assert site_full.content_manager.hashfield.hasHash(optional_file_info[\"sha512\"])  # Source full server he has the file\n        assert site_full.content_manager.hashfield.hasHash(optional_file_info2[\"sha512\"])  # Source full server he has the file\n\n        site_temp.log.info(\"Request optional files\")\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            # Request 2 file same time\n            threads = []\n            threads.append(site_temp.needFile(\"data/optional.txt\", blocking=False))\n            threads.append(site_temp.needFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\", blocking=False))\n            gevent.joinall(threads)\n\n            assert len([request for request in requests if request[1] == \"findHashIds\"]) == 1  # findHashids should call only once\n\n        assert site_temp.storage.isFile(\"data/optional.txt\")\n        assert site_temp.storage.isFile(\"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/peanut-butter-jelly-time.gif\")\n\n        assert site_temp.storage.deleteFiles()\n        file_server_full.stop()\n        [connection.close() for connection in file_server.connections]\n        site_full.content_manager.contents.db.close(\"FindOptional test end\")\n\n    def testUpdate(self, file_server, site, site_temp):\n        assert site.storage.directory == config.data_dir + \"/\" + site.address\n        assert site_temp.storage.directory == config.data_dir + \"-temp/\" + site.address\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Don't try to find peers from the net\n        site.announce = mock.MagicMock(return_value=True)\n        site_temp.announce = mock.MagicMock(return_value=True)\n\n        # Connect peers\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site from site to site_temp\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n        assert len(site_temp.bad_files) == 1\n\n        # Update file\n        data_original = site.storage.open(\"data/data.json\").read()\n        data_new = data_original.replace(b'\"ZeroBlog\"', b'\"UpdatedZeroBlog\"')\n        assert data_original != data_new\n\n        site.storage.open(\"data/data.json\", \"wb\").write(data_new)\n\n        assert site.storage.open(\"data/data.json\").read() == data_new\n        assert site_temp.storage.open(\"data/data.json\").read() == data_original\n\n        site.log.info(\"Publish new data.json without patch\")\n        # Publish without patch\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n            site.publish()\n            time.sleep(0.1)\n            site.log.info(\"Downloading site\")\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n            assert len([request for request in requests if request[1] in (\"getFile\", \"streamFile\")]) == 1\n\n        assert site_temp.storage.open(\"data/data.json\").read() == data_new\n\n        # Close connection to avoid update spam limit\n        list(site.peers.values())[0].remove()\n        site.addPeer(file_server.ip, 1545)\n        list(site_temp.peers.values())[0].ping()  # Connect back\n        time.sleep(0.1)\n\n        # Update with patch\n        data_new = data_original.replace(b'\"ZeroBlog\"', b'\"PatchedZeroBlog\"')\n        assert data_original != data_new\n\n        site.storage.open(\"data/data.json-new\", \"wb\").write(data_new)\n\n        assert site.storage.open(\"data/data.json-new\").read() == data_new\n        assert site_temp.storage.open(\"data/data.json\").read() != data_new\n\n        # Generate diff\n        diffs = site.content_manager.getDiffs(\"content.json\")\n        assert not site.storage.isFile(\"data/data.json-new\")  # New data file removed\n        assert site.storage.open(\"data/data.json\").read() == data_new  # -new postfix removed\n        assert \"data/data.json\" in diffs\n        assert diffs[\"data/data.json\"] == [('=', 2), ('-', 29), ('+', [b'\\t\"title\": \"PatchedZeroBlog\",\\n']), ('=', 31102)]\n\n        # Publish with patch\n        site.log.info(\"Publish new data.json with patch\")\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n\n            event_done = gevent.event.AsyncResult()\n            site.publish(diffs=diffs)\n            time.sleep(0.1)\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n            assert [request for request in requests if request[1] in (\"getFile\", \"streamFile\")] == []\n\n        assert site_temp.storage.open(\"data/data.json\").read() == data_new\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n\n    def testBigUpdate(self, file_server, site, site_temp):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Connect peers\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site from site to site_temp\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n        assert list(site_temp.bad_files.keys()) == [\"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\"]\n\n        # Update file\n        data_original = site.storage.open(\"data/data.json\").read()\n        data_new = data_original.replace(b'\"ZeroBlog\"', b'\"PatchedZeroBlog\"')\n        assert data_original != data_new\n\n        site.storage.open(\"data/data.json-new\", \"wb\").write(data_new)\n\n        assert site.storage.open(\"data/data.json-new\").read() == data_new\n        assert site_temp.storage.open(\"data/data.json\").read() != data_new\n\n        # Generate diff\n        diffs = site.content_manager.getDiffs(\"content.json\")\n        assert not site.storage.isFile(\"data/data.json-new\")  # New data file removed\n        assert site.storage.open(\"data/data.json\").read() == data_new  # -new postfix removed\n        assert \"data/data.json\" in diffs\n\n        content_json = site.storage.loadJson(\"content.json\")\n        content_json[\"description\"] = \"BigZeroBlog\" * 1024 * 10\n        site.storage.writeJson(\"content.json\", content_json)\n        site.content_manager.loadContent(\"content.json\", force=True)\n\n        # Publish with patch\n        site.log.info(\"Publish new data.json with patch\")\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n            assert site.storage.getSize(\"content.json\") > 10 * 1024  # Make it a big content.json\n            site.publish(diffs=diffs)\n            time.sleep(0.1)\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n            file_requests = [request for request in requests if request[1] in (\"getFile\", \"streamFile\")]\n            assert len(file_requests) == 1\n\n        assert site_temp.storage.open(\"data/data.json\").read() == data_new\n        assert site_temp.storage.open(\"content.json\").read() == site.storage.open(\"content.json\").read()\n\n    # Test what happened if the content.json of the site is bigger than the site limit\n    def testHugeContentSiteUpdate(self, file_server, site, site_temp):\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Connect peers\n        site_temp.addPeer(file_server.ip, 1544)\n\n        # Download site from site to site_temp\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n        site_temp.settings[\"size_limit\"] = int(20 * 1024 *1024)\n        site_temp.saveSettings()\n\n        # Raise limit size to 20MB on site so it can be signed\n        site.settings[\"size_limit\"] = int(20 * 1024 *1024)\n        site.saveSettings()\n\n        content_json = site.storage.loadJson(\"content.json\")\n        content_json[\"description\"] = \"PartirUnJour\" * 1024 * 1024\n        site.storage.writeJson(\"content.json\", content_json)\n        changed, deleted = site.content_manager.loadContent(\"content.json\", force=True)\n\n        # Make sure we have 2 differents content.json\n        assert site_temp.storage.open(\"content.json\").read() != site.storage.open(\"content.json\").read()\n\n        # Generate diff\n        diffs = site.content_manager.getDiffs(\"content.json\")\n\n        # Publish with patch\n        site.log.info(\"Publish new content.json bigger than 10MB\")\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n            assert site.storage.getSize(\"content.json\") > 10 * 1024 * 1024  # verify it over 10MB\n            time.sleep(0.1)\n            site.publish(diffs=diffs)\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n\n        assert site_temp.storage.getSize(\"content.json\") < site_temp.getSizeLimit() * 1024 * 1024\n        assert site_temp.storage.open(\"content.json\").read() == site.storage.open(\"content.json\").read()\n\n    def testUnicodeFilename(self, file_server, site, site_temp):\n        assert site.storage.directory == config.data_dir + \"/\" + site.address\n        assert site_temp.storage.directory == config.data_dir + \"-temp/\" + site.address\n\n        # Init source server\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n\n        # Init client server\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n        site_temp.announce = mock.MagicMock(return_value=True)  # Don't try to find peers from the net\n\n        site_temp.addPeer(file_server.ip, 1544)\n\n        assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)\n\n        site.storage.write(\"data/img/árvíztűrő.png\", b\"test\")\n\n        site.content_manager.sign(\"content.json\", privatekey=\"5KUh3PvNm5HUWoCfSUfcYvfQ2g3PrRNJWr6Q9eqdBGu23mtMntv\")\n\n        content = site.storage.loadJson(\"content.json\")\n        assert \"data/img/árvíztűrő.png\" in content[\"files\"]\n        assert not site_temp.storage.isFile(\"data/img/árvíztűrő.png\")\n        settings_before = site_temp.settings\n\n        with Spy.Spy(FileRequest, \"route\") as requests:\n            site.publish()\n            time.sleep(0.1)\n            assert site_temp.download(blind_includes=True, retry_bad_files=False).get(timeout=10)  # Wait for download\n            assert len([req[1] for req in requests if req[1] == \"streamFile\"]) == 1\n\n        content = site_temp.storage.loadJson(\"content.json\")\n        assert \"data/img/árvíztűrő.png\" in content[\"files\"]\n        assert site_temp.storage.isFile(\"data/img/árvíztűrő.png\")\n\n        assert site_temp.settings[\"size\"] == settings_before[\"size\"]\n        assert site_temp.settings[\"size_optional\"] == settings_before[\"size_optional\"]\n\n        assert site_temp.storage.deleteFiles()\n        [connection.close() for connection in file_server.connections]\n"
  },
  {
    "path": "src/Test/TestSiteStorage.py",
    "content": "import pytest\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestSiteStorage:\n    def testWalk(self, site):\n        # Rootdir\n        walk_root = list(site.storage.walk(\"\"))\n        assert \"content.json\" in walk_root\n        assert \"css/all.css\" in walk_root\n\n        # Subdir\n        assert list(site.storage.walk(\"data-default\")) == [\"data.json\", \"users/content-default.json\"]\n\n    def testList(self, site):\n        # Rootdir\n        list_root = list(site.storage.list(\"\"))\n        assert \"content.json\" in list_root\n        assert \"css/all.css\" not in list_root\n\n        # Subdir\n        assert set(site.storage.list(\"data-default\")) == set([\"data.json\", \"users\"])\n\n    def testDbRebuild(self, site):\n        assert site.storage.rebuildDb()\n"
  },
  {
    "path": "src/Test/TestThreadPool.py",
    "content": "import time\nimport threading\n\nimport gevent\nimport pytest\n\nfrom util import ThreadPool\n\n\nclass TestThreadPool:\n    def testExecutionOrder(self):\n        with ThreadPool.ThreadPool(4) as pool:\n            events = []\n\n            @pool.wrap\n            def blocker():\n                events.append(\"S\")\n                out = 0\n                for i in range(10000000):\n                    if i == 3000000:\n                        events.append(\"M\")\n                    out += 1\n                events.append(\"D\")\n                return out\n\n            threads = []\n            for i in range(3):\n                threads.append(gevent.spawn(blocker))\n            gevent.joinall(threads)\n\n            assert events == [\"S\"] * 3 + [\"M\"] * 3 + [\"D\"] * 3\n\n            res = blocker()\n            assert res == 10000000\n\n    def testLockBlockingSameThread(self):\n        lock = ThreadPool.Lock()\n\n        s = time.time()\n\n        def unlocker():\n            time.sleep(1)\n            lock.release()\n\n        gevent.spawn(unlocker)\n        lock.acquire(True)\n        lock.acquire(True, timeout=2)\n\n        unlock_taken = time.time() - s\n\n        assert 1.0 < unlock_taken < 1.5\n\n    def testLockBlockingDifferentThread(self):\n        lock = ThreadPool.Lock()\n\n        def locker():\n            lock.acquire(True)\n            time.sleep(0.5)\n            lock.release()\n\n        with ThreadPool.ThreadPool(10) as pool:\n            threads = [\n                pool.spawn(locker),\n                pool.spawn(locker),\n                gevent.spawn(locker),\n                pool.spawn(locker)\n            ]\n            time.sleep(0.1)\n\n            s = time.time()\n\n            lock.acquire(True, 5.0)\n\n            unlock_taken = time.time() - s\n\n            assert 1.8 < unlock_taken < 2.2\n\n            gevent.joinall(threads)\n\n    def testMainLoopCallerThreadId(self):\n        main_thread_id = threading.current_thread().ident\n        with ThreadPool.ThreadPool(5) as pool:\n            def getThreadId(*args, **kwargs):\n                return threading.current_thread().ident\n\n            t = pool.spawn(getThreadId)\n            assert t.get() != main_thread_id\n\n            t = pool.spawn(lambda: ThreadPool.main_loop.call(getThreadId))\n            assert t.get() == main_thread_id\n\n    def testMainLoopCallerGeventSpawn(self):\n        main_thread_id = threading.current_thread().ident\n        with ThreadPool.ThreadPool(5) as pool:\n            def waiter():\n                time.sleep(1)\n                return threading.current_thread().ident\n\n            def geventSpawner():\n                event = ThreadPool.main_loop.call(gevent.spawn, waiter)\n\n                with pytest.raises(Exception) as greenlet_err:\n                    event.get()\n                assert str(greenlet_err.value) == \"cannot switch to a different thread\"\n\n                waiter_thread_id = ThreadPool.main_loop.call(event.get)\n                return waiter_thread_id\n\n            s = time.time()\n            waiter_thread_id = pool.apply(geventSpawner)\n            assert main_thread_id == waiter_thread_id\n            time_taken = time.time() - s\n            assert 0.9 < time_taken < 1.2\n\n    def testEvent(self):\n        with ThreadPool.ThreadPool(5) as pool:\n            event = ThreadPool.Event()\n\n            def setter():\n                time.sleep(1)\n                event.set(\"done!\")\n\n            def getter():\n                return event.get()\n\n            pool.spawn(setter)\n            t_gevent = gevent.spawn(getter)\n            t_pool = pool.spawn(getter)\n            s = time.time()\n            assert event.get() == \"done!\"\n            time_taken = time.time() - s\n            gevent.joinall([t_gevent, t_pool])\n\n            assert t_gevent.get() == \"done!\"\n            assert t_pool.get() == \"done!\"\n\n            assert 0.9 < time_taken < 1.2\n\n            with pytest.raises(Exception) as err:\n                event.set(\"another result\")\n\n            assert \"Event already has value\" in str(err.value)\n\n    def testMemoryLeak(self):\n        import gc\n        thread_objs_before = [id(obj) for obj in gc.get_objects() if \"threadpool\" in str(type(obj))]\n\n        def worker():\n            time.sleep(0.1)\n            return \"ok\"\n\n        def poolTest():\n            with ThreadPool.ThreadPool(5) as pool:\n                for i in range(20):\n                    pool.spawn(worker)\n\n        for i in range(5):\n            poolTest()\n            new_thread_objs = [obj for obj in gc.get_objects() if \"threadpool\" in str(type(obj)) and id(obj) not in thread_objs_before]\n            #print(\"New objs:\", new_thread_objs, \"run:\", num_run)\n\n        # Make sure no threadpool object left behind\n        assert not new_thread_objs\n"
  },
  {
    "path": "src/Test/TestTor.py",
    "content": "import time\n\nimport pytest\nimport mock\n\nfrom File import FileServer\nfrom Crypt import CryptRsa\nfrom Config import config\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.usefixtures(\"resetTempSettings\")\nclass TestTor:\n    def testDownload(self, tor_manager):\n        for retry in range(15):\n            time.sleep(1)\n            if tor_manager.enabled and tor_manager.conn:\n                break\n        assert tor_manager.enabled\n\n    def testManagerConnection(self, tor_manager):\n        assert \"250-version\" in tor_manager.request(\"GETINFO version\")\n\n    def testAddOnion(self, tor_manager):\n        # Add\n        address = tor_manager.addOnion()\n        assert address\n        assert address in tor_manager.privatekeys\n\n        # Delete\n        assert tor_manager.delOnion(address)\n        assert address not in tor_manager.privatekeys\n\n    def testSignOnion(self, tor_manager):\n        address = tor_manager.addOnion()\n\n        # Sign\n        sign = CryptRsa.sign(b\"hello\", tor_manager.getPrivatekey(address))\n        assert len(sign) == 128\n\n        # Verify\n        publickey = CryptRsa.privatekeyToPublickey(tor_manager.getPrivatekey(address))\n        assert len(publickey) == 140\n        assert CryptRsa.verify(b\"hello\", publickey, sign)\n        assert not CryptRsa.verify(b\"not hello\", publickey, sign)\n\n        # Pub to address\n        assert CryptRsa.publickeyToOnion(publickey) == address\n\n        # Delete\n        tor_manager.delOnion(address)\n\n    @pytest.mark.slow\n    def testConnection(self, tor_manager, file_server, site, site_temp):\n        file_server.tor_manager.start_onions = True\n        address = file_server.tor_manager.getOnion(site.address)\n        assert address\n        print(\"Connecting to\", address)\n        for retry in range(5):  # Wait for hidden service creation\n            time.sleep(10)\n            try:\n                connection = file_server.getConnection(address + \".onion\", 1544)\n                if connection:\n                    break\n            except Exception as err:\n                continue\n        assert connection.handshake\n        assert not connection.handshake[\"peer_id\"]  # No peer_id for Tor connections\n\n        # Return the same connection without site specified\n        assert file_server.getConnection(address + \".onion\", 1544) == connection\n        # No reuse for different site\n        assert file_server.getConnection(address + \".onion\", 1544, site=site) != connection\n        assert file_server.getConnection(address + \".onion\", 1544, site=site) == file_server.getConnection(address + \".onion\", 1544, site=site)\n        site_temp.address = \"1OTHERSITE\"\n        assert file_server.getConnection(address + \".onion\", 1544, site=site) != file_server.getConnection(address + \".onion\", 1544, site=site_temp)\n\n        # Only allow to query from the locked site\n        file_server.sites[site.address] = site\n        connection_locked = file_server.getConnection(address + \".onion\", 1544, site=site)\n        assert \"body\" in connection_locked.request(\"getFile\", {\"site\": site.address, \"inner_path\": \"content.json\", \"location\": 0})\n        assert connection_locked.request(\"getFile\", {\"site\": \"1OTHERSITE\", \"inner_path\": \"content.json\", \"location\": 0})[\"error\"] == \"Invalid site\"\n\n    def testPex(self, file_server, site, site_temp):\n        # Register site to currently running fileserver\n        site.connection_server = file_server\n        file_server.sites[site.address] = site\n        # Create a new file server to emulate new peer connecting to our peer\n        file_server_temp = FileServer(file_server.ip, 1545)\n        site_temp.connection_server = file_server_temp\n        file_server_temp.sites[site_temp.address] = site_temp\n\n        # We will request peers from this\n        peer_source = site_temp.addPeer(file_server.ip, 1544)\n\n        # Get ip4 peers from source site\n        site.addPeer(\"1.2.3.4\", 1555)  # Add peer to source site\n        assert peer_source.pex(need_num=10) == 1\n        assert len(site_temp.peers) == 2\n        assert \"1.2.3.4:1555\" in site_temp.peers\n\n        # Get onion peers from source site\n        site.addPeer(\"bka4ht2bzxchy44r.onion\", 1555)\n        assert \"bka4ht2bzxchy44r.onion:1555\" not in site_temp.peers\n\n        # Don't add onion peers if not supported\n        assert \"onion\" not in file_server_temp.supported_ip_types\n        assert peer_source.pex(need_num=10) == 0\n\n        file_server_temp.supported_ip_types.append(\"onion\")\n        assert peer_source.pex(need_num=10) == 1\n\n        assert \"bka4ht2bzxchy44r.onion:1555\" in site_temp.peers\n\n    def testFindHash(self, tor_manager, file_server, site, site_temp):\n        file_server.ip_incoming = {}  # Reset flood protection\n        file_server.sites[site.address] = site\n        file_server.tor_manager = tor_manager\n\n        client = FileServer(file_server.ip, 1545)\n        client.sites = {site_temp.address: site_temp}\n        site_temp.connection_server = client\n\n        # Add file_server as peer to client\n        peer_file_server = site_temp.addPeer(file_server.ip, 1544)\n\n        assert peer_file_server.findHashIds([1234]) == {}\n\n        # Add fake peer with requred hash\n        fake_peer_1 = site.addPeer(\"bka4ht2bzxchy44r.onion\", 1544)\n        fake_peer_1.hashfield.append(1234)\n        fake_peer_2 = site.addPeer(\"1.2.3.5\", 1545)\n        fake_peer_2.hashfield.append(1234)\n        fake_peer_2.hashfield.append(1235)\n        fake_peer_3 = site.addPeer(\"1.2.3.6\", 1546)\n        fake_peer_3.hashfield.append(1235)\n        fake_peer_3.hashfield.append(1236)\n\n        res = peer_file_server.findHashIds([1234, 1235])\n\n        assert sorted(res[1234]) == [('1.2.3.5', 1545), (\"bka4ht2bzxchy44r.onion\", 1544)]\n        assert sorted(res[1235]) == [('1.2.3.5', 1545), ('1.2.3.6', 1546)]\n\n        # Test my address adding\n        site.content_manager.hashfield.append(1234)\n\n        res = peer_file_server.findHashIds([1234, 1235])\n        assert sorted(res[1234]) == [('1.2.3.5', 1545), (file_server.ip, 1544), (\"bka4ht2bzxchy44r.onion\", 1544)]\n        assert sorted(res[1235]) == [('1.2.3.5', 1545), ('1.2.3.6', 1546)]\n\n    def testSiteOnion(self, tor_manager):\n        with mock.patch.object(config, \"tor\", \"always\"):\n            assert tor_manager.getOnion(\"address1\") != tor_manager.getOnion(\"address2\")\n            assert tor_manager.getOnion(\"address1\") == tor_manager.getOnion(\"address1\")\n"
  },
  {
    "path": "src/Test/TestTranslate.py",
    "content": "from Translate import Translate\n\nclass TestTranslate:\n    def testTranslateStrict(self):\n        translate = Translate()\n        data = \"\"\"\n            translated = _(\"original\")\n            not_translated = \"original\"\n        \"\"\"\n        data_translated = translate.translateData(data, {\"_(original)\": \"translated\"})\n        assert 'translated = _(\"translated\")' in data_translated\n        assert 'not_translated = \"original\"' in data_translated\n\n    def testTranslateStrictNamed(self):\n        translate = Translate()\n        data = \"\"\"\n            translated = _(\"original\", \"original named\")\n            translated_other = _(\"original\", \"original other named\")\n            not_translated = \"original\"\n        \"\"\"\n        data_translated = translate.translateData(data, {\"_(original, original named)\": \"translated\"})\n        assert 'translated = _(\"translated\")' in data_translated\n        assert 'not_translated = \"original\"' in data_translated\n\n    def testTranslateUtf8(self):\n        translate = Translate()\n        data = \"\"\"\n            greeting = \"Hi again árvztűrőtökörfúrógép!\"\n        \"\"\"\n        data_translated = translate.translateData(data, {\"Hi again árvztűrőtökörfúrógép!\": \"Üdv újra árvztűrőtökörfúrógép!\"})\n        assert data_translated == \"\"\"\n            greeting = \"Üdv újra árvztűrőtökörfúrógép!\"\n        \"\"\"\n\n    def testTranslateEscape(self):\n        _ = Translate()\n        _[\"Hello\"] = \"Szia\"\n\n        # Simple escaping\n        data = \"{_[Hello]} {username}!\"\n        username = \"Hacker<script>alert('boom')</script>\"\n        data_translated = _(data)\n        assert 'Szia' in data_translated\n        assert '<' not in data_translated\n        assert data_translated == \"Szia Hacker&lt;script&gt;alert(&#x27;boom&#x27;)&lt;/script&gt;!\"\n\n        # Escaping dicts\n        user = {\"username\": \"Hacker<script>alert('boom')</script>\"}\n        data = \"{_[Hello]} {user[username]}!\"\n        data_translated = _(data)\n        assert 'Szia' in data_translated\n        assert '<' not in data_translated\n        assert data_translated == \"Szia Hacker&lt;script&gt;alert(&#x27;boom&#x27;)&lt;/script&gt;!\"\n\n        # Escaping lists\n        users = [{\"username\": \"Hacker<script>alert('boom')</script>\"}]\n        data = \"{_[Hello]} {users[0][username]}!\"\n        data_translated = _(data)\n        assert 'Szia' in data_translated\n        assert '<' not in data_translated\n        assert data_translated == \"Szia Hacker&lt;script&gt;alert(&#x27;boom&#x27;)&lt;/script&gt;!\"\n"
  },
  {
    "path": "src/Test/TestUiWebsocket.py",
    "content": "import sys\nimport pytest\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestUiWebsocket:\n    def testPermission(self, ui_websocket):\n        res = ui_websocket.testAction(\"ping\")\n        assert res == \"pong\"\n\n        res = ui_websocket.testAction(\"certList\")\n        assert \"You don't have permission\" in res[\"error\"]\n"
  },
  {
    "path": "src/Test/TestUpnpPunch.py",
    "content": "import socket\nfrom urllib.parse import urlparse\n\nimport pytest\nimport mock\n\nfrom util import UpnpPunch as upnp\n\n\n@pytest.fixture\ndef mock_socket():\n    mock_socket = mock.MagicMock()\n    mock_socket.recv = mock.MagicMock(return_value=b'Hello')\n    mock_socket.bind = mock.MagicMock()\n    mock_socket.send_to = mock.MagicMock()\n\n    return mock_socket\n\n\n@pytest.fixture\ndef url_obj():\n    return urlparse('http://192.168.1.1/ctrlPoint.xml')\n\n\n@pytest.fixture(params=['WANPPPConnection', 'WANIPConnection'])\ndef igd_profile(request):\n    return \"\"\"<root><serviceList><service>\n  <serviceType>urn:schemas-upnp-org:service:{}:1</serviceType>\n  <serviceId>urn:upnp-org:serviceId:wanpppc:pppoa</serviceId>\n  <controlURL>/upnp/control/wanpppcpppoa</controlURL>\n  <eventSubURL>/upnp/event/wanpppcpppoa</eventSubURL>\n  <SCPDURL>/WANPPPConnection.xml</SCPDURL>\n</service></serviceList></root>\"\"\".format(request.param)\n\n\n@pytest.fixture\ndef httplib_response():\n    class FakeResponse(object):\n        def __init__(self, status=200, body='OK'):\n            self.status = status\n            self.body = body\n\n        def read(self):\n            return self.body\n    return FakeResponse\n\n\nclass TestUpnpPunch(object):\n    def test_perform_m_search(self, mock_socket):\n        local_ip = '127.0.0.1'\n\n        with mock.patch('util.UpnpPunch.socket.socket',\n                        return_value=mock_socket):\n            result = upnp.perform_m_search(local_ip)\n            assert result == 'Hello'\n            assert local_ip == mock_socket.bind.call_args_list[0][0][0][0]\n            assert ('239.255.255.250',\n                    1900) == mock_socket.sendto.call_args_list[0][0][1]\n\n    def test_perform_m_search_socket_error(self, mock_socket):\n        mock_socket.recv.side_effect = socket.error('Timeout error')\n\n        with mock.patch('util.UpnpPunch.socket.socket',\n                        return_value=mock_socket):\n            with pytest.raises(upnp.UpnpError):\n                upnp.perform_m_search('127.0.0.1')\n\n    def test_retrieve_location_from_ssdp(self, url_obj):\n        ctrl_location = url_obj.geturl()\n        parsed_location = urlparse(ctrl_location)\n        rsp = ('auth: gibberish\\r\\nlocation: {0}\\r\\n'\n               'Content-Type: text/html\\r\\n\\r\\n').format(ctrl_location)\n        result = upnp._retrieve_location_from_ssdp(rsp)\n        assert result == parsed_location\n\n    def test_retrieve_location_from_ssdp_no_header(self):\n        rsp = 'auth: gibberish\\r\\nContent-Type: application/json\\r\\n\\r\\n'\n        with pytest.raises(upnp.IGDError):\n            upnp._retrieve_location_from_ssdp(rsp)\n\n    def test_retrieve_igd_profile(self, url_obj):\n        with mock.patch('urllib.request.urlopen') as mock_urlopen:\n            upnp._retrieve_igd_profile(url_obj)\n            mock_urlopen.assert_called_with(url_obj.geturl(), timeout=5)\n\n    def test_retrieve_igd_profile_timeout(self, url_obj):\n        with mock.patch('urllib.request.urlopen') as mock_urlopen:\n            mock_urlopen.side_effect = socket.error('Timeout error')\n            with pytest.raises(upnp.IGDError):\n                upnp._retrieve_igd_profile(url_obj)\n\n    def test_parse_igd_profile_service_type(self, igd_profile):\n        control_path, upnp_schema = upnp._parse_igd_profile(igd_profile)\n        assert control_path == '/upnp/control/wanpppcpppoa'\n        assert upnp_schema in ('WANPPPConnection', 'WANIPConnection',)\n\n    def test_parse_igd_profile_no_ctrlurl(self, igd_profile):\n        igd_profile = igd_profile.replace('controlURL', 'nope')\n        with pytest.raises(upnp.IGDError):\n            control_path, upnp_schema = upnp._parse_igd_profile(igd_profile)\n\n    def test_parse_igd_profile_no_schema(self, igd_profile):\n        igd_profile = igd_profile.replace('Connection', 'nope')\n        with pytest.raises(upnp.IGDError):\n            control_path, upnp_schema = upnp._parse_igd_profile(igd_profile)\n\n    def test_create_open_message_parsable(self):\n        from xml.parsers.expat import ExpatError\n        msg, _ = upnp._create_open_message('127.0.0.1', 8888)\n        try:\n            upnp.parseString(msg)\n        except ExpatError as e:\n            pytest.fail('Incorrect XML message: {}'.format(e))\n\n    def test_create_open_message_contains_right_stuff(self):\n        settings = {'description': 'test desc',\n                    'protocol': 'test proto',\n                    'upnp_schema': 'test schema'}\n        msg, fn_name = upnp._create_open_message('127.0.0.1', 8888, **settings)\n        assert fn_name == 'AddPortMapping'\n        assert '127.0.0.1' in msg\n        assert '8888' in msg\n        assert settings['description'] in msg\n        assert settings['protocol'] in msg\n        assert settings['upnp_schema'] in msg\n\n    def test_parse_for_errors_bad_rsp(self, httplib_response):\n        rsp = httplib_response(status=500)\n        with pytest.raises(upnp.IGDError) as err:\n            upnp._parse_for_errors(rsp)\n        assert 'Unable to parse' in str(err.value)\n\n    def test_parse_for_errors_error(self, httplib_response):\n        soap_error = ('<document>'\n                      '<errorCode>500</errorCode>'\n                      '<errorDescription>Bad request</errorDescription>'\n                      '</document>')\n        rsp = httplib_response(status=500, body=soap_error)\n        with pytest.raises(upnp.IGDError) as err:\n            upnp._parse_for_errors(rsp)\n        assert 'SOAP request error' in str(err.value)\n\n    def test_parse_for_errors_good_rsp(self, httplib_response):\n        rsp = httplib_response(status=200)\n        assert rsp == upnp._parse_for_errors(rsp)\n\n    def test_send_requests_success(self):\n        with mock.patch(\n                'util.UpnpPunch._send_soap_request') as mock_send_request:\n            mock_send_request.return_value = mock.MagicMock(status=200)\n            upnp._send_requests(['msg'], None, None, None)\n\n        assert mock_send_request.called\n\n    def test_send_requests_failed(self):\n        with mock.patch(\n                'util.UpnpPunch._send_soap_request') as mock_send_request:\n            mock_send_request.return_value = mock.MagicMock(status=500)\n            with pytest.raises(upnp.UpnpError):\n                upnp._send_requests(['msg'], None, None, None)\n\n        assert mock_send_request.called\n\n    def test_collect_idg_data(self):\n        pass\n\n    @mock.patch('util.UpnpPunch._get_local_ips')\n    @mock.patch('util.UpnpPunch._collect_idg_data')\n    @mock.patch('util.UpnpPunch._send_requests')\n    def test_ask_to_open_port_success(self, mock_send_requests,\n                                      mock_collect_idg, mock_local_ips):\n        mock_collect_idg.return_value = {'upnp_schema': 'schema-yo'}\n        mock_local_ips.return_value = ['192.168.0.12']\n\n        result = upnp.ask_to_open_port(retries=5)\n\n        soap_msg = mock_send_requests.call_args[0][0][0][0]\n\n        assert result is True\n\n        assert mock_collect_idg.called\n        assert '192.168.0.12' in soap_msg\n        assert '15441' in soap_msg\n        assert 'schema-yo' in soap_msg\n\n    @mock.patch('util.UpnpPunch._get_local_ips')\n    @mock.patch('util.UpnpPunch._collect_idg_data')\n    @mock.patch('util.UpnpPunch._send_requests')\n    def test_ask_to_open_port_failure(self, mock_send_requests,\n                                      mock_collect_idg, mock_local_ips):\n        mock_local_ips.return_value = ['192.168.0.12']\n        mock_collect_idg.return_value = {'upnp_schema': 'schema-yo'}\n        mock_send_requests.side_effect = upnp.UpnpError()\n\n        with pytest.raises(upnp.UpnpError):\n            upnp.ask_to_open_port()\n\n    @mock.patch('util.UpnpPunch._collect_idg_data')\n    @mock.patch('util.UpnpPunch._send_requests')\n    def test_orchestrate_soap_request(self, mock_send_requests,\n                                      mock_collect_idg):\n        soap_mock = mock.MagicMock()\n        args = ['127.0.0.1', 31337, soap_mock, 'upnp-test', {'upnp_schema':\n                                                             'schema-yo'}]\n        mock_collect_idg.return_value = args[-1]\n\n        upnp._orchestrate_soap_request(*args[:-1])\n\n        assert mock_collect_idg.called\n        soap_mock.assert_called_with(\n            *args[:2] + ['upnp-test', 'UDP', 'schema-yo'])\n        assert mock_send_requests.called\n\n    @mock.patch('util.UpnpPunch._collect_idg_data')\n    @mock.patch('util.UpnpPunch._send_requests')\n    def test_orchestrate_soap_request_without_desc(self, mock_send_requests,\n                                                   mock_collect_idg):\n        soap_mock = mock.MagicMock()\n        args = ['127.0.0.1', 31337, soap_mock, {'upnp_schema': 'schema-yo'}]\n        mock_collect_idg.return_value = args[-1]\n\n        upnp._orchestrate_soap_request(*args[:-1])\n\n        assert mock_collect_idg.called\n        soap_mock.assert_called_with(*args[:2] + [None, 'UDP', 'schema-yo'])\n        assert mock_send_requests.called\n\n    def test_create_close_message_parsable(self):\n        from xml.parsers.expat import ExpatError\n        msg, _ = upnp._create_close_message('127.0.0.1', 8888)\n        try:\n            upnp.parseString(msg)\n        except ExpatError as e:\n            pytest.fail('Incorrect XML message: {}'.format(e))\n\n    def test_create_close_message_contains_right_stuff(self):\n        settings = {'protocol': 'test proto',\n                    'upnp_schema': 'test schema'}\n        msg, fn_name = upnp._create_close_message('127.0.0.1', 8888, **\n                                                  settings)\n        assert fn_name == 'DeletePortMapping'\n        assert '8888' in msg\n        assert settings['protocol'] in msg\n        assert settings['upnp_schema'] in msg\n\n    @mock.patch('util.UpnpPunch._get_local_ips')\n    @mock.patch('util.UpnpPunch._orchestrate_soap_request')\n    def test_communicate_with_igd_success(self, mock_orchestrate,\n                                          mock_get_local_ips):\n        mock_get_local_ips.return_value = ['192.168.0.12']\n        upnp._communicate_with_igd()\n        assert mock_get_local_ips.called\n        assert mock_orchestrate.called\n\n    @mock.patch('util.UpnpPunch._get_local_ips')\n    @mock.patch('util.UpnpPunch._orchestrate_soap_request')\n    def test_communicate_with_igd_succeed_despite_single_failure(\n            self, mock_orchestrate, mock_get_local_ips):\n        mock_get_local_ips.return_value = ['192.168.0.12']\n        mock_orchestrate.side_effect = [upnp.UpnpError, None]\n        upnp._communicate_with_igd(retries=2)\n        assert mock_get_local_ips.called\n        assert mock_orchestrate.called\n\n    @mock.patch('util.UpnpPunch._get_local_ips')\n    @mock.patch('util.UpnpPunch._orchestrate_soap_request')\n    def test_communicate_with_igd_total_failure(self, mock_orchestrate,\n                                                mock_get_local_ips):\n        mock_get_local_ips.return_value = ['192.168.0.12']\n        mock_orchestrate.side_effect = [upnp.UpnpError, upnp.IGDError]\n        with pytest.raises(upnp.UpnpError):\n            upnp._communicate_with_igd(retries=2)\n        assert mock_get_local_ips.called\n        assert mock_orchestrate.called\n"
  },
  {
    "path": "src/Test/TestUser.py",
    "content": "import pytest\n\nfrom Crypt import CryptBitcoin\n\n\n@pytest.mark.usefixtures(\"resetSettings\")\nclass TestUser:\n    def testAddress(self, user):\n        assert user.master_address == \"15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc\"\n        address_index = 1458664252141532163166741013621928587528255888800826689784628722366466547364755811\n        assert user.getAddressAuthIndex(\"15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc\") == address_index\n\n    # Re-generate privatekey based on address_index\n    def testNewSite(self, user):\n        address, address_index, site_data = user.getNewSiteData()  # Create a new random site\n        assert CryptBitcoin.hdPrivatekey(user.master_seed, address_index) == site_data[\"privatekey\"]\n\n        user.sites = {}  # Reset user data\n\n        # Site address and auth address is different\n        assert user.getSiteData(address)[\"auth_address\"] != address\n        # Re-generate auth_privatekey for site\n        assert user.getSiteData(address)[\"auth_privatekey\"] == site_data[\"auth_privatekey\"]\n\n    def testAuthAddress(self, user):\n        # Auth address without Cert\n        auth_address = user.getAuthAddress(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\")\n        assert auth_address == \"1MyJgYQjeEkR9QD66nkfJc9zqi9uUy5Lr2\"\n        auth_privatekey = user.getAuthPrivatekey(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\")\n        assert CryptBitcoin.privatekeyToAddress(auth_privatekey) == auth_address\n\n    def testCert(self, user):\n        cert_auth_address = user.getAuthAddress(\"1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz\")  # Add site to user's registry\n        # Add cert\n        user.addCert(cert_auth_address, \"zeroid.bit\", \"faketype\", \"fakeuser\", \"fakesign\")\n        user.setCert(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\", \"zeroid.bit\")\n\n        # By using certificate the auth address should be same as the certificate provider\n        assert user.getAuthAddress(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\") == cert_auth_address\n        auth_privatekey = user.getAuthPrivatekey(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\")\n        assert CryptBitcoin.privatekeyToAddress(auth_privatekey) == cert_auth_address\n\n        # Test delete site data\n        assert \"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\" in user.sites\n        user.deleteSiteData(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\")\n        assert \"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\" not in user.sites\n\n        # Re-create add site should generate normal, unique auth_address\n        assert not user.getAuthAddress(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\") == cert_auth_address\n        assert user.getAuthAddress(\"1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr\") == \"1MyJgYQjeEkR9QD66nkfJc9zqi9uUy5Lr2\"\n"
  },
  {
    "path": "src/Test/TestWeb.py",
    "content": "import urllib.request\n\nimport pytest\n\ntry:\n    from selenium.webdriver.support.ui import WebDriverWait\n    from selenium.webdriver.support.expected_conditions import staleness_of, title_is\n    from selenium.common.exceptions import NoSuchElementException\nexcept:\n    pass\n\n\nclass WaitForPageLoad(object):\n    def __init__(self, browser):\n        self.browser = browser\n\n    def __enter__(self):\n        self.old_page = self.browser.find_element_by_tag_name('html')\n\n    def __exit__(self, *args):\n        WebDriverWait(self.browser, 10).until(staleness_of(self.old_page))\n\n\ndef getContextUrl(browser):\n    return browser.execute_script(\"return window.location.toString()\")\n\n\ndef getUrl(url):\n    content = urllib.request.urlopen(url).read()\n    assert \"server error\" not in content.lower(), \"Got a server error! \" + repr(url)\n    return content\n\n@pytest.mark.usefixtures(\"resetSettings\")\n@pytest.mark.webtest\nclass TestWeb:\n    def testFileSecurity(self, site_url):\n        assert \"Not Found\" in getUrl(\"%s/media/sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/media/./sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/media/../config.py\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/media/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../../zeronet.py\" % site_url)\n\n        assert \"Not Found\" in getUrl(\"%s/raw/sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/raw/./sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/raw/../config.py\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/raw/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/raw/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/raw/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../../zeronet.py\" % site_url)\n\n        assert \"Forbidden\" in getUrl(\"%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/..//sites.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/../../zeronet.py\" % site_url)\n\n        assert \"Forbidden\" in getUrl(\"%s/content.db\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/./users.json\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/./key-rsa.pem\" % site_url)\n        assert \"Forbidden\" in getUrl(\"%s/././././././././././//////sites.json\" % site_url)\n\n    def testLinkSecurity(self, browser, site_url):\n        browser.get(\"%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html\" % site_url)\n        WebDriverWait(browser, 10).until(title_is(\"ZeroHello - ZeroNet\"))\n        assert getContextUrl(browser) == \"%s/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html\" % site_url\n\n        # Switch to inner frame\n        browser.switch_to.frame(browser.find_element_by_id(\"inner-iframe\"))\n        assert \"wrapper_nonce\" in getContextUrl(browser)\n        assert browser.find_element_by_id(\"script_output\").text == \"Result: Works\"\n        browser.switch_to.default_content()\n\n        # Clicking on links without target\n        browser.switch_to.frame(browser.find_element_by_id(\"inner-iframe\"))\n        with WaitForPageLoad(browser):\n            browser.find_element_by_id(\"link_to_current\").click()\n        assert \"wrapper_nonce\" not in getContextUrl(browser)  # The browser object back to default content\n        assert \"Forbidden\" not in browser.page_source\n        # Check if we have frame inside frame\n        browser.switch_to.frame(browser.find_element_by_id(\"inner-iframe\"))\n        with pytest.raises(NoSuchElementException):\n            assert not browser.find_element_by_id(\"inner-iframe\")\n        browser.switch_to.default_content()\n\n        # Clicking on link with target=_top\n        browser.switch_to.frame(browser.find_element_by_id(\"inner-iframe\"))\n        with WaitForPageLoad(browser):\n            browser.find_element_by_id(\"link_to_top\").click()\n        assert \"wrapper_nonce\" not in getContextUrl(browser)  # The browser object back to default content\n        assert \"Forbidden\" not in browser.page_source\n        browser.switch_to.default_content()\n\n        # Try to escape from inner_frame\n        browser.switch_to.frame(browser.find_element_by_id(\"inner-iframe\"))\n        assert \"wrapper_nonce\" in getContextUrl(browser)  # Make sure we are inside of the inner-iframe\n        with WaitForPageLoad(browser):\n            browser.execute_script(\"window.top.location = window.location\")\n        assert \"wrapper_nonce\" in getContextUrl(browser)  # We try to use nonce-ed html without iframe\n        assert \"<iframe\" in browser.page_source  # Only allow to use nonce once-time\n        browser.switch_to.default_content()\n\n    def testRaw(self, browser, site_url):\n        browser.get(\"%s/raw/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html\" % site_url)\n        WebDriverWait(browser, 10).until(title_is(\"Security tests\"))\n        assert getContextUrl(browser) == \"%s/raw/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/security.html\" % site_url\n\n        assert browser.find_element_by_id(\"script_output\").text == \"Result: Fail\"\n"
  },
  {
    "path": "src/Test/TestWorkerTaskManager.py",
    "content": "import pytest\n\nfrom Worker import WorkerTaskManager\nfrom . import Spy\n\n\nclass TestUiWebsocket:\n    def checkSort(self, tasks):  # Check if it has the same order as a list sorted separately\n        tasks_list = list(tasks)\n        tasks_list.sort(key=lambda task: task[\"id\"])\n        assert tasks_list != list(tasks)\n        tasks_list.sort(key=lambda task: (0 - (task[\"priority\"] - task[\"workers_num\"] * 10), task[\"id\"]))\n        assert tasks_list == list(tasks)\n\n    def testAppendSimple(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        tasks.append({\"id\": 1, \"priority\": 15, \"workers_num\": 1, \"inner_path\": \"file1.json\"})\n        tasks.append({\"id\": 2, \"priority\": 1, \"workers_num\": 0, \"inner_path\": \"file2.json\"})\n        tasks.append({\"id\": 3, \"priority\": 8, \"workers_num\": 0, \"inner_path\": \"file3.json\"})\n        assert [task[\"inner_path\"] for task in tasks] == [\"file3.json\", \"file1.json\", \"file2.json\"]\n\n        self.checkSort(tasks)\n\n    def testAppendMany(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        for i in range(1000):\n            tasks.append({\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i})\n        assert tasks[0][\"inner_path\"] == \"file39.json\"\n        assert tasks[-1][\"inner_path\"] == \"file980.json\"\n\n        self.checkSort(tasks)\n\n    def testRemove(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        for i in range(1000):\n            tasks.append({\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i})\n\n        i = 333\n        task = {\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i}\n        assert task in tasks\n\n        with Spy.Spy(tasks, \"indexSlow\") as calls:\n            tasks.remove(task)\n            assert len(calls) == 0\n\n        assert task not in tasks\n\n        # Remove non existent item\n        with Spy.Spy(tasks, \"indexSlow\") as calls:\n            with pytest.raises(ValueError):\n                tasks.remove(task)\n            assert len(calls) == 0\n\n        self.checkSort(tasks)\n\n    def testRemoveAll(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        tasks_list = []\n        for i in range(1000):\n            task = {\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i}\n            tasks.append(task)\n            tasks_list.append(task)\n\n        for task in tasks_list:\n            tasks.remove(task)\n\n        assert len(tasks.inner_paths) == 0\n        assert len(tasks) == 0\n\n    def testModify(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        for i in range(1000):\n            tasks.append({\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i})\n\n        task = tasks[333]\n        task[\"priority\"] += 10\n\n        with pytest.raises(AssertionError):\n            self.checkSort(tasks)\n\n        with Spy.Spy(tasks, \"indexSlow\") as calls:\n            tasks.updateItem(task)\n            assert len(calls) == 1\n\n        assert task in tasks\n\n        self.checkSort(tasks)\n\n        # Check reorder optimization\n        with Spy.Spy(tasks, \"indexSlow\") as calls:\n            tasks.updateItem(task, \"priority\", task[\"priority\"] + 10)\n            assert len(calls) == 0\n\n        with Spy.Spy(tasks, \"indexSlow\") as calls:\n            tasks.updateItem(task, \"priority\", task[\"workers_num\"] - 1)\n            assert len(calls) == 0\n\n        self.checkSort(tasks)\n\n    def testModifySamePriority(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        for i in range(1000):\n            tasks.append({\"id\": i, \"priority\": 10, \"workers_num\": 5, \"inner_path\": \"file%s.json\" % i})\n\n        task = tasks[333]\n\n        # Check reorder optimization\n        with Spy.Spy(tasks, \"indexSlow\") as calls:\n            tasks.updateItem(task, \"priority\", task[\"workers_num\"] - 1)\n            assert len(calls) == 0\n\n    def testIn(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n\n        i = 1\n        task = {\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i}\n\n        assert task not in tasks\n\n    def testFindTask(self):\n        tasks = WorkerTaskManager.WorkerTaskManager()\n        for i in range(1000):\n            tasks.append({\"id\": i, \"priority\": i % 20, \"workers_num\": i % 3, \"inner_path\": \"file%s.json\" % i})\n\n        assert tasks.findTask(\"file999.json\")\n        assert not tasks.findTask(\"file-unknown.json\")\n        tasks.remove(tasks.findTask(\"file999.json\"))\n        assert not tasks.findTask(\"file999.json\")\n"
  },
  {
    "path": "src/Test/__init__.py",
    "content": ""
  },
  {
    "path": "src/Test/conftest.py",
    "content": "import os\nimport sys\nimport urllib.request\nimport time\nimport logging\nimport json\nimport shutil\nimport gc\nimport datetime\nimport atexit\nimport threading\nimport socket\n\nimport pytest\nimport mock\n\nimport gevent\nif \"libev\" not in str(gevent.config.loop):\n    # Workaround for random crash when libuv used with threads\n    gevent.config.loop = \"libev-cext\"\n\nimport gevent.event\nfrom gevent import monkey\nmonkey.patch_all(thread=False, subprocess=False)\n\natexit_register = atexit.register\natexit.register = lambda func: \"\"  # Don't register shutdown functions to avoid IO error on exit\n\ndef pytest_addoption(parser):\n    parser.addoption(\"--slow\", action='store_true', default=False, help=\"Also run slow tests\")\n\n\ndef pytest_collection_modifyitems(config, items):\n    if config.getoption(\"--slow\"):\n        # --runslow given in cli: do not skip slow tests\n        return\n    skip_slow = pytest.mark.skip(reason=\"need --slow option to run\")\n    for item in items:\n        if \"slow\" in item.keywords:\n            item.add_marker(skip_slow)\n\n# Config\nif sys.platform == \"win32\":\n    CHROMEDRIVER_PATH = \"tools/chrome/chromedriver.exe\"\nelse:\n    CHROMEDRIVER_PATH = \"chromedriver\"\nSITE_URL = \"http://127.0.0.1:43110\"\n\nTEST_DATA_PATH = 'src/Test/testdata'\nsys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + \"/../lib\"))  # External modules directory\nsys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + \"/..\"))  # Imports relative to src dir\n\nfrom Config import config\nconfig.argv = [\"none\"]  # Dont pass any argv to config parser\nconfig.parse(silent=True, parse_config=False)  # Plugins need to access the configuration\nconfig.action = \"test\"\n\n# Load plugins\nfrom Plugin import PluginManager\n\nconfig.data_dir = TEST_DATA_PATH  # Use test data for unittests\nconfig.debug = True\n\nos.chdir(os.path.abspath(os.path.dirname(__file__) + \"/../..\"))  # Set working dir\n\nall_loaded = PluginManager.plugin_manager.loadPlugins()\nassert all_loaded, \"Not all plugin loaded successfully\"\n\nconfig.loadPlugins()\nconfig.parse(parse_config=False)  # Parse again to add plugin configuration options\n\nconfig.action = \"test\"\nconfig.debug = True\nconfig.debug_socket = True  # Use test data for unittests\nconfig.verbose = True  # Use test data for unittests\nconfig.tor = \"disable\"  # Don't start Tor client\nconfig.trackers = []\nconfig.data_dir = TEST_DATA_PATH  # Use test data for unittests\nif \"ZERONET_LOG_DIR\" in os.environ:\n    config.log_dir = os.environ[\"ZERONET_LOG_DIR\"]\nconfig.initLogging(console_logging=False)\n\n# Set custom formatter with realative time format (via: https://stackoverflow.com/questions/31521859/python-logging-module-time-since-last-log)\ntime_start = time.time()\nclass TimeFilter(logging.Filter):\n    def __init__(self, *args, **kwargs):\n        self.time_last = time.time()\n        self.main_thread_id = threading.current_thread().ident\n        super().__init__(*args, **kwargs)\n\n    def filter(self, record):\n        if threading.current_thread().ident != self.main_thread_id:\n            record.thread_marker = \"T\"\n            record.thread_title = \"(Thread#%s)\" % self.main_thread_id\n        else:\n            record.thread_marker = \" \"\n            record.thread_title = \"\"\n\n        since_last = time.time() - self.time_last\n        if since_last > 0.1:\n            line_marker = \"!\"\n        elif since_last > 0.02:\n            line_marker = \"*\"\n        elif since_last > 0.01:\n            line_marker = \"-\"\n        else:\n            line_marker = \" \"\n\n        since_start = time.time() - time_start\n        record.since_start = \"%s%.3fs\" % (line_marker, since_start)\n\n        self.time_last = time.time()\n        return True\n\nlog = logging.getLogger()\nfmt = logging.Formatter(fmt='%(since_start)s %(thread_marker)s %(levelname)-8s %(name)s %(message)s %(thread_title)s')\n[hndl.addFilter(TimeFilter()) for hndl in log.handlers]\n[hndl.setFormatter(fmt) for hndl in log.handlers]\n\nfrom Site.Site import Site\nfrom Site import SiteManager\nfrom User import UserManager\nfrom File import FileServer\nfrom Connection import ConnectionServer\nfrom Crypt import CryptConnection\nfrom Crypt import CryptBitcoin\nfrom Ui import UiWebsocket\nfrom Tor import TorManager\nfrom Content import ContentDb\nfrom util import RateLimit\nfrom Db import Db\nfrom Debug import Debug\n\ngevent.get_hub().NOT_ERROR += (Debug.Notify,)\n\ndef cleanup():\n    Db.dbCloseAll()\n    for dir_path in [config.data_dir, config.data_dir + \"-temp\"]:\n        if os.path.isdir(dir_path):\n            for file_name in os.listdir(dir_path):\n                ext = file_name.rsplit(\".\", 1)[-1]\n                if ext not in [\"csr\", \"pem\", \"srl\", \"db\", \"json\", \"tmp\"]:\n                    continue\n                file_path = dir_path + \"/\" + file_name\n                if os.path.isfile(file_path):\n                    os.unlink(file_path)\n\natexit_register(cleanup)\n\n@pytest.fixture(scope=\"session\")\ndef resetSettings(request):\n    open(\"%s/sites.json\" % config.data_dir, \"w\").write(\"{}\")\n    open(\"%s/filters.json\" % config.data_dir, \"w\").write(\"{}\")\n    open(\"%s/users.json\" % config.data_dir, \"w\").write(\"\"\"\n        {\n            \"15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc\": {\n                \"certs\": {},\n                \"master_seed\": \"024bceac1105483d66585d8a60eaf20aa8c3254b0f266e0d626ddb6114e2949a\",\n                \"sites\": {}\n            }\n        }\n    \"\"\")\n\n\n@pytest.fixture(scope=\"session\")\ndef resetTempSettings(request):\n    data_dir_temp = config.data_dir + \"-temp\"\n    if not os.path.isdir(data_dir_temp):\n        os.mkdir(data_dir_temp)\n    open(\"%s/sites.json\" % data_dir_temp, \"w\").write(\"{}\")\n    open(\"%s/filters.json\" % data_dir_temp, \"w\").write(\"{}\")\n    open(\"%s/users.json\" % data_dir_temp, \"w\").write(\"\"\"\n        {\n            \"15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc\": {\n                \"certs\": {},\n                \"master_seed\": \"024bceac1105483d66585d8a60eaf20aa8c3254b0f266e0d626ddb6114e2949a\",\n                \"sites\": {}\n            }\n        }\n    \"\"\")\n\n    def cleanup():\n        os.unlink(\"%s/sites.json\" % data_dir_temp)\n        os.unlink(\"%s/users.json\" % data_dir_temp)\n        os.unlink(\"%s/filters.json\" % data_dir_temp)\n    request.addfinalizer(cleanup)\n\n\n@pytest.fixture()\ndef site(request):\n    threads_before = [obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet)]\n    # Reset ratelimit\n    RateLimit.queue_db = {}\n    RateLimit.called_db = {}\n\n    site = Site(\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\")\n\n    # Always use original data\n    assert \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\" in site.storage.getPath(\"\")  # Make sure we dont delete everything\n    shutil.rmtree(site.storage.getPath(\"\"), True)\n    shutil.copytree(site.storage.getPath(\"\") + \"-original\", site.storage.getPath(\"\"))\n\n    # Add to site manager\n    SiteManager.site_manager.get(\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\")\n    site.announce = mock.MagicMock(return_value=True)  # Don't try to find peers from the net\n\n    def cleanup():\n        site.delete()\n        site.content_manager.contents.db.close(\"Test cleanup\")\n        site.content_manager.contents.db.timer_check_optional.kill()\n        SiteManager.site_manager.sites.clear()\n        db_path = \"%s/content.db\" % config.data_dir\n        os.unlink(db_path)\n        del ContentDb.content_dbs[db_path]\n        gevent.killall([obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet) and obj not in threads_before])\n    request.addfinalizer(cleanup)\n\n    site.greenlet_manager.stopGreenlets()\n    site = Site(\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\")  # Create new Site object to load content.json files\n    if not SiteManager.site_manager.sites:\n        SiteManager.site_manager.sites = {}\n    SiteManager.site_manager.sites[\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\"] = site\n    site.settings[\"serving\"] = True\n    return site\n\n\n@pytest.fixture()\ndef site_temp(request):\n    threads_before = [obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet)]\n    with mock.patch(\"Config.config.data_dir\", config.data_dir + \"-temp\"):\n        site_temp = Site(\"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\")\n        site_temp.settings[\"serving\"] = True\n        site_temp.announce = mock.MagicMock(return_value=True)  # Don't try to find peers from the net\n\n    def cleanup():\n        site_temp.delete()\n        site_temp.content_manager.contents.db.close(\"Test cleanup\")\n        site_temp.content_manager.contents.db.timer_check_optional.kill()\n        db_path = \"%s-temp/content.db\" % config.data_dir\n        os.unlink(db_path)\n        del ContentDb.content_dbs[db_path]\n        gevent.killall([obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet) and obj not in threads_before])\n    request.addfinalizer(cleanup)\n    site_temp.log = logging.getLogger(\"Temp:%s\" % site_temp.address_short)\n    return site_temp\n\n\n@pytest.fixture(scope=\"session\")\ndef user():\n    user = UserManager.user_manager.get()\n    if not user:\n        user = UserManager.user_manager.create()\n    user.sites = {}  # Reset user data\n    return user\n\n\n@pytest.fixture(scope=\"session\")\ndef browser(request):\n    try:\n        from selenium import webdriver\n        print(\"Starting chromedriver...\")\n        options = webdriver.chrome.options.Options()\n        options.add_argument(\"--headless\")\n        options.add_argument(\"--window-size=1920x1080\")\n        options.add_argument(\"--log-level=1\")\n        browser = webdriver.Chrome(executable_path=CHROMEDRIVER_PATH, service_log_path=os.path.devnull, options=options)\n\n        def quit():\n            browser.quit()\n        request.addfinalizer(quit)\n    except Exception as err:\n        raise pytest.skip(\"Test requires selenium + chromedriver: %s\" % err)\n    return browser\n\n\n@pytest.fixture(scope=\"session\")\ndef site_url():\n    try:\n        urllib.request.urlopen(SITE_URL).read()\n    except Exception as err:\n        raise pytest.skip(\"Test requires zeronet client running: %s\" % err)\n    return SITE_URL\n\n\n@pytest.fixture(params=['ipv4', 'ipv6'])\ndef file_server(request):\n    if request.param == \"ipv4\":\n        return request.getfixturevalue(\"file_server4\")\n    else:\n        return request.getfixturevalue(\"file_server6\")\n\n\n@pytest.fixture\ndef file_server4(request):\n    time.sleep(0.1)\n    file_server = FileServer(\"127.0.0.1\", 1544)\n    file_server.ip_external = \"1.2.3.4\"  # Fake external ip\n\n    def listen():\n        ConnectionServer.start(file_server)\n        ConnectionServer.listen(file_server)\n\n    gevent.spawn(listen)\n    # Wait for port opening\n    for retry in range(10):\n        time.sleep(0.1)  # Port opening\n        try:\n            conn = file_server.getConnection(\"127.0.0.1\", 1544)\n            conn.close()\n            break\n        except Exception as err:\n            print(\"FileServer6 startup error\", Debug.formatException(err))\n    assert file_server.running\n    file_server.ip_incoming = {}  # Reset flood protection\n\n    def stop():\n        file_server.stop()\n    request.addfinalizer(stop)\n    return file_server\n\n\n@pytest.fixture\ndef file_server6(request):\n    try:\n        sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)\n        sock.connect((\"::1\", 80, 1, 1))\n        has_ipv6 = True\n    except OSError:\n        has_ipv6 = False\n    if not has_ipv6:\n        pytest.skip(\"Ipv6 not supported\")\n\n\n    time.sleep(0.1)\n    file_server6 = FileServer(\"::1\", 1544)\n    file_server6.ip_external = 'fca5:95d6:bfde:d902:8951:276e:1111:a22c'  # Fake external ip\n\n    def listen():\n        ConnectionServer.start(file_server6)\n        ConnectionServer.listen(file_server6)\n\n    gevent.spawn(listen)\n    # Wait for port opening\n    for retry in range(10):\n        time.sleep(0.1)  # Port opening\n        try:\n            conn = file_server6.getConnection(\"::1\", 1544)\n            conn.close()\n            break\n        except Exception as err:\n            print(\"FileServer6 startup error\", Debug.formatException(err))\n    assert file_server6.running\n    file_server6.ip_incoming = {}  # Reset flood protection\n\n    def stop():\n        file_server6.stop()\n    request.addfinalizer(stop)\n    return file_server6\n\n\n@pytest.fixture()\ndef ui_websocket(site, user):\n    class WsMock:\n        def __init__(self):\n            self.result = gevent.event.AsyncResult()\n\n        def send(self, data):\n            logging.debug(\"WsMock: Set result (data: %s) called by %s\" % (data, Debug.formatStack()))\n            self.result.set(json.loads(data)[\"result\"])\n\n        def getResult(self):\n            logging.debug(\"WsMock: Get result\")\n            back = self.result.get()\n            logging.debug(\"WsMock: Got result (data: %s)\" % back)\n            self.result = gevent.event.AsyncResult()\n            return back\n\n    ws_mock = WsMock()\n    ui_websocket = UiWebsocket(ws_mock, site, None, user, None)\n\n    def testAction(action, *args, **kwargs):\n        ui_websocket.handleRequest({\"id\": 0, \"cmd\": action, \"params\": list(args) if args else kwargs})\n        return ui_websocket.ws.getResult()\n\n    ui_websocket.testAction = testAction\n    return ui_websocket\n\n\n@pytest.fixture(scope=\"session\")\ndef tor_manager():\n    try:\n        tor_manager = TorManager(fileserver_port=1544)\n        tor_manager.start()\n        assert tor_manager.conn is not None\n        tor_manager.startOnions()\n    except Exception as err:\n        raise pytest.skip(\"Test requires Tor with ControlPort: %s, %s\" % (config.tor_controller, err))\n    return tor_manager\n\n\n@pytest.fixture()\ndef db(request):\n    db_path = \"%s/zeronet.db\" % config.data_dir\n    schema = {\n        \"db_name\": \"TestDb\",\n        \"db_file\": \"%s/zeronet.db\" % config.data_dir,\n        \"maps\": {\n            \"data.json\": {\n                \"to_table\": [\n                    \"test\",\n                    {\"node\": \"test\", \"table\": \"test_importfilter\", \"import_cols\": [\"test_id\", \"title\"]}\n                ]\n            }\n        },\n        \"tables\": {\n            \"test\": {\n                \"cols\": [\n                    [\"test_id\", \"INTEGER\"],\n                    [\"title\", \"TEXT\"],\n                    [\"json_id\", \"INTEGER REFERENCES json (json_id)\"]\n                ],\n                \"indexes\": [\"CREATE UNIQUE INDEX test_id ON test(test_id)\"],\n                \"schema_changed\": 1426195822\n            },\n            \"test_importfilter\": {\n                \"cols\": [\n                    [\"test_id\", \"INTEGER\"],\n                    [\"title\", \"TEXT\"],\n                    [\"json_id\", \"INTEGER REFERENCES json (json_id)\"]\n                ],\n                \"indexes\": [\"CREATE UNIQUE INDEX test_importfilter_id ON test_importfilter(test_id)\"],\n                \"schema_changed\": 1426195822\n            }\n        }\n    }\n\n    if os.path.isfile(db_path):\n        os.unlink(db_path)\n    db = Db.Db(schema, db_path)\n    db.checkTables()\n\n    def stop():\n        db.close(\"Test db cleanup\")\n        os.unlink(db_path)\n\n    request.addfinalizer(stop)\n    return db\n\n\n@pytest.fixture(params=[\"sslcrypto\", \"sslcrypto_fallback\", \"libsecp256k1\"])\ndef crypt_bitcoin_lib(request, monkeypatch):\n    monkeypatch.setattr(CryptBitcoin, \"lib_verify_best\", request.param)\n    CryptBitcoin.loadLib(request.param)\n    return CryptBitcoin\n\n@pytest.fixture(scope='function', autouse=True)\ndef logCaseStart(request):\n    global time_start\n    time_start = time.time()\n    logging.debug(\"---- Start test case: %s ----\" % request._pyfuncitem)\n    yield None  # Wait until all test done\n\n\n# Workaround for pytest bug when logging in atexit/post-fixture handlers (I/O operation on closed file)\ndef workaroundPytestLogError():\n    import _pytest.capture\n    write_original = _pytest.capture.EncodedFile.write\n\n    def write_patched(obj, *args, **kwargs):\n        try:\n            write_original(obj, *args, **kwargs)\n        except ValueError as err:\n            if str(err) == \"I/O operation on closed file\":\n                pass\n            else:\n                raise err\n\n    def flush_patched(obj, *args, **kwargs):\n        try:\n            obj.buffer.flush(*args, **kwargs)\n        except ValueError as err:\n            if str(err).startswith(\"I/O operation on closed file\"):\n                pass\n            else:\n                raise err\n\n    _pytest.capture.EncodedFile.write = write_patched\n    _pytest.capture.EncodedFile.flush = flush_patched\n\n\nworkaroundPytestLogError()\n\n@pytest.fixture(scope='session', autouse=True)\ndef disableLog():\n    yield None  # Wait until all test done\n    logging.getLogger('').setLevel(logging.getLevelName(logging.CRITICAL))\n\n"
  },
  {
    "path": "src/Test/coverage.ini",
    "content": "[run]\nbranch = True\nconcurrency = gevent\nomit =\n    src/lib/*\n    src/Test/*\n\n[report]\nexclude_lines =\n    pragma: no cover\n    if __name__ == .__main__.:\n    if config.debug:\n    if config.debug_socket:\n    if self.logging:\n    def __repr__\n"
  },
  {
    "path": "src/Test/pytest.ini",
    "content": "[pytest]\npython_files = Test*.py\naddopts = -rsxX -v --durations=6 --no-print-logs --capture=fd\nmarkers =\n    slow: mark a tests as slow.\n    webtest: mark a test as a webtest.\n"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/content.json",
    "content": "{\n \"address\": \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\",\n \"background-color\": \"white\",\n \"description\": \"Blogging platform Demo\",\n \"domain\": \"Blog.ZeroNetwork.bit\",\n \"files\": {\n  \"css/all.css\": {\n   \"sha512\": \"65ddd3a2071a0f48c34783aa3b1bde4424bdea344630af05a237557a62bd55dc\",\n   \"size\": 112710\n  },\n  \"data-default/data.json\": {\n   \"sha512\": \"3f5c5a220bde41b464ab116cce0bd670dd0b4ff5fe4a73d1dffc4719140038f2\",\n   \"size\": 196\n  },\n  \"data-default/users/content-default.json\": {\n   \"sha512\": \"0603ce08f7abb92b3840ad0cf40e95ea0b3ed3511b31524d4d70e88adba83daa\",\n   \"size\": 679\n  },\n  \"data/data.json\": {\n   \"sha512\": \"0f2321c905b761a05c360a389e1de149d952b16097c4ccf8310158356e85fb52\",\n   \"size\": 31126\n  },\n  \"data/img/autoupdate.png\": {\n   \"sha512\": \"d2b4dc8e0da2861ea051c0c13490a4eccf8933d77383a5b43de447c49d816e71\",\n   \"size\": 24460\n  },\n  \"data/img/direct_domains.png\": {\n   \"sha512\": \"5f14b30c1852735ab329b22496b1e2ea751cb04704789443ad73a70587c59719\",\n   \"size\": 16185\n  },\n  \"data/img/domain.png\": {\n   \"sha512\": \"ce87e0831f4d1e95a95d7120ca4d33f8273c6fce9f5bbedf7209396ea0b57b6a\",\n   \"size\": 11881\n  },\n  \"data/img/memory.png\": {\n   \"sha512\": \"dd56515085b4a79b5809716f76f267ec3a204be3ee0d215591a77bf0f390fa4e\",\n   \"size\": 12775\n  },\n  \"data/img/multiuser.png\": {\n   \"sha512\": \"88e3f795f9b86583640867897de6efc14e1aa42f93e848ed1645213e6cc210c6\",\n   \"size\": 29480\n  },\n  \"data/img/progressbar.png\": {\n   \"sha512\": \"23d592ae386ce14158cec34d32a3556771725e331c14d5a4905c59e0fe980ebf\",\n   \"size\": 13294\n  },\n  \"data/img/slides.png\": {\n   \"sha512\": \"1933db3b90ab93465befa1bd0843babe38173975e306286e08151be9992f767e\",\n   \"size\": 14439\n  },\n  \"data/img/slots_memory.png\": {\n   \"sha512\": \"82a250e6da909d7f66341e5b5c443353958f86728cd3f06e988b6441e6847c29\",\n   \"size\": 9488\n  },\n  \"data/img/trayicon.png\": {\n   \"sha512\": \"e7ae65bf280f13fb7175c1293dad7d18f1fcb186ebc9e1e33850cdaccb897b8f\",\n   \"size\": 19040\n  },\n  \"dbschema.json\": {\n   \"sha512\": \"2e9466d8aa1f340c91203b4ddbe9b6669879616a1b8e9571058a74195937598d\",\n   \"size\": 1527\n  },\n  \"img/loading.gif\": {\n   \"sha512\": \"8a42b98962faea74618113166886be488c09dad10ca47fe97005edc5fb40cc00\",\n   \"size\": 723\n  },\n  \"index.html\": {\n   \"sha512\": \"c4039ebfc4cb6f116cac05e803a18644ed70404474a572f0d8473f4572f05df3\",\n   \"size\": 4667\n  },\n  \"js/all.js\": {\n   \"sha512\": \"034c97535f3c9b3fbebf2dcf61a38711dae762acf1a99168ae7ddc7e265f582c\",\n   \"size\": 201178\n  }\n },\n \"files_optional\": {\n  \"data/img/zeroblog-comments.png\": {\n   \"sha512\": \"efe4e815a260e555303e5c49e550a689d27a8361f64667bd4a91dbcccb83d2b4\",\n   \"size\": 24001\n  },\n  \"data/img/zeroid.png\": {\n   \"sha512\": \"b46d541a9e51ba2ddc8a49955b7debbc3b45fd13467d3c20ef104e9d938d052b\",\n   \"size\": 18875\n  },\n  \"data/img/zeroname.png\": {\n   \"sha512\": \"bab45a1bb2087b64e4f69f756b2ffa5ad39b7fdc48c83609cdde44028a7a155d\",\n   \"size\": 36031\n  },\n  \"data/img/zerotalk-mark.png\": {\n   \"sha512\": \"a335b2fedeb8d291ca68d3091f567c180628e80f41de4331a5feb19601d078af\",\n   \"size\": 44862\n  },\n  \"data/img/zerotalk-upvote.png\": {\n   \"sha512\": \"b1ffd7f948b4f99248dde7efe256c2efdfd997f7e876fb9734f986ef2b561732\",\n   \"size\": 41092\n  },\n  \"data/img/zerotalk.png\": {\n   \"sha512\": \"54d10497a1ffca9a4780092fd1bd158c15f639856d654d2eb33a42f9d8e33cd8\",\n   \"size\": 26606\n  },\n  \"data/optional.txt\": {\n   \"sha512\": \"c6f81db0e9f8206c971c9e5826e3ba823ffbb1a3a900f8047652a8bf78ea98fd\",\n   \"size\": 6\n  }\n },\n \"ignore\": \"((js|css)/(?!all.(js|css))|data/.*db|data/users/.*/.*|data/test_include/.*)\",\n \"includes\": {\n  \"data/test_include/content.json\": {\n   \"added\": 1424976057,\n   \"files_allowed\": \"data.json\",\n   \"includes_allowed\": false,\n   \"max_size\": 20000,\n   \"signers\": [\"15ik6LeBWnACWfaika1xqGapRZ1zh3JpCo\"],\n   \"signers_required\": 1,\n   \"user_id\": 47,\n   \"user_name\": \"test\"\n  },\n  \"data/users/content.json\": {\n   \"signers\": [\"1LSxsKfC9S9TVXGGNSM3vPHjyW82jgCX5f\"],\n   \"signers_required\": 1\n  }\n },\n \"inner_path\": \"content.json\",\n \"modified\": 1503257990,\n \"optional\": \"(data/img/zero.*|data/optional.*)\",\n \"signers_sign\": \"HDNmWJHM2diYln4pkdL+qYOvgE7MdwayzeG+xEUZBgp1HtOjBJS+knDEVQsBkjcOPicDG2it1r6R1eQrmogqSP0=\",\n \"signs\": {\n  \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": \"G4Uq365UBliQG66ygip1jNGYqW6Eh9Mm7nLguDFqAgk/Hksq/ruqMf9rXv78mgUfPBvL2+XgDKYvFDtlykPFZxk=\"\n },\n \"signs_required\": 1,\n \"title\": \"ZeroBlog\",\n \"zeronet_version\": \"0.5.7\"\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/css/all.css",
    "content": "\n\n/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/Comments.css ---- */\n\n\n.comments { margin-bottom: 60px }\n.comment { background-color: white; padding: 25px 0px; margin: 1px; border-top: 1px solid #EEE }\n.comment .user_name { font-size: 14px; font-weight: bold }\n.comment .added { color: #AAA }\n.comment .reply { color: #CCC; opacity: 0; -webkit-transition: opacity 0.3s; -moz-transition: opacity 0.3s; -o-transition: opacity 0.3s; -ms-transition: opacity 0.3s; transition: opacity 0.3s ; border: none }\n.comment:hover .reply { opacity: 1 }\n.comment .reply .icon { opacity: 0.3 }\n.comment .reply:hover { border-bottom: none; color: #666 }\n.comment .reply:hover .icon { opacity: 1 }\n.comment .info { font-size: 12px; color: #AAA; margin-bottom: 7px }\n.comment .info .score { margin-left: 5px }\n.comment .comment-body { line-height: 1.5em; margin-top: 0.5em; margin-bottom: 0.5em }\n.comment .comment-body p { margin-bottom: 0px; margin-top: 0.5em; }\n.comment .comment-body p:first-child { margin: 0px; margin-top: 0px; }\n.comment .comment-body.editor { margin-top: 0.5em !important; margin-bottom: 0.5em !important }\n.comment .comment-body h1, .comment .body h2, .comment .body h3 { font-size: 110% }\n.comment .comment-body blockquote { padding: 1px 15px; border-left: 2px solid #E7E7E7; margin: 0px; margin-top: 30px }\n.comment .comment-body blockquote:first-child { margin-top: 0px }\n.comment .comment-body blockquote p { margin: 0px; color: #999; font-size: 90% }\n.comment .comment-body blockquote a { color: #333; font-weight: normal; border-bottom: 0px }\n.comment .comment-body blockquote a:hover { border-bottom: 1px solid #999 }\n.comment .editable-edit { margin-top: -5px }\n\n.comment-new { margin-bottom: 5px; border-top: 0px }\n.comment-new .button-submit { \n\tmargin: 0px; font-weight: normal; padding: 5px 15px; display: inline-block;\n\tbackground-color: #FFF85F; border-bottom: 2px solid #CDBD1E; font-size: 15px; line-height: 30px\n}\n.comment-new h2 { margin-bottom: 25px }\n\n/* Input */\n.comment-new textarea { \n\tline-height: 1.5em; width: 100%; padding: 10px; font-family: 'Roboto', sans-serif; font-size: 16px;\n\t-webkit-transition: border 0.3s; -moz-transition: border 0.3s; -o-transition: border 0.3s; -ms-transition: border 0.3s; transition: border 0.3s ; border: 2px solid #eee; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; overflow-y: auto\n}\ninput.text:focus, textarea:focus { border-color: #5FC0EA; outline: none; background-color: white }\n\n.comment-nocert textarea { opacity: 0.5; pointer-events: none }\n.comment-nocert .info { opacity: 0.1; pointer-events: none }\n.comment-nocert .button-submit-comment { opacity: 0.1; pointer-events: none }\n.comment-nocert .button.button-certselect { display: inherit }\n.button.button-certselect { \n\tposition: absolute; left: 50%; white-space: nowrap; -webkit-transform: translateX(-50%); -moz-transform: translateX(-50%); -o-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%) ; z-index: 99;\n\tmargin-top: 13px; background-color: #007AFF; color: white; border-bottom-color: #3543F9; display: none\n}\n.button.button-certselect:hover { background-color: #3396FF; color: white; border-bottom-color: #5D68FF; }\n.button.button-certselect:active { position: absolute; -webkit-transform: translateX(-50%) translateY(1px); -moz-transform: translateX(-50%) translateY(1px); -o-transform: translateX(-50%) translateY(1px); -ms-transform: translateX(-50%) translateY(1px); transform: translateX(-50%) translateY(1px) ; top: auto; }\n\n.user-size { font-size: 11px; margin-top: 6px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; text-transform: uppercase; display: inline-block; color: #AAA }\n.user-size-used { position: absolute; color: #B10DC9; overflow: hidden; width: 40px; white-space: nowrap } \n\n\n\n/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/ZeroBlog.css ---- */\n\n\n/* Design based on medium */\n\nbody { background-color: white; color: #333332; margin: 10px; padding: 0px; font-family: 'Roboto', sans-serif; height: 15000px; overflow: hidden }\nbody.loaded { height: auto; overflow: auto }\nh1, h2, h3, h4 { font-family: 'Roboto', sans-serif; font-weight: normal; margin: 0px; padding: 0px }\nh1 { font-size: 32px; line-height: 1.2em; font-weight: bold; letter-spacing: -0.5px; margin-bottom: 5px }\nh2 { margin-top: 3em }\nh3 { font-size: 24px; margin-top: 2em }\nh1 + h2, h2 + h3 { margin-top: inherit }\n\np { margin-top: 0.9em; margin-bottom: 0.9em }\nhr { margin: 20px 0px; border: none; border-bottom: 1px solid #eee; margin-left: auto; margin-right: auto; width: 120px; }\nsmall { font-size: 80%; color: #999; }\n\na { border-bottom: 1px solid #3498db; text-decoration: none; color: black; font-weight: bold }\na.nolink { border-bottom: none }\na:hover { color: #3498db }\n\n.button { \n\tpadding: 5px 10px; margin-left: 10px; background-color: #DDE0E0; border-bottom: 2px solid #999998; background-position: left center; \n\t-webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; text-decoration: none; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; color: #333\n}\n.button:hover { background-color: #FFF400; border-color: white; border-bottom: 2px solid #4D4D4C; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; color: inherit }\n.button:active { position: relative; top: 1px }\n\n/*.button-delete { background-color: #e74c3c; border-bottom-color: #A83F34; color: white }*/\n.button-outline { background-color: white; color: #DDD; border: 1px solid #eee }\n\n.button-delete:hover { background-color: #FF5442; border: 1px solid #FF5442; color: white } \n.button-ok:hover { background-color: #27AE60; border: 1px solid #27AE60; color: white } \n\n.button.loading { \n\tcolor: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center; \n\t-webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; pointer-events: none; border-bottom: 2px solid #666\n} \n\n.cancel { margin-left: 10px; font-size: 80%; color: #999; }\n\n\n.template { display: none }\n\n/* Editable */\n.editable { outline: none }\n.editable-edit:hover { opacity: 1; border: none; color: #333 }\n.editable-edit { \n\topacity: 0; float: left; margin-top: 0px; margin-left: -40px; padding: 8px 20px; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; width: 0px; display: inline-block; padding-right: 0px;\n\tcolor: rgba(100,100,100,0.5); text-decoration: none; font-size: 18px; font-weight: normal; border: none; \n}\n/*.editing { white-space: pre-wrap; z-index: 1; position: relative; outline: 10000px solid rgba(255,255,255,0.9) !important; }\n.editing p { margin: 0px; padding: 0px }*/ /* IE FIX */\n.editor { width: 100%; display: block; overflow :hidden; resize: none; border: none; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; z-index: 900; position: relative }\n.editor:focus { border: 0px; outline-offset: 0px }\n\n\n/* -- Editbar -- */\n\n.bottombar { \n\tdisplay: none; position: fixed; padding: 10px 20px; opacity: 0; background-color: rgba(255,255,255,0.9);\n\tright: 30px; bottom: 0px; z-index: 999; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; transform: translateY(50px) \n}\n.bottombar.visible { -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -o-transform: translateY(0px); -ms-transform: translateY(0px); transform: translateY(0px) ; opacity: 1 }\n.publishbar { z-index: 990}\n.publishbar.visible { display: inline-block; }\n.editbar { -webkit-perspective: 900px ; -moz-perspective: 900px ; -o-perspective: 900px ; -ms-perspective: 900px ; perspective: 900px  }\n.markdown-help { \n\tposition: absolute; bottom: 30px; -webkit-transform: translateX(0px) rotateY(-40deg); -moz-transform: translateX(0px) rotateY(-40deg); -o-transform: translateX(0px) rotateY(-40deg); -ms-transform: translateX(0px) rotateY(-40deg); transform: translateX(0px) rotateY(-40deg) ; transform-origin: right; right: 0px; \n\tlist-style-type: none; background-color: rgba(255,255,255,0.9); padding: 10px; opacity: 0; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ; display: none\n}\n.markdown-help.visible { -webkit-transform: none; -moz-transform: none; -o-transform: none; -ms-transform: none; transform: none ; opacity: 1 }\n.markdown-help li { margin: 10px 0px }\n.markdown-help code { font-size: 100% }\n.icon-help { border: 1px solid #EEE; padding: 2px; display: inline-block; width: 17px; text-align: center; font-size: 13px; margin-right: 6px; vertical-align: 1px }\n.icon-help.active { background-color: #EEE }\n\n/* -- Left -- */\n\n.left { float: left; position: absolute; width: 170px; padding-left: 60px; padding-right: 20px; margin-top: 60px; text-align: right }\n.right { float: left; padding-left: 60px; margin-left: 240px; max-width: 650px; padding-right: 60px; padding-top: 60px }\n\n.left .avatar { \n\tbackground-color: #F0F0F0; width: 60px; height: 60px; -webkit-border-radius: 100%; -moz-border-radius: 100%; -o-border-radius: 100%; -ms-border-radius: 100%; border-radius: 100% ; margin-bottom: 10px; \n\tbackground-position: center center; background-size: 70%; display: inline-block;\n}\n.left h1 a.nolink { font-family: Tinos; display: inline-block; padding: 1px }\n.left h1 a.editable-edit { float: none }\n.left h2 { font-size: 15px; font-family: Tinos; line-height: 1.6em; color: #AAA; margin-top: 14px; letter-spacing: 0.2px }\n.left ul, .left li { padding: 0px; margin: 0px; list-style-type: none; line-height: 2em }\n.left hr { margin-left: 100px; margin-right: 0px; width: auto }\n.left .links { width: 230px; margin-left: -60px }\n.left .links.editor { text-align: left !important }\n\n/* -- Post -- */\n\n.posts .new { display: none; position: absolute; top: -50px; margin-left: 0px; left: 50%; -webkit-transform: translateX(-50%) ; -moz-transform: translateX(-50%) ; -o-transform: translateX(-50%) ; -ms-transform: translateX(-50%) ; transform: translateX(-50%)  }\n\n.posts, .post-full { display: none; position: relative; }\n.page-main .posts { display: block }\n.page-post.loaded .post-full { display: block; border-bottom: none }\n\n\n.post { margin-bottom: 50px; padding-bottom: 50px; border-bottom: 1px solid #eee; min-width: 500px }\n.post .title a { text-decoration: none; color: inherit; display: inline-block; border-bottom: none; font-weight: inherit }\n.posts .title a:visited { color: #666969 }\n.post .details { color: #BBB; margin-top: 5px; margin-bottom: 20px }\n.post .details .comments-num { border: none; color: #BBB; font-weight: normal; }\n.post .details .comments-num .num { border-bottom: 1px solid #eee; color: #000; }\n.post .details .comments-num:hover .num { border-bottom: 1px solid #D6A1DE; }\n.post .body { font-size: 21.5px; line-height: 1.6; font-family: Tinos; margin-top: 20px }\n\n.post .body h1 { text-align: center; margin-top: 50px }\n.post .body h1:before { content: \" \"; border-top: 1px solid #EEE; width: 120px; display: block; margin-left: auto; margin-right: auto; margin-bottom: 50px; }\n\n.post .body p + ul { margin-top: -0.5em }\n.post .body li { margin-top: 0.5em; margin-bottom: 0.5em }\n.post .body hr:first-of-type { display: none }\n\n.post .body a img { margin-bottom: -8px }\n.post .body img { max-width: 100% }\n\ncode { \n\tbackground-color: #f5f5f5; border: 1px solid #ccc; padding: 0px 5px; overflow: auto; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; display: inline-block;\n\tcolor: #444; font-weight: normal; font-size: 60%; vertical-align: text-bottom; border-bottom-width: 2px;\n}\n.post .body pre { table-layout: fixed; width: auto; display: table; white-space: normal; }\n.post .body pre code { padding: 10px 20px; white-space: pre; max-width: 850px }\n\nblockquote { border-left: 3px solid #333; margin-left: 0px; padding-left: 1em }\n/*.post .more { \n\tdisplay: inline-block; border: 1px solid #eee; padding: 10px 25px; -webkit-border-radius: 26px; -moz-border-radius: 26px; -o-border-radius: 26px; -ms-border-radius: 26px; border-radius: 26px ; font-size: 11px; color: #AAA; font-weight: normal; \n\tleft: 50%; position: relative; -webkit-transform: translateX(-50%); -moz-transform: translateX(-50%); -o-transform: translateX(-50%); -ms-transform: translateX(-50%); transform: translateX(-50%) ;\n}*/\n\n.post .more { border: 2px solid #333; padding: 10px 20px; font-size: 15px; margin-top: 30px; display: none; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.post .more .readmore {  }\n.post .more:hover { color: white; -webkit-box-shadow: inset 150px 0px 0px 0px #333; -moz-box-shadow: inset 150px 0px 0px 0px #333; -o-box-shadow: inset 150px 0px 0px 0px #333; -ms-box-shadow: inset 150px 0px 0px 0px #333; box-shadow: inset 150px 0px 0px 0px #333 ; }\n.post .more:active { color: white; -webkit-box-shadow: inset 150px 0px 0px 0px #AF3BFF; -moz-box-shadow: inset 150px 0px 0px 0px #AF3BFF; -o-box-shadow: inset 150px 0px 0px 0px #AF3BFF; -ms-box-shadow: inset 150px 0px 0px 0px #AF3BFF; box-shadow: inset 150px 0px 0px 0px #AF3BFF ; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; border-color: #AF3BFF }\n\n\n\n/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/fonts.css ---- */\n\n\n/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */\n/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */\n\n@font-face {\n    font-family: 'Tinos';\n    src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');\n    font-weight: normal;\n    font-style: normal;\n\n}\n\n\n@font-face {\n\tfont-family: 'Roboto';\n\tfont-style: normal;\n\tfont-weight: 300;\n\tsrc: \n\t\tlocal('Roboto Light'),\n\t\turl(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAEScABMAAAAAdFQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABqAAAABwAAAAcXzC5yUdERUYAAAHEAAAAHgAAACAAzgAER1BPUwAAAeQAAAVxAAANIkezYOlHU1VCAAAHWAAAACwAAAAwuP+4/k9TLzIAAAeEAAAAVgAAAGC3ouDrY21hcAAAB9wAAAG+AAACioYHy/VjdnQgAAAJnAAAADQAAAA0CnAOGGZwZ20AAAnQAAABsQAAAmVTtC+nZ2FzcAAAC4QAAAAIAAAACAAAABBnbHlmAAALjAAAMaIAAFTUMXgLR2hlYWQAAD0wAAAAMQAAADYBsFYkaGhlYQAAPWQAAAAfAAAAJA7cBhlobXR4AAA9hAAAAeEAAAKEbjk+b2xvY2EAAD9oAAABNgAAAUQwY0cibWF4cAAAQKAAAAAgAAAAIAG+AZluYW1lAABAwAAAAZAAAANoT6qDDHBvc3QAAEJQAAABjAAAAktoPRGfcHJlcAAAQ9wAAAC2AAABI0qzIoZ3ZWJmAABElAAAAAYAAAAGVU1R3QAAAAEAAAAAzD2izwAAAADE8BEuAAAAAM4DBct42mNgZGBg4ANiCQYQYGJgBMIFQMwC5jEAAAsqANMAAHjapZZ5bNRFFMff79dtd7u03UNsORWwKYhWGwFLsRBiGuSKkdIDsBg0kRCVGq6GcpSEFINKghzlMDFBVBITNRpDJEGCBlBBRSEQIQYJyLHd/pA78a99fn6zy3ZbykJxXr7zm3nz5s2b7xy/EUtE/FIiY8SuGDe5SvLeeHlhvfQRD3pRFbc9tWy9/ur8evG5JQOP2Hxt8ds7xLJrjO1AmYxUyiyZLQtlpayRmOWx/FbQGmSVWM9aVdZs6z1rk/WZFbU9dtgutIeCsVivND1dsWSG9JAMKZOeMkrCUi756MI6AN0g3Se1ellm6GlqOXpBxuoNmYXGlgn6D/qo9JOA5ksIFOoBKY79K6V4qtC/ZJy2yXNgPJgIKkEVqMbPNHpO14jUgXr6LcK+gbbFoBEsoX0pWE55Bd8W/G8BW9WNboZ+b/KPyWslDy5K9biU6TkZpY6U6ymiLdUv0Vyi9jvt1boT+x9lTmyXzNUhaHKIcqyEaDkLfw8YTQBNDpo2NHmsVjZtrl2u/kZLmDlHaT0BJ1HTZ45+gbdfTSznJVOK4WQkWAAWgiYQQB/EVzAxYhheIvASgZcIvETgJGK8NfDdgN1GsAlsBllYO1g7WDtYO1g7WDrMcAK+a2UA6xci+kp0i0EjWA4s2nMZO6DNrE4zDDbDYDMMNptIHSJ1iNQhUodI3R4DafGzG8JSKEUyRB6VJ+RJGSbDZQSrWsb+KJfR7OAJ8rxUM/Z0xq6Tl6Re3iTyjUS9WezsQ+7e9L7j24G//uznFl2th/WAOrqPNelG0hq5z6Srk6Ub4Kau0Mv6qe7W7ZQPsxIhPcgeX3sPns6DCDjYSX/9rj3/7ka8bbeNGQXHE/UzyZb3Naqtt/W+FAepZ1J3mVOWPoW7ipYzFE8hSiE3Erfcabyo/I+kF7TVzPBMiq6VU3Wr/FGy9F2y1MD5aLfeG7ukh3SKztOQHtOldxmvgTW/3uWKBeLrqifdSuxbPeNypiOTPb/StfqBbgBrYCOIKkifoH6ou3S//oxFky4jLzLWvTSoV/RrU96pR/UY36Mdx9VzerNDbA+b/M8UzXE97TKTYCcvdY079Fxl8v2duY3vJb3Y3lvbjK+QWdMjScujKb226ze6V0+AH9gHId3G3ghxPk5yZs+m2BVzo4j+otuYZ3wX5ibGa4uP3R5tYufcaU32pGm7er+ninU2ffVaVz47Mt+tHXstTVvae0Cv3PeYTjqG4n5v927ukWDyTnDucuZXdXEerpqzcsc10D9M3nKnmNPFnZ6n7nOlY/RxrdBhYDA7yovKyx/Mq5N0vr6l67EIaA4ne4k5369QP6Kvpd4r8RRjZ+hP4PPkPrp4i832qOJ/AP1E1+ke7uE9nPDWJJ+Jrx4Cu92zEZtr6m93h6H2O7CDtjENA6eSpZOdzwL/84C8m3g93kuyeVN44C/L1LyIT7J5D3gNqz0SVjloc7lZuAc7/RfC3NHu/+dBU8tP6vORAnN/90poeoM+5H3vIaYsM3omo/oYwfVdgLgpk6+vWxvGSuQWfkuMV4v5+Q1TAaIMIr2ZVYhyIWLzCipijKGIT4qRPvIU4uNFNJz8aaQvL6NSeBqJ+HkjlcHUKCRHnkEKeDGVw9dopJdUIBkyTsbD80TEIy/IFKKoRLJkKpIpVYhHahCvTEPyeGVNJ7oXkX68tuooz0SCvLrqiXCezCeSBbz//bIIyZAGxCOLpRGfS2QpHpYhPlmOZEkT4pcVSJ6sk/XM1325WdKC5JsXnCVbZCtlG75djiSFI9uwkwE37hv6Md6G2cx+NJYVzKs3MxtPlJOQ/sxtqjzEO7FaBpk5PMIMZtKznvgGm/hKiKsJPjcw3oj/AIgWgIQAAAB42mNgZGBg4GLQYdBjYHJx8wlh4MtJLMljkGBgAYoz/P8PJBAsIAAAnsoHa3jaY2BmvsGow8DKwMI6i9WYgYFRHkIzX2RIY2JgYABhCHjAwPQ/gEEhGshUAPHd8/PTgRTvAwa2tH9pDAwcSUzBCgyM8/0ZGRhYrFg3gNUxAQCExA4aAAB42mNgYGBmgGAZBkYgycDYAuQxgvksjBlAOozBgYGVQQzI4mWoY1jAsJhhKcNKhtUM6xi2MOxg2M1wkOEkw1mGywzXGG4x3GF4yPCS4S3DZ4ZvDL8Y/jAGMhYyHWO6xXRHgUtBREFKQU5BTUFfwUohXmGNotIDhv//QTYCzVUAmrsIaO4KoLlriTA3gLEAai6DgoCChIIM2FxLJHMZ/3/9//j/of8H/x/4v+//3v97/m//v+X/pv9r/y/7v/j/vP9z/s/8P+P/lP+9/7v+t/5v/t/wv/6/zn++v7v+Lv+77EHzg7oH1Q+qHhQ/yH6Q9MDu/qf7tQoLIOFDC8DIxgA3nJEJSDChKwBGEQsrGzsHJxc3Dy8fv4CgkLCIqJi4hKSUtIysnLyCopKyiqqauoamlraOrp6+gaGRsYmpmbmFpZW1ja2dvYOjk7OLq5u7h6eXt4+vn39AYFBwSGhYeERkVHRMbFx8QiLIlnyGopJSiIVlQFwOYlQwMFQyVDEwVDMwJKeABLLS52enQZ2ViumVjNyZSWDGxEnTpk+eAmbOmz0HRE2dASTyGBgKgFQhEBcDcUMTkGjMARIAqVuf0QAAAAAEOgWvAGYAqABiAGUAZwBoAGkAagBrAHUApABcAHgAZQBsAHIAeAB8AHAAegBaAEQFEXjaXVG7TltBEN0NDwOBxNggOdoUs5mQxnuhBQnE1Y1iZDuF5QhpN3KRi3EBH0CBRA3arxmgoaRImwYhF0h8Qj4hEjNriKI0Ozuzc86ZM0vKkap36WvPU+ckkMLdBs02/U5ItbMA96Tr642MtIMHWmxm9Mp1+/4LBpvRlDtqAOU9bykPGU07gVq0p/7R/AqG+/wf8zsYtDTT9NQ6CekhBOabcUuD7xnNussP+oLV4WIwMKSYpuIuP6ZS/rc052rLsLWR0byDMxH5yTRAU2ttBJr+1CHV83EUS5DLprE2mJiy/iQTwYXJdFVTtcz42sFdsrPoYIMqzYEH2MNWeQweDg8mFNK3JMosDRH2YqvECBGTHAo55dzJ/qRA+UgSxrxJSjvjhrUGxpHXwKA2T7P/PJtNbW8dwvhZHMF3vxlLOvjIhtoYEWI7YimACURCRlX5hhrPvSwG5FL7z0CUgOXxj3+dCLTu2EQ8l7V1DjFWCHp+29zyy4q7VrnOi0J3b6pqqNIpzftezr7HA54eC8NBY8Gbz/v+SoH6PCyuNGgOBEN6N3r/orXqiKu8Fz6yJ9O/sVoAAAAAAQAB//8AD3jarXwHfBRl+v/7TtuWLbMlm54smwIJJLBLCKGJCOqJgIp6NBEiiUgNiCb0IgiIFU9FkKCABKXNbAIqcoAUC3Y9I6ioh5yaE8RT9CeQHf7P885sCgS4/+/zE7OZzO7O+z79+5QZwpG+hHBjxNsIT0wkX6WkoEfEJCScDKmS+FWPCM/BIVF5PC3i6YhJSmzoEaF4PiwH5KyAHOjLZWiZdIU2Vrzt7Ka+wvsELkmqCKHtRYVdt4BE4FyeSoX6iMiRPKqYCxShTiEh1eSsV7iQaqF5RBWp7FaE4o6dwoVhHy+H5apHH6iorqZf85805OM15wrd6edSAhGJjfSCa1KSp0jhWk4gFiFPMYeoEleg0DpVcNXXii6SBCcFl2qieaoVztjYGdUOS3XslExxjbAHX+fyZYFqoTQgdCfnvz6snaPcl/AK611DiLAGaEgm6fRmEkkCGiK++MRwOBwxARkRsy0OjmsJTTLZ82o4OSU10x9WiaO+xutPSM70h2pFgb3Fu9LS8S1RrK+RLFY7vEWVjAIlqU5NdNUrifomza76iMlszavpbRIsQI9LjYezPjjri8ezPg+c9blUG5yNc9WrAZqndEna2etfp3OJL8+6s9e3p514oCS5argkkwfWZa8SvsIiNZZEMxzEu2qs8TYPXqrG7ouDD7jYq8xevfiKn/Gzz8C3Eti34JrJseukxK6Tip+pSYt9Mh3P871dHI9EumTkQkpqWnr+Bf8pvZNABJ7CgCcAP2Eef8K+IB/wBfigB3+K4K1rqGuwVk/bDRoziHaDl3/9z2ByXjs1YMwA7S14uY92G6y9SVfeQV8bRZ/X2M8o7bo7tDK6En/gPKggqTzfkY9Kj5AO5CkSyQMJKm1BDub6SJ6IPM3LteRFZBCm4g2rKZb6iJyCp2W3BbQ0v0Bx1KnpoKIko05WOXe9ku5SZWB7bkj1guDahhSvSzXDicSQmuWsV/3uerUAxCOngyrHFSteucYmprTJ9BcrZrcSLCZqiii7txPq8CdkwVngQlHYGx8OdSnsnJ2TTws7dykClUyjThrsnB1sI/m88f406vNKJl+wMJ9W8uWHHvvblsd3fPT225vLtu3l+PLnH//bs0ve+PCtj5TS7afoc5L63KqKSQ9f3WfnS2vfcxw65Pr+gLhi96r7py7r3e+V6g1vOXb/3fYxWNCk8z+JC8WDxI7aDdzpTh7S+aN2ctRHBOCImuCor+2amSfY89SucCjb2KHsqKdKjwKF1KkOYIHDpXp13UWFzYDDfDjMd6md4bAtaGlP+O11yO4am5ACRlCsds6HP1Iz89LgD6J27SS71ZT04mI1QYaj1LRiZArwIRyKT6VeKdgmu4gxqCfVGeKhfpp1mfcnrZ43d/Vzc+ZXjbprxNDRJcOG3VXLvXVDtJjOgTeqVsMbo0v0N0qE/gPmbt06d8CcLVvmDJk1a8iAIXPmDGmQhakdzz26euCcrVvnDIy9NXD4jJnDCHiz4ed/El4DvrUhHUlPUkEiKegVMpBx2VJ9xIqM684Di3oxFgVBeYK6eXeCw04utSsc2kGT7C7VB4fxcr16FfxGPmy3ChnZHWRkks8OTHInprZjTOqeLbt3EJM9MbVDZ11rOne5ijJ1ATaAdjgp7QUeDdTEbwrmOGgjV4rgUzkmB/WAHhXBRxiPhj+x1HnzwMiqx18adtsa+lynLpP+0u81bumM2w7d9/Hpyk1rR2y7VisRTVzBtEEPXXW12q3TPSPLJtN7K98YYxvz4l+rNq+dOWzB1TO09OuUMfM+/+th8ZGBt9ZFZlVffw09JpqEzJEruEN9Hr1pYYeSroPGLgAbnCb0IceY387WvbbhsqkiXeCvkVGN3nmauSxb6EOt7+3XThK05Ye1TtxEaSiRiYdQxc0YbAWr87AveQpdpCidSpzsc7mBDdnkYRq/SUp64vDhJ5KkLdoJrqeTjud6l9C/3B39Vdvu1bZHfx1/7RiuM17brXWivza/Nl+n2puu3cUtF7q4nKJwPIHLE1PQ/fiRow8nSS/TeO3EZkmrKOPc9EYv/QvnK7u2JLpXe8qpPRx9bwzbdyo3m78B4oiD3EMgpIKzoQVUcbL9cyB7EczExZy5kp1EIQjnv0NUQvPfQfd+ovP+TPTqDoW4FMdeQaEuhdvLqZwjP58qDnSmVBU58Dc20BQeY6jE/IrIh/ksv+gx2WiOJzWD3iiMNdO+Aa3mm9vq3rvtiHBr6Uw6VVs2t/Re7YuraCft4560PWH77U+WC52EHRBlbyEKKVBMYZXa6hUxBMJD70is4DQpwUPKo6OEsGutY3EcdFwIRSxWfM9igo9ZLXhoJZZY5AW3D6EdXL0clPvTyHT6utZvOjetnH6i5ZdrafSYvofBmkadZBfoTBbuATXG2kxjQDJoUwKSKxY3qszgfhXj4Iv+6pe1E/p1OnHdOBe3Biy3DV5HpVI9/lBFKAAW59XyXtREwB7G3nyd6Ddct9JS/G41vHQk6+G77WIIxl7feICXQAny3nr2o18CsUv10vXr8ftp5x/g/s0wkEwAMiHwgVX1z/lpmKZxoyZEX5gtdTjzKcNMi8G3BA2f3I1EbLiQLMW8MTqVFN3vOpv8LjAi1fCwqk0oRlZ4ZJc7HHInUhcXbMN59PAi695x8ekjR/44feTw/1SqGzZsU6qrt3KFtB9NpCHtA+0H7XXte+0j2omavv799Dd0/Lf/+c+3QMeu82e4DWItyKI7iQjo7zjcEeVcGXsLEO8wsQjACidslkeBC9SiGzNoMxMRMjcLRL6L/rtSNN865Gw/sRvyaDJgLBloToKjiAMptgHFaCRqPF8fiWdXi09CLUvWAZPMABPYpSrBcpIHPyDZQdU8Eh56HLByCrzrSZTdEd5mLQamqDbgj+IsVuLliEQ8xSzIZBvO00T9oI6FNOYefcHJ4h+f7Dr2zGJtMsf93FBJjy6c+OzDGzZPFjw7Gg7vqPyfFVo3sXQEl/rUOyOWrH91JdIx9vxP/GmgIxe0JtIW6RCBDrEtbkkEZkRSkCQvkORlCMObYMmrtce1TYGQakfR5unuACID51L8iDcS4DihADEFnEKUgRBDyXIp6fiuDMdyAaKTiJzOMEscEN4ewYcfYgegjrYsdsQB4FBJVnGxYpeVNgBJ3GpienFL5JEHxsMOGPU5jYxhyCPYJnMsV/7Gs6u27nhp2bI161eueLimnBP/3L3/h3nTliw+d3CP9jNdJC1TXnj62SfL1sxesvbFxdLLx+p23729fc5rc/Z9fQR1ux/IuT/YgpU4yRASscS0qJbYLJwdgDoAZ6lekQAYuwoUS50SF0LlVvhQxMxciFkCJloYPLagN5FRuWyoXLRY4WTFwVSMhmVAkqBnkJjkmPpxax44frwi+h2XKoVpeV++oSGrVHuclpfyvbiJzD9sBZszw77SyX4SSW2UW2qj3FwoN4+tvsaR6jLn1fptqS4Qmd9WzxC8s64myUkceSoHcRxFlOSMAXPmyx1O9OVOh+7Lr9p8ZjH6clFxuhTXXjBixbN351UP/tkVztpqvA6PJy8CrxkPZTwUlEBli4nizacRl8erw2aqmtHTpxYrSaABbtRsB8g3QsxJxRfIFERpyvEgpO5Fi7q4fV5wBtlbufHVy9a+8MITDz8ZGH0ztz+6rkvRwik7jx/9uvYXOl168rkDO9cdHDrMxadOjp4JdeH58+TwUe3PdwjzTyuAV+nMVnPIXSSSgNxKi/knG19f685MQIjoFoE5bZk+J6OrCinJLmSK6gPmtIPfgWTQUMHkTmAampkGGupzAgS0uYE4c7EiyIoJqZE7E9BEvykfAI2UCgYKbo0RQoqak7mCpn3cf3lxenH5wLWf9dg55cDx3w+8o52r3Pv08m0vV03fHuBS6OQG2qtNRklGWsP78weO1H498rn2I23f8PGv/3pxW92cu5guDAAdRV2II51JxIwaik5bJWie9gLFXIfpaixFg8CnOlAHiRk2zRfr0cNKeVOwyE08A/jXT5zNtVXacqn5C/GGsjLtx+gebemMGXQq91dqIoglxwA/7cBPPwlCjnw/ifiQo8nAUQuu2wE4mhPwWYCjObiFjoyjCcBRCR1AJhwkuNQ04KcbDnPxXBwwuBOcyM0ENGnhfckBJ2MxMlx1E3ACObLq5OF3B7caJxXrULKoGZJkNi+AzTfnsKfZ8ZiqRfcuPvn3Xf956N5FL2hnP/hEi1bse27FgbefXnGg3ZYli7aqCxdvpgvm72nXVrl/10cfv36/2rbdnnkHPv3kwGNr1z360JYtXMH8Vavmz6l+HnVqKPjNfxk6BejIGot5LAJkAQcS0qw8cCBBatIpbz0qFIQ/JRBSTV5dp5LRFdhZymV18LpmyVb9XAK6BzUL9Yz4dKIJi5BeAkaRU5RGWQKBuJkzcLNO7FByftenmnb6i4Grr4vvu2jwhgOFNZPe+m3W5uULtmVtX/XIK/zuozRXO6md1QZHtfq09DEZKV9/uHzEGOr9cuOxRSUrP/zytG47GCSCQldWD+nQhCYYIEAsYUbSADshlAAvyBCFpRFR8PCzculSwBX83xBbcARhTo7QDWKyhXQiEROgalXCC1ljAEkxh7D8IeH1CljR4AK0ZMOXcYCY0pbGMJOwAq+u28IMfgn/EVydgFf1UZPPT30D+O7RlRMmcGX099F0xhztlxQpRTs9B/fzFN3Af85vYvQl6UjLqlNnZdQZxKCNUPh5iu/TsJvvQzeMG0dXjRunrzkL1nxHX7OokBYV5lBYeRZXOWFCdAk/YMYs6k4GL+CcqT04mvH0ZjCi65nupJFJJJKMPE2xx9CDrSV6SNfRg5uhB4CiSnIIzaU2zUu6C3lKXCOkYElsXBLoCh8PhuKRVYsLHW18CjpaKe4C8OCgviB42Bh4MAWRqzfzdRtq3l00o1dyBc29Y8JdS+bcD1GHtlkmlLy4+9DmxR9PLRwx6oG7byt/Ztq8h5fed279ypVAzwytu/S5+DAJk2vIFhJxYrXCElaLxHolLaR0KlBzHfXK1QWqD35lFqg8Aq++zCRyIOfO0X2sBMlEP70ydNW+s1P11KGnS+m1FzzLGSVpL6lJSu7ZC+swtPGIhZYcsCCVtgWaA3Jvi4WXM3PzOxV2w+KF5FZNbZAJzlz4TId88NVXFwE7EhINdrhJIIPwEsYYI/3s4mauO8xLzJ70D3AkAMd++EQGofobPWiRh/n3GW76Ga2gi+lS2Vr3wcB75MLnyh5Y4vGf2Dhyaj+OD1lvKnr0RZtbU7Sntb9rI2QPnUhvHlLbK733B3dqC7VRXLHr1lG3P9KZFmQM7PigQr+mGzlJS9WGHNb2lQ0fNfqXgxoNFxZx0X0LR515iy6i27R22jxtkdahfbB/u470Nzp11au3T4UMlsvwJ/0M8oCsXvgG4oEJMqH2us0qfJgFhVrJTCi4JQlxQFwBy21UipHAigVMAPdBPsB7AkAo124KlzXr6Wjp07u5G7WvJVE5exN9WhvHUcg9WBzYA+ssZvmhH9Ycb3gHJ3hBFn8y0Av62XLMCwaYyJ3o/kMAJJje2pz1NaLNYwYDgPMpYHagyG0o/slCKlH9TpYioi+ECJuhY3JIxJojvayA7uUDhbGDPfSl76JzJy7aEP2HNo/Oe+HV6jXaRDqoasurivaBqOzZW74hI+HQwv2flK557IGNpcsWP7RMt+WFENs2g22mkrGGZXqAHk8yg+jxgKsYaIgDPBwn4Lk4CxppGiPNBSS4WPVTsYQYDDaF1HQslrhA+4TkYqRClRJRIeM8cMqUoFeNXODVBUj9UZ+4VOp1o4KF/RLEM7KQ5v72I3V5uPKEd17d88MPe1495C/nPNrP3/+m1XGjT9J4OvqPb6Tte7XDP5z6t3Zk1+vSl+fonehnUD7vg3wsxEM6GtKxxqTjwdDsjdUiFKsLUQHzIz7dfcug+FgzCAB3SU/amSBXq6mNjtDWa79DutXxMPVrP36ufSQq2nNa/evaj1pVKc3/Yfdxms94iesPhfVt5DpjdUtsdQF0Q9RVUeSZKuJGYmk4S9EtgFQUa0jPx40kXE/A9Z89/FMNx7i/R6/hg6JSFj1aFl1fShrXHcXo7q2ve/GaJj3itLamsaDtggX38C801HEHoj1wsbfujt6ur7Uc9OUD0JcMrKmlxfSlFSWpTUhMQ5DJ8uFAK/qCkNMUisQzVYuHNIvZga46aaA6yTKzhwRQHCW5WI2DNNFAmy3Uxyfr6iODMchMg5bTwj9+ohYfNzlp364Dp7T3n3g3S5tNz3XSogc17XVuCMjUQW/9aZe0fLt2/Gvtt+PaVzd3pLPKomevm0mHNfG0nsnyKsOjmHSPoojhWivPuGptkqSN9UcUm15lFljDpFGG2IAJQ64DTK3ge1RUNBwQleit3OazN3FV0RJ9PUi+6M2sBhFoJsPG2gVcDX/ExiseqUT/pH/3FsBmKnzXg3rnaMyNHI25kYVdCpTfHctcWQ5k05Vfz1UcwGsL5CiKu3l+AithZpmTXdj5Fq5843OLNlee3PV+xVS6TKpat32F4Dl38q2fxpXtNcd49jPzjzGeWZp4xtsZz3j0jM7G8ggXwooaUXm7nlFQPaNACsE5+y0U4nQQ2PYW13MxF93ALeIejT7/NrCvhKsSo8XRgMhtiQ421jbB2mIsAuBKBg+lGA8jPNN6XrTEKphMOL49lRwY9dntTfYkdYRryeQ241qmuHAjJbGKJkvsdUaa9AKkKhPGSMUs13BinB0jskmv92F1JcLbHCwKM9ooaoQnhwapySPvWc35JS6xqsIqRb8bHD0u2WA7msiBhjzAzebOakIDjS6Jzm7SzVNMN6+9SDebKyRoo2Dszo7ixt1xLGszG1tSeUtsQ0WootQk76nku0ugowchAJ5Lo8I/z94kHKfnUsG/zgLb//7Cupc5VveyXLHuJdj0uhf4/5ivzSAeNF83+Fssgvlm0Y6UUIF20d7VGs4T7cPK+o8+O3nqHx/9iK4/kY7U1mo/nNS+19bTETTpZ+1bmn7q1AmaoX17QsfvyJu/sfqFh/Rp7g3B/9dabEwHLS1DgS2E0cCJBV4jGqgem9wy8AYDibQp1v7+r3Pn/qUtoHNqt9du1xaISv3efT9G13H7X1n28Gv6Pmadby86gFcesOebSURGXvljvEpDXrVhG/DCBrwuNcngVRBLE17Muh2yjbWjZEiMABXIumalyaBOzVjo5Ux+UxbDaZdg5MTSs4O1P7s/cP0lubleOzP4RP8zqakXs5Qju4CfH4nbALsHSamhbS5d29QgsDQxmbE0EVmayShKAoqSQ0qSnvmlM/SuiCE1C9UgSTfzOFmRgapEomMd5uqV4EVYB6BBvN8Hfp41jZqJYBc9+e+zD85YXJGRNSMrbcsqbSy9++CO7a9oD4nb3j847ZXcNtsWLu07oU1C5oJrFz24KjqJ+3PN4sdXge1gLl8JculAyluv/2GTUU2BUJYi47mUhJYdxvbNOoytNBTN7bGmZ5ODLK/FJmKNw5fVvtUWYmY45AdCfaaWLUQhKKG7HcNN0jZv+Sxy9NQf1HP4nw89yE/6UN12cMc3P/2ufXf0i7VVdIX08voVsyue6dZj77rqT2ZP3yqK0vJdz02b9GTXHu9Vb/2AThp3SEJ/0QFk+BjDx2C1UvN6icKHWEor1aHuR0RWmRUBFEQk1naVsILXlBFiL6CDUKLZKrFScnaHeAPzR9Ws14b+skjPhlTJ8L2KtdFd8lgkdOHFWPUD3SWkLljsZaVwiDONAQfLGtWVX6m1xyq0o//+QTtGP+O/bMja+e6h1/H3zw1R3Q8i7v+Q4Z6AUakkHBs1QKzDAI1KLLGiT5j6w0WI9zMW0B2pkJ9uXxD95xTwcdeOHi3shFBKSTH4fewD+EitXuNRnGF2yQjFAACXjWekUEjVqUuNww4hyl7P4t7485erWVufuBTfXofe/9m5r+rkcaOUmO9Q5L2q2XdGVEzwxuyfb8FqIsSQGpfs9ORF4LVZQbGGM7tklv3t4Exmp0v2NXXlKaxthGziQ8fKvDiQmE6RRP9VFAmlOUETDRbPpJb2UhHtPIV2LpQKqGmG9tAU7bVsKUvbMRXIP/EN/VbwnjvxT/wFvv6OZ589t07nb3fgr8LiTLZh+eYwKwYbcUbPpjiMI4KVxREL1f8PWmh3elpLfoI+S1c9oaXQ049pt2m3c8e4D6LLuUnRUDSNWxCdA2sEYI2dsIYZEbupUYY8LGApUEx1DKFbEambWPQCivUDpBfWooirltG9dP+y6MkKUWn4nG/XMCZ6gkvWaYDEQBjPdCQ/FstjeJXn65sUxaRXqAE0G425cCENYBEk4LuTH9bwBv9xwzp+9gjh57K/noszcMI67W16UpoHdlXIKimA7LGSQvlYnajW5CV2IQ9RDphX7C8+FDMpgB5BOexbR2/45BPtbdOrZWe8ZXDdjucf4MVYP4q07EeBkIMd7+NG3ScqZz6FzxLYQ3+2h15EMRXoRl2A2J/twVQHy9VK+sKSS6VghRTs3RXbjClW8fFB+AcEHfj0U9pf2/6JdKLsz+uxvsQd4RoY/xp7YwbLYC8sfQYt4wfQvGE0d9qBNCntDfjC59F29Pi4cVqKzid6fhU/lWXQSc2wGR40IywM7oXyUxoeK2XfuUPYSfeLB4hA2hC9AcELxIWdRZFxFnLyOAG0Qt9IUdgTvINbeeg+cY+o/YHx927AxG8LAyFq5ZMTemarJIUjAVw9xwoZLhbizBDA+PYBD+JSLNIUMPPGgm2mS7Ghp2cTAECvG09hDTcipOaGQiFI0zGtVzsatn/tb/2Z7SfnC0rqXlFNij8jKAl7d+799XcLs/IEV01iQpInT0l11aSkJoO5w59N5h6Bc8zqExJTUmM1n8SURnvPtLNBFTUNgEnEE8hhzTI+AJbnx1zJLEdszni9xNM5s3usQVYAJt+5iFXAwL36IZAWNp85KITP3E35r0499eDsFydxk6Ztr/nC7pwdZ+3x9uyqbRXTx89/s/1/1u2nGU/XPjht4ZzhVJKkqcNG7Xg5eqJ4QmHRTe1uK9+4dMjk6SOPLWOYZzXEAUlKAE1JJ6MN7GVHhvsA+EjI8BQ8YH01iWJczWAMd+uJgOyqV9wuNQHnwPTujOpG2OPSywh2JDkF3Z2LN0CrzDoNst4zyTF5jPowIiDJtLqyy8Zp+7/66o2KzYV2ue2a+1dXPb969rNZUkK0cvhd2jta1Peb9s2dQ9fRjJGTfzzg+5Dys0Yz3RsNuvMO051RRNeYeNDX+ECsSBkRkBYnYAQnS3edNqRFRz8eoMXjUhNBL+JCaqqM5V0GfRKxACIEWHEuHg7NqcYEjbslDEDMg4Ew7Pf6vCbIvbjRv34Zuf9ebvy2uVurNygVO8ZxlbPXH/0PZ849QTveU7ZOEqUFq878PXfvn0umS5L4aEkpLWDymAx0fGrI404dr+vhGeUhxOQhMHkI5pbyMARhsoGux6SR4EYSnKBvVhmU0ZBGnMko6rBCImYROc0L9LKepU/+8sCUDUUV46xdXr5335eVq6umrcpr9/T0qjX0vI/ytGjUEG7BmR9X3z6CBn478OPYEbRh5H1a9ENGxwig4yOQRzzQMYxEvEiCXTJISMWqm8UrxKpuGc1LPIlG+oO7T7QirLZ7/Swtk1WXjLKw2FGhZEMWhE0rBXz61rH+2YZ4/AHdnEZQ2+63jkeFfVXlVV3DPV+f/67223yOm7Hh0UW1NFr0Iw01fFKW+sofvbrd0rs/bU8nimmP7H4X9KkPEFEjdSB+ciuJxDOrwPgjWQAk4WykHFaJCGoDWCyhQIlnExo+rJWEmk0URuJ9TP8QkSVixJLQJVjYvsN6W6ixAacjtT41654M9A06E8JtSsZSTtMq+cMlVesiVstdkmlWeVVJQ1v+MNMTrT9fB/xNJXlkmlEFDIBmmGFzOpPbmpkb9GIVtT1jcBrsL83FsE9mKMZuNl1WoHYAbqcR3XL9co0g25ONyToTcDwZ0htA/2pbe/OKIFOeIr3a0HqnJ6ZIRw/eu7HIUfrDBwOVPum9H7256oWijeX7j1Y+DyqVm/PM9Kq1hkqVjthy7h8f/5odKM0I7Fi75JahtM2v++vH3UH/GFmpNXygx6YqCEtfgI14yAAD41jDuq9yoq9yNvkqb6N9cyE0cZvhp7CCYvMw1ACmTQy8GfNO4HmD+kyHSa6q7FJbuemVymUzZr6YA27ontET/vFNtJRbrTw7f3xUYrq+BTaVCfthc76x/BWVBAOl0KIB5dQbUM7GBhQsiQ2oLRUVFUK3c2+K5Rs34jXPP6L1p3lwTSdQ2ZUwsaI0BQvAFZdCMc5hT99VoMp2PTMG2ODSpeoOGfVRXpdJrCKUje2Te+2urr6hYyqefzStkAoV2shS0TqzUnjy3MTq7VZTeqxHtQZ4jHNljlhdFOtCIs6X8XYiYvA11Ud4OyvNMFZfuj4ktlofWlM5hy5/mNMG0a/5pVr/h6SEhpH0gKglRF8VOWf0P7CHJr6mkEbo0XppbUuFlHDmR/jOCsgH5oJdZGGuyHCLKwXrQGgWqCJKXBjtRPGB4Wazi2Xp2pHlYkUPVuJng6hY+lRzcDJE1w8lVQZ1UVLQgBVZVuN86IsCLSoyfqY+/guUyNtcoVaMt3XeUjmrOrPT9gVbdlU+MmfZCjed/tjsuU+lCd1q7hxbOXPq/O//E13KTX/7xa1LTElStIKbfuCl+ROj5pjuHwH6Wuh+I3VoAJfXeo9BjE2+SPf9F+n+OFtndbryauWyeXPWBIVufx8z8fPj0Ync8p0rF02K2pnu48xmAuznorkq+v83V8X8OEllXWNS1KIsAhjm8BEqaecOf6Gdrdz9cvWevRs37ubiAqdwsupU4BftQ9rpl13ncZoq8Bo6TaOes1obJYiwN4ylQ4kBa6T6ZuyCWApJQCwAybrtcC5WJGyOaWRO5xpgGrt0AabxGJxrxDSJtCWmKXV22cRAzdRNXdqtmrZ63fqq6c9ka6PELzYOK4lhmttvin7IbRtadmK/7wMq3DtC9/Gj+A+M/d9pZOm4/yYfnwKZg63gAgwA4kaY29K/IxW2RixglplbbwULFGGJs3UsMLm6S9zYiqINkxgWKH+2fbtn7m3EAnfcvuZsNpc/6FbEAj+V/pVzD52infsw5q+554EOF+RcTd5R76vHxYGKyI2tBsizcNrHjf4jjsTuWQAO+3TLMuUwxbzHWVA10Z/ncA2d8kS60K02bky5SSiX5k6O+mC9SYA9VsN6Hci8S9SL6GXrRaT1epHPD7gKC0YOI+80p8vuWjFODuI0mJIlKwmx+hFx+BpH0HUXHBtBb71+xMr1RZ0Bz5vUygVPz16377WPN78yvoyb/My8Bx6Y8tIbe7+sfbN8PKXtpPvGTb35xqmZuQ/NmbVp2O3zAd4PXTjlxv4lWXlPzVtcPXLoDInxPPv8T9wUcRDgl9tIxIM8iItBF1GHLqbm0CXWYYpvHC6Nt7SELtgMRHBAZMWpAxhZnwdrhruyC+Xs16f//POA3qlFme602/OmzgX4Qn3aTyXRq8YNFaWhdsfjz3FvwP5Wgow+F7rpfgwtUy+3SmZjk1iE8l5QhFLsrDDJ/BirQ8msKoklFSqx2kqzqlRRI6rNXlm5eNaStRmV46ydlcpN++hb3L3RZW9unjGe5869qd55N8aN9uBX98N+mtWl6JXrUu1n0dyglE2zZ2mlo4RuDZ/NncvnnXsTvno1IeIBuJ6PfGPMHjmcEIfwojXUhH2GVktT3sbS1L6bfj7dSmnqtxPvtihNWUS9NNXzvVND9XmEOEiD94qKHSead+7bd/IelsuaXDVmkwVy2cbSFfzZLJeFc5jLbufMFptew4J8treVM8HfjmaVLCO51YtYBjc8wI3Yq1FcCF4961A7Kfz93d93ljocnKUdLPulQOp44m6hWzTrjTe4L6NZb77JfXnuTe74669HU4ArIeB/LfCrZd2K/nd1qxCdqz3xCA3SrEe1J+ich7X3tPe4HM6jXUt3Rk9Gj9D3tTCsEQTMfIjJxJiVh2tjh9UeVmVEyfEFyHwgTW4uaJAz0yID4F5Fg4tou2yJXveglpv74HxfD4cjrjBu4MhAMSjAT/P5p88lTlppEcdw4uS/Lme2iDc3bGG61aKehU6IN/139axh3MPRJbwzOoXbM4SfeffQhoVGPauvNoFbKfUkaeRGAuZc63eQRCGPzQhBbLMU1JrZCTajk8wwKHYvIM3NYJT6gZ8ebPpTGY3b4lZFux4OWABjdo23gsQK+ya9rt/3/imrXkmae9/wO+4YXjEv9ZVVU7j0sQ/OPL7pVNGgdoceOz5pbVbOuonHHjuYe1PRyZePzVjK9hrRfqV+ViNLIS1bpa569mOUy8ByI6Xar9LuM33Y9yxA450xGtMKaolOo79AjQcaHQW1ziYa+TrFqvep3QaNfhIbbIjHqKc43KrVzWjsRRmJOkkoXpbH+1g+L5kscytH3nXXyPvmJu14rryionzVK9qu3IOPHStfmxlcO+X44++0G1R0atPxGYvHLp1x7OWTRbo8HqPVQj3vIYnkJoLo3GKtR73iUb+SGLHGXWnM3IHmZCyuJyKIZJNQFuylk0S2W1XywG8eQrTdmCbEEKjHE7+edLHk0fdY1cy/Pjn0qvHFAyaUrJ0+5IkhvSd2HXQP/eKBHTfcWByeV+Kcv+u6QV0Kp4/R9zjjvI3/TswmQTJDr5UoaWE1XqyPBJj7D2QY5RK8OcEJpwWWUQniRRWTDL1vns6yGoyWRgklSa5HKWAJJT0D6MEyl15CqbHaEpP1yFjY2d3yfqymKko8uyUrm5vxwd8rq97l+cYyynhO+MdTlbvf58y5R2hOwldfyu+tblZIWbrP/d1xP80BGvH+wo7sXqJn9fuI1FRIlxJDEQnTeAdfX0toimTPU9xhVn/1hmpsKZIZKAyy+1Nk7DwzdMATnLfgUyzoOxUfYoM2QHCbAoULs5QfFC0ePh3fhgVML346Ppl9Wkfe7no1E6ck0KoTEXmrksMAvWGeybTxjjScKQbJmnBmPtyLFuZc867tH5HXd/F8+dLK2U/Y6D7talM4n6cNg63XXmviFpTRtu/Vf7hV+ttSZY12uEwZv693aanz+0ol1kNaDvYWjxUCR7M6fa1LdhA7G4BzIYIM1Xp97ARAAy+vQwM/wiGkzc7GHSN2NppgtwFhUijiYJmfwwV/eUMMKtsdsVq/r0WtH0jx6bUNcGX4r8MyWk03LtOK6b3acPqiNrxCv8GQThWVaAfu06hctq1M20mvhV86jl8revgs437XHiTWNVeJnWEWvS/WOOeJVeYErNizRjqWzOGvxn5YGBnrW7uVtt0ielbDf1jhHn/+J/EP8QDEHj8g1FV6/FedDmPa0QcHmQwx4gGrvGWCidSG8yyZkAiH4WxemN3wWIAW0oXtIs5F8vTRxwT9Zj2lrUvN18dqO8Jf6SGlowtxbq3EPqkW4e19bWX3DovTx2emhPXx7TzZvV2Kc6eTjrrR6C1kvQnf7NiYMW7NksBLjKdVtC3NoVXaaO0L7bBWchudSAVK6WRtuaZpDdqTNGnHM09uELjhk8ZNmjVz8vgJwznhxSef2cEdod2pot2kHdQOaANphPbQ6rW5dD71Ux/E3PnatorNn1c9JU2ZVD2/cuGLE6ZJT1d9xmQ2k6zle/ObiASZIU65YqA2fs2kOfdoJ6j3HkfsgEv10JnaTG0WnWkcXHB/EWlx9xCoNSkDmf1qyCxEuuNM50VSqwWQgPPNeNdlJyahToD0lbah2sTu7I3ExvstL5BXCCQUDikhFxNLu/YA/FPBVwfbhkJKagux4S2YRSHIA1BsGXh7oTsV9D8HhNcJpwKDxUpYrgUREnxT6Y43GFxGjpfoo+fRRBq7naTMkOYakOYRXZqTIAPj6CQmzai2HKTLPVn1l759e5gtZVbhxqG7tg8aP+Le568kzehA/pY5M/relZY4rn/Xtn18Lt/NuV1uvUF7ju65+frb9L7xNGEXPSK+CRJor1tiLblEj0flMfByen6fTMN+ftqHT/Jn4PtWSWvAa5VoA+hKuKoTpz5MDP7H1SvOWIBnd6uY6motumgsLpU37s5m96dIRL8P2CTrFVU9ySoKG/OWJcNmDh6bekfcoNFVT2qrenYv7mCe29syaPDwiUw/F4B+DojpZxE6Kh/Dk/BrAfVqJ+6hOdqRTxqP1tKFdJG2yKMtajzQ50vZHKspnc2xui47ySoX6Gltq5OsvAf4c9E4axEyrPlMKyU68/SZmaGwLq56xclF+UqTi+6LJhcpbqjZ+GL0XX0vxhCj5DOkiLw8BC8FsBeBmEkWiYgYaSQG7ywFiljHCj7YDjaLLKE31MFGAecdwqveUWlc7sxPxoAcr88tmTqzulIG6dnq5FKgtcpSm9g90YKN3RN9heElRuelJ5joZNzgFeeYuC90dgjGvpONe7+DpKyVnWNJLCOspkL8CoRikMogIwVcS7oewdIZwKoN6n8Fm0hEXJWRjiTKCbYrkxiLepemcjbGwysSyeezgMnpsyMgbxmQRffWpkf8rU2PJBhZe8Tp9hUXtz5BwqTRcozkLRTARcMkYodG/eON/YA/gMwukZRcvCMcZ4kPqx5gOD4dIqn59tCX+3QW+9ica22i/ldi09YRo8djrcwpXWLjMR632PtnyNaLtz4/hjtYv1v8GvQbrI/8j37Xl+IP6zO6mdb6iKux490uzRXreHdi2w/A9gMXd7wDLtxtREjKwY435nq+kBq6oOOdkC8oSXtF1Y8db1+zjrfPVRPv8+uPpEhMSvBgB8vfrEoA51jH2xefmKR3vP0J8YmNHe+A0fFOtgFscaVltu+AsEXxymp+AWt+411C3mSj+W33tNL8zr5s55uFkWbtb6m+ttX29x9MaZp64NP3tNYA52+OKRGv9ytBFtivzCQjrtSxzGqtY5ltdCy3Y8cyI/i/7VkyIi/XuDzHqLtk95K+0sw3PwuBVhPfbumb6X/lm5/VfbOwm13uXB/sT5HYcxoSxKMX+uYWVf/L+2bjeRVXKPwzb9B69Z+2ZX75cj0AbkPMJ+v7PdDok8c223EqeohAGO9tUjJCzQj4v/HKlyYu5jFap68L88iXJe+s7kbw/jespYKMPSQB51YvUU1NvEQ1NSnml2WvHwzyv6qoMslcWFa9k6nlRcVV/iddDryxT5x594MkFly4Ux+KIhEyUDuO6TRtPCW28RovT/A24cYEr4mKmuQ4C7yVoL+VUFCbrOd92GdKwCKXLOm3J1yRtJhcLqBuIvPlFxEn9GZSiMX9UUzHAiSHXN8qYmnbmlW0M6xiByKWNsFsfYRYzcy64uQ18xTBInilwUtH91/qFvG/l/1KzU9w2uEpVw7zNiqCvCQq6E7EsB/JcjFtLSz+8rShxbdC26XtozltrdvISy3puqyxfN6Sphhm6A+YwU9ScSb/YhST1hqKSTesZTugmITEFKQnTlaTki8HaAwqWuKa61vs/mKUMLL5jpntCFbxNMHKYjr2dC5h5RmXsPKAse9asPKkNGPbDtz25c2huRguMIlvW1JwsW2ktGA6Jc8Lx7l3xTqIRHns2Scie76YLOjBCJJH0UvMYLTWWKlfv3eosCgMiXCO6fnvSr4vr94gHPcd/dbNxiTA920SltKz4iesDnAjwYK3XgxWfAW1vJFGJsQy/CQ9wzfSd3wmDoZudxz4BwuPrPBByg6JZVO11dfsKUh6dN5017V9S0b3u65kYGF2VjiclV0otu83Gk6MGHFdTudw27aFXZDWMuEUdx5ipAd3BdhMEtmwBi/G+vO1Hj2t9TAx1Vr1cgJrbeHUGc9G59i8EClWeZeRM+q7aioAI2gqmzD46vWF+X1umnTLDSu7FPQW6e33Tbq+yDtk2qRru1y+jvK/f+9FbqvwHST7PPCddRv4en2ItmnqFb7yotCL21qG87FLuK3i3it+fonY1fj8cCFEZfZco8Zn1MSeakTY4Dt7Ro2o3x7Dvu0J877hk6+7SghtpV21t7fq+7zMdS7zrJvhV1VMhi923FGjvW9c53wHKlH+v76Onz3+bnjnijGfUut7+zS8LwP2wpmNZ+z1YRZw0RP2dNoU0cUqKDbjLiCDTEWS2egGu+k0RnK4kfB5zYg3WKCvab/8msYt7bHH+RlrGqRgeUUqVqzslqiWz/ZDJm1vxiiDXTgT0oX+Qd3/V2vqrDTWDFeO2di5cswhmrN9m/YpfAde0Z/jPS93s+cJYSWmn1EREczhMD4KQBUtoVCzpwvFxZ4uZJSJ8UkHism4w87beBegAQXwZ9dSKi8l55euZ//pOjGBrKUNrIYUIFQxxVyYTZ8XN8cEJ+jCYrXPCReVPOE6pXCd31teR+FCxqWarkPxOkapqrSVyhTb002Asd4TD4KHhXwyBwnOMB6dptjCqszjhGItoTlWO8Na2PpIxmcpshP4GEUeM8YaR44VeyHtC5TcOpWTsP4JMvImABdTc7F+lIodjvhQJJc9zSWXWLAThLVRlGOHZg9pseNDWuzGQ1p+nfzGNL197WAPabFjr3rn6bq951j6aXPVxEFamKe4XDVOlwPST/izWfoJ5zD9hICGqactzulq1o/OYNVWfbQyiOOV5ILxSvavecbVk9700ksvUedXxZN7W7pM6br5bS4YPYo/724qLu9s6XJf96+0U5yvbGNZ1mkadDnHuTw/vpUDf3rePCHLY50u2uZ3jx6HRvHPCNew+3X8pFKvjELOh0+w1MMR3/iAL3zWjtnpgfScRSapzng+W+t38qArAA2o9evRy+/C2bpaZ1P0ciG6tdoNPBVgD+iB7M0D/+Aohw/yJnkUnbfiBtpx5CZp65C/SM+HX5TE8f36ae3pP7T2XKI2lFZHf6BzqTaPPka1qUyPEPh1Zc/UIJ3kgIzH597+f+LPPhMAAHjaY2BkYGAAYqY1CuLx/DZfGeQ5GEDgHDPraRj9v/efIdsr9gQgl4OBCSQKAP2qCgwAAAB42mNgZGDgSPq7Fkgy/O/9f4rtFQNQBAUsBACcywcFAHjaNZJNSFRRGIafc853Z2rTohZu+lGiAknINv1trKZFP0ZWmxorNf8ycVqMkDpQlJQLIxCCEjWzRCmScBEExmyCpEXRrqBlizLJKGpr771Ni4f3fOec7573e7l+kcwKwP0s8ZYxf4Qr9of9luNytECXLZJ19eT9VQb9IKtDC+usn8NugBP+ENXuK1OhivX2mJvqmRM50S4OiBlxV9SKZnHKzTLsntNhZdrr445tohAmqEsfpdeWKbffFKMK+qMaijYiRlX3MBRNU/SVfLQ2jkdrtb+DYmpJZzOiiYL9kp6nEGXk4Z3eeklVdJYpW6I8Xcku+8Ie+0SFzXPOfeNh2MI2KeEktSGP8wc5Y7W0WZ5ReWqU5mwD9f4B+6xb6zxj7j1P3eflW+E79+N1ukyzaV9kkz71+Beq19Dlp9msejgssDW1ir3S7WKjOO0fkXGvmJWujHq5HWdvWc0/pNxfUxWKTKRauBgm6YszTnXQ6mvI615TGOdaktNIksebePYEzZrMG88g326eeyVfMcMxSU6qk3uxt0uMy8OTUKA1PIN0g/Ioqe/W//BB7P4Hi9IeabvO5Ok/0Q0mU9cZcJ36T2IayfpmcUHU6a0K5uI+30inaIm/adUcsx802E74C0holcIAAAB42mNgYNCBwjCGPsYCxj9MM5iNmMOYW5g3sXCx+LAUsPSxrGM5xirE6sC6hM2ErYFdjL2NfR+HA8cWjjucPJwqnG6ccZzHuPq4DnHrcE/ivsTDx+PCs4PnAy8fbxDvBN5tfGx8TnxT+G7w2/AvEZAT8BPoEtgkaCWYIzhH8JTgNyEeIRuhOKEKoRnCQcLbRKRE6kTuieqJrhH9IiYnFie2QGyXuJZ4kfgBCQWJFok9knaSfZLXJP9JTZM6Ic0ibSTdIb1E+peMDxDuk3WQXSJ7Ra5OboHcOvks+Qny5+Q/KegplCjMU/ilmKO4RUlA6Zqyk3KO8hEVE5UOlW+qKarn1NTUOtQ2qf1Td8EBg9QT1PPU29TnqR9Sf6bBoeGkUaOxTeODxgdNEU0rIPymFaeVBQDd1FqqAAAAAQAAAKEARAAFAAAAAAACAAEAAgAWAAABAAFRAAAAAHjadVLLSsNQED1Jq9IaRYuULoMLV22aVhGJIBVfWIoLLRbETfqyxT4kjYh7P8OvcVV/QvwUT26mNSlKuJMzcydnzswEQAZfSEBLpgAc8YRYg0EvxDrSqApOwEZdcBI5vAleQh7vgpcZnwpeQQXfglMwNFPwKra0vGADO1pF8Bruta7gddS1D8EbMPSs4E2k9W3BGeT0Gc8UWf1U8Cds/Q7nGGMEHybacPl2iVqMPeEVHvp4QE/dXjA2pjdAh16ZPZZorxlr8vg8tXn2LNdhZjTDjOQ4wmLj4N+cW9byMKEfaDRZ0eKxVe092sO5kt0YRyHCEefuk81UPfpkdtlzB0O+PTwyNkZ3oVMr5sVvgikNccIqnuL1aV2lM6wZaPcZD7QHelqMjOh3WNXEM3Fb5QRaemqqx5y6y7zQi3+TZ2RxHmWqsFWXPr90UOTzoh6LPL9cFvM96i5SeZRzwkgNl+zhDFe4oS0I5997/W9PDXI1ObvZn1RSHA3ptMpeBypq0wb7drivfdoy8XyDP0JQfA542m3Ou0+TcRTG8e+hpTcol9JSoCqKIiqI71taCqJCtS3ekIsWARVoUmxrgDaFd2hiTEx0AXVkZ1Q3Edlw0cHEwcEBBv1XlNLfAAnP8slzknNyKGM//56R5Kisg5SJCRNmyrFgxYYdBxVU4qSKamqoxUUdbjzU46WBRprwcYzjnKCZk5yihdOcoZWztHGO81ygnQ4u0sklNHT8dBEgSDcheujlMn1c4SrX6GeAMNe5QYQoMQa5yS1uc4e7DHGPYUYYZYz7PCDOOA+ZYJIpHvGYJ0wzwywJMfOK16zxjlXeSzkrvOUvH/jBHD/5RYrfpMmQY5kCz3nBS7GIVWxiZ4c/7IpDKqRSnFIl1VIjteKSOnGLR+rFyyc2+MIW3/jMJt/5KA1s81UapYk34rOk5gu5tG41FjOapkVKhjVlxDmcNhZTibyxMJ8wlp3ZQy1+qBkHW3Hfv3dQqSv9yi5lQBlUditDyh5lrzJcUld3dd3xNJMy8nPJxFK6NPLHSgZj5qiRzxZLdO+P/+/adfZ42j3OKRLCQBAF0Bkm+0JWE0Ex6LkCksTEUKikiuIGWCwYcHABOEQHReE5BYcJHWjG9fst/n/w/gj8zGpwlk3H+aXtKks1M4jbGvIVHod2ApZaNwyELEGoBRiyvItipL4wEcaUYMnyyUy+ZWQbn9ab4CDsF8FFODeCh3CvBB/hnQgBwq8IISL4V40RofyBQ0TTUkwj7OhEtUMmyHSjGSOTuWY2rI32PdNJPiQZL3TSQq4+STRSagAAAAFR3VVMAAA=) format('woff'),\n\t\turl('../font/Roboto-Light.woff') format('woff');\n}\n\n@font-face {\n  font-family: 'Roboto';\n  font-style: normal;\n  font-weight: bold;\n  src: \n  \tlocal('Roboto Medium'), \n  \turl(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'),\n  \turl(../font/Roboto-Medium.woff) format('woff');\n}\n\n\n\n/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/github.css ---- */\n\n\n/*\n\ngithub.com style (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.hljs {\n  display: block;\n  overflow-x: auto;\n  padding: 0.5em;\n  color: #333;\n  background: #f8f8f8;\n  -webkit-text-size-adjust: none;\n}\n\n.hljs-comment,\n.diff .hljs-header,\n.hljs-javadoc {\n  color: #998;\n  font-style: italic;\n}\n\n.hljs-keyword,\n.css .rule .hljs-keyword,\n.hljs-winutils,\n.nginx .hljs-title,\n.hljs-subst,\n.hljs-request,\n.hljs-status {\n  color: #333;\n  font-weight: bold;\n}\n\n.hljs-number,\n.hljs-hexcolor,\n.ruby .hljs-constant {\n  color: #008080;\n}\n\n.hljs-string,\n.hljs-tag .hljs-value,\n.hljs-phpdoc,\n.hljs-dartdoc,\n.tex .hljs-formula {\n  color: #d14;\n}\n\n.hljs-title,\n.hljs-id,\n.scss .hljs-preprocessor {\n  color: #900;\n  font-weight: bold;\n}\n\n.hljs-list .hljs-keyword,\n.hljs-subst {\n  font-weight: normal;\n}\n\n.hljs-class .hljs-title,\n.hljs-type,\n.vhdl .hljs-literal,\n.tex .hljs-command {\n  color: #458;\n  font-weight: bold;\n}\n\n.hljs-tag,\n.hljs-tag .hljs-title,\n.hljs-rules .hljs-property,\n.django .hljs-tag .hljs-keyword {\n  color: #000080;\n  font-weight: normal;\n}\n\n.hljs-attribute,\n.hljs-variable,\n.lisp .hljs-body {\n  color: #008080;\n}\n\n.hljs-regexp {\n  color: #009926;\n}\n\n.hljs-symbol,\n.ruby .hljs-symbol .hljs-string,\n.lisp .hljs-keyword,\n.clojure .hljs-keyword,\n.scheme .hljs-keyword,\n.tex .hljs-special,\n.hljs-prompt {\n  color: #990073;\n}\n\n.hljs-built_in {\n  color: #0086b3;\n}\n\n.hljs-preprocessor,\n.hljs-pragma,\n.hljs-pi,\n.hljs-doctype,\n.hljs-shebang,\n.hljs-cdata {\n  color: #999;\n  font-weight: bold;\n}\n\n.hljs-deletion {\n  background: #fdd;\n}\n\n.hljs-addition {\n  background: #dfd;\n}\n\n.diff .hljs-change {\n  background: #0086b3;\n}\n\n.hljs-chunk {\n  color: #aaa;\n}\n\n\n\n/* ---- data/1Hb9rY98TNnA6TYeozJv4w36bqEiBn6x8Y/css/icons.css ---- */\n\n\n.icon { display: inline-block; vertical-align: text-bottom; background-repeat: no-repeat; }\n.icon-profile { font-size: 6px; top: 0em; -webkit-border-radius: 0.7em 0.7em 0 0; -moz-border-radius: 0.7em 0.7em 0 0; -o-border-radius: 0.7em 0.7em 0 0; -ms-border-radius: 0.7em 0.7em 0 0; border-radius: 0.7em 0.7em 0 0 ; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }\n.icon-profile:before { position: absolute; content: \"\"; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; -ms-border-radius: 50%; border-radius: 50% ; background: #FFFFFF; } \n\n.icon-comment { width: 16px; height: 10px; -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; background: #B10DC9; margin-top: 0px; display: inline-block; position: relative; top: -2px; }\n.icon-comment:after { left: 9px; border: 2px solid transparent; border-top-color: #B10DC9; border-left-color: #B10DC9; background: transparent; content: \"\"; display: block; margin-top: 10px; width: 0px; margin-left: 7px; }\n\n.icon-edit { \n\twidth: 16px; height: 16px; background-repeat: no-repeat; background-position: 20px center;\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAOVBMVEUAAAC9w8e9w8e9w8e9w8e/xMi9w8e9w8e+w8e9w8e9w8e9w8e9w8e9w8e9w8e+w8e/xMi9w8e9w8fvY4+KAAAAEnRSTlMASPv3WQbwOTCkt4/psX4YDMWr+RRCAAAAUUlEQVQY06XLORKAMAxDUTs7kA3d/7AYGju0UfffjIgoHkxm0vB5bZyxKHx9eX0FJw0Y4bcXKQ4/CTtS5yqp5GFFOjGpVGl00k1pNDIb3Nv9AHC7BOZC4ZjvAAAAAElFTkSuQmCC+d0ckOwyAMRVGHUOO0gUyd+P8f7WApz4Iki9wFmyOEATrXLZcFp5LrGogPOxKp6zfFf9fZ1/I/cY7YZSS3U6S3XFZJmGBwL+FuJX/F1K0wUUlZyZGlXgXESthTEs4B8fh7xoVUDPGYJnsfkCRarKAgz8cAKbpD6pqDPz3XB8K6HdUEeN9NAAAAAElFTkSuQmCC);\n}\n.icon-reply {\n\twidth: 16px; height: 16px;\n\tbackground-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAIVBMVEUAAABmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYs5FxxAAAAC3RSTlMAgBFwYExAMHgoCDJmUTYAAAA3SURBVAjXY8APGGEMQZgAjCEoKBwEEQCCAoiIh6AQVM1kMaguJhGYOSJQjexiUMbiAChDCclCAOHqBBdHpwQTAAAAAElFTkSuQmCC);\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/data.json",
    "content": "{\n\t\"title\": \"ZeroBlog\",\n\t\"description\": \"Demo for decentralized, self publishing blogging platform.\",\n\t\"links\": \"- [Source code](https://github.com/HelloZeroNet)\\n- [Create new blog](?Post:3:How+to+have+a+blog+like+this)\",\n\t\"next_post_id\": 42,\n\t\"demo\": false,\n\t\"modified\": 1433033806,\n\t\"post\": [\n\t\t{\n\t\t\t\"post_id\": 41,\n\t\t\t\"title\": \"Changelog: May 31, 2015\",\n\t\t\t\"date_published\": 1433033779.604,\n\t\t\t\"body\": \" - rev194\\n - Ugly OpenSSL memory leak fix\\n - Added Docker and Vargant files (thanks to n3r0-ch)\\n\\nZeroBlog\\n - Comment editing, Deleting, Replying added\\n\\nNew official site: http://zeronet.io/\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 40,\n\t\t\t\"title\": \"Trusted authorization providers\",\n\t\t\t\"date_published\": 1432549828.319,\n\t\t\t\"body\": \"What is it good for?\\n\\n - It allows you to have multi-user sites without need of a bot that listen to new user registration requests.\\n - You can use the same username across sites\\n - The site owner can give you (or revoke)  permissions based on your ZeroID username\\n\\nHow does it works?\\n\\n - You visit an authorization provider site (eg zeroid.bit)\\n - You enter the username you want to register and sent the request to the authorization provider site owner (zeroid supports bitmessage and simple http request).\\n - The authorization provider process your request and it he finds everything all right (unique username, other anti-spam methods) he sends you a certificate for the username registration.\\n - If a site trust your authorization provider you can post your own content (comments, topics, upvotes, etc.) using this certificate without ever contacting the site owner.\\n\\nWhat sites currently supports ZeroID?\\n\\n - You can post comments to ZeroBlog using your ZeroID\\n - Later, if everyone is updated to 0.3.0 a new ZeroTalk is also planned that supports ZeroID certificates\\n\\nWhy is it necessary?\\n\\n - To have some kind of control over the users of your site. (eg. remove misbehaving users)\\n\\nOther info\\n\\n - ZeroID is a standard site, anyone can clone it and have his/her own one\\n - You can stop seeding ZeroID site after you got your cert\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 39,\n\t\t\t\"title\": \"Changelog: May 25, 2015\",\n\t\t\t\"date_published\": 1432511642.167,\n\t\t\t\"body\": \"- Version 0.3.0, rev187\\n- Trusted authorization provider support: Easier multi-user sites by allowing site owners to define tusted third-party user certificate signers. (more info about it in the next days)\\n- `--publish` option to siteSign to publish automatically after the new files signed.\\n- `cryptSign` command line command to sign message using private key.\\n- New, more stable OpenSSL layer that also works on OSX.\\n- New json table format support.\\n- DbCursor SELECT parameters bugfix.\\n- Faster multi-threaded peer discovery from trackers.\\n- New http trackers added.\\n- Wait for dbschema.json file to execute query.\\n- Handle json import errors.\\n- More compact json writeJson storage command output.\\n- Workaround to make non target=_top links work.\\n- Cleaner UiWebsocket command router.\\n- Notify other local users on local file changes.\\n- Option to wait file download before execute query.\\n- fileRules, certAdd, certSelect, certSet websocket API commands.\\n- Allow more file errors on big sites.\\n- On stucked downloads skip worker's current file instead of stopping it.\\n- NoParallel parameter bugfix.\\n- RateLimit interval bugfix.\\n- Updater skips non-writeable files.\\n- Try to close OpenSSL dll before update.\\n\\nZeroBlog:\\n- Rewritten to use SQL database\\n- Commenting on posts (**Please note: The comment publishing and distribution can be slow until most of the clients is not updated to version 0.3.0**)\\n\\n![comments](data/img/zeroblog-comments.png)\\n\\nZeroID\\n- Sample Trusted authorization provider site with Bitmessage registration support\\n\\n![comments](data/img/zeroid.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 38,\n\t\t\t\"title\": \"Status report: Trusted authorization providers\",\n\t\t\t\"date_published\": 1431286381.226,\n\t\t\t\"body\": \"Currently working on a new feature that allows to create multi-user sites more easily. For example it will allows us to have comments on ZeroBlog (without contacting the site owner).\\n\\nCurrent status:\\n\\n - Sign/verification process: 90%\\n - Sample trusted authorization provider site: 70%\\n - ZeroBlog modifications: 30%\\n - Authorization UI enhacements: 10%\\n - Total progress: 60%\\n \\nEta.: 1-2weeks\\n\\n### Update: May 18, 2015:\\n\\nThings left:\\n - More ZeroBlog modifications on commenting interface\\n - Bitmessage support in Sample trusted authorization provider site\\n - Test everything on multiple platform/browser and machine\\n - Total progress: 80%\\n\\nIf no major flaw discovered it should be out this week.\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 37,\n\t\t\t\"title\": \"Changelog: May 3, 2015\",\n\t\t\t\"date_published\": 1430652299.794,\n\t\t\t\"body\": \" - rev134\\n - Removed ZeroMQ dependencies and support (if you are on pre 0.2.0 version please, upgrade)\\n - Save CPU and memory on file requests by streaming content directly to socket without loading to memory and encoding with msgpack.\\n - Sites updates without re-download all content.json by querying the modified files from peers.\\n - Fix urllib memory leak\\n - SiteManager testsuite\\n - Fix UiServer security testsuite\\n - Announce to tracker on site resume\\n\\nZeroBoard:\\n\\n - Only last 100 messages loaded by default\\n - Typo fix\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 36,\n\t\t\t\"title\": \"Changelog: Apr 29, 2015\",\n\t\t\t\"date_published\": 1430388168.315,\n\t\t\t\"body\": \" - rev126\\n - You can install the \\\"127.0.0.1:43110-less\\\" extension from [Chrome Web Store](https://chrome.google.com/webstore/detail/zeronet-protocol/cpkpdcdljfbnepgfejplkhdnopniieop). (thanks to g0ld3nrati0!)\\n - You can disable the use of openssl using `--use_openssl False`\\n - OpenSSL disabled on OSX because of possible segfault. You can enable it again using `zeronet.py --use_openssl True`,<br> please [give your feedback](https://github.com/HelloZeroNet/ZeroNet/issues/94)!\\n - Update on non existent file bugfix\\n - Save 20% memory using Python slots\\n\\n![Memory save](data/img/slots_memory.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 35,\n\t\t\t\"title\": \"Changelog: Apr 27, 2015\",\n\t\t\t\"date_published\": 1430180561.716,\n\t\t\t\"body\": \" - Revision 122\\n - 40x faster signature verification by using OpenSSL if available\\n - Added OpenSSL benchmark: beat my CPU at http://127.0.0.1:43110/Benchmark :)\\n - Fixed UiServer socket memory leak\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 34,\n\t\t\t\"title\": \"Slides about ZeroNet\",\n\t\t\t\"date_published\": 1430081791.43,\n\t\t\t\"body\": \"Topics:\\n - ZeroNet cryptography\\n - How site downloading works\\n - Site updates\\n - Multi-user sites\\n - Current status of the project / Future plans\\n\\n<a href=\\\"https://docs.google.com/presentation/d/1_2qK1IuOKJ51pgBvllZ9Yu7Au2l551t3XBgyTSvilew/pub?start=false&loop=false&delayms=3000&slide=id.g9a1cce9ee_0_4\\\"><img src=\\\"data/img/slides.png\\\"/></a>\\n\\n[Any feedback is  welcome!](http://127.0.0.1:43110/Talk.ZeroNetwork.bit/?Topic:18@2/Presentation+about+how+ZeroNet+works) \\n\\nThanks! :)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 33,\n\t\t\t\"title\": \"Changelog: Apr 24, 2014\",\n\t\t\t\"date_published\": 1429873756.187,\n\t\t\t\"body\": \" - Revision 120\\n - Batched publishing to avoid update flood: Only send one update in every 7 seconds\\n - Protection against update flood by adding update queue: Only allows 1 update in every 10 second for the same file\\n - Fix stucked notification icon\\n - Fix websocket error when writing to not-owned sites\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 32,\n\t\t\t\"title\": \"Changelog: Apr 20, 2014\",\n\t\t\t\"date_published\": 1429572874,\n\t\t\t\"body\": \" - Revision 115\\n - For faster pageload times allow browser cache on css/js/font files\\n - Support for experimental chrome extension that allows to browse zeronet sites using `http://talk.zeronetwork.bit` and/or `http://zero/1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F`\\n - Allow to browse memory content in /Stats\\n - Peers uses Site's logger to save some memory\\n - Give not-that-good peers on initial PEX if required\\n - Allows more than one `--ui_restrict` ip address\\n - Disable ssl monkey patching to avoid ssl error in Debian Jessie\\n - Fixed websocket error when writing not-allowed files\\n - Fixed bigsite file not found error\\n - Fixed loading screen stays on screen even after index.html loaded\\n\\nZeroHello:\\n\\n - Site links converted to 127.0.0.1:43110 -less if using chrome extension\\n\\n![direct domains](data/img/direct_domains.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 31,\n\t\t\t\"title\": \"Changelog: Apr 17, 2014\",\n\t\t\t\"date_published\": 1429319617.201,\n\t\t\t\"body\": \" - Revision 101\\n - Revision numbering between version\\n - Allow passive publishing\\n - Start Zeronet when Windows starts option to system tray icon\\n - Add peer ping time to publish timeout\\n - Passive connected peers always get the updates\\n - Pex count bugfix\\n - Changed the topright button hamburger utf8 character to more supported one and removed click anim\\n - Passive peers only need 3 connection\\n - Passive connection store on tracker bugfix\\n - Not exits file bugfix\\n - You can compare your computer speed (bitcoin crypto, sha512, sqlite access) to mine: http://127.0.0.1:43110/Benchmark :)\\n\\nZeroTalk:\\n\\n - Only quote the last message\\n - Message height bugfix\\n\\nZeroHello:\\n\\n - Changed the burger icon to more supported one\\n - Added revision display\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 30,\n\t\t\t\"title\": \"Changelog: Apr 16, 2015\",\n\t\t\t\"date_published\": 1429135541.581,\n\t\t\t\"body\": \"Apr 15:\\n\\n - Version 0.2.9\\n - To get rid of dead ips only send peers over pex that messaged within 2 hour\\n - Only ask peers from 2 sources using pex every 20 min\\n - Fixed mysterious notification icon disappearings\\n - Mark peers as bad if publish is timed out (5s+)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 29,\n\t\t\t\"title\": \"Changelog: Apr 15, 2015\",\n\t\t\t\"date_published\": 1429060414.445,\n\t\t\t\"body\": \" - Sexy system tray icon with statistics instead of ugly console. (sorry, Windows only yet)\\n - Total sent/received bytes stats\\n - Faster connections and publishing by don't send passive peers using PEX and don't store them on trackers\\n\\n![Tray icon](data/img/trayicon.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 28,\n\t\t\t\"title\": \"Changelog: Apr 14, 2015\",\n\t\t\t\"date_published\": 1428973199.042,\n\t\t\t\"body\": \" - Experimental socks proxy support (Tested using Tor)\\n - Tracker-less peer exchange between peers\\n - Http bittorrent tracker support\\n - Option to disable udp connections (udp tracker)\\n - Other stability/security fixes\\n\\nTo use ZeroNet over Tor network start it with `zeronet.py --proxy 127.0.0.1:9050 --disable_udp`\\n\\nIt's still an experimental feature, there is lot work/fine tuning needed to make it work better and more secure (eg. by supporting hidden service peer addresses to allow connection between Tor clients). \\nIn this mode you can only access to sites where there is at least one peer with peer exchange support. (client updated to latest commit)\\n\\nIf no more bug found i'm going to tag it as 0.2.9 in the next days.\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 27,\n\t\t\t\"title\": \"Changelog: Apr 9, 2015\",\n\t\t\t\"date_published\": 1428626164.266,\n\t\t\t\"body\": \" - Packaged windows dependencies for windows to make it easier to install: [ZeroBundle](https://github.com/HelloZeroNet/ZeroBundle)\\n - ZeroName site downloaded at startup, so first .bit domain access is faster.\\n - Fixed updater bug. (argh)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 26,\n\t\t\t\"title\": \"Changelog: Apr 7, 2015\",\n\t\t\t\"date_published\": 1428454413.286,\n\t\t\t\"body\": \" - Fix for big sites confirmation display\\n - Total objects in memory stat\\n - Memory optimizations\\n - Retry bad files in every 20min\\n - Load files to db when executing external siteSign command\\n - Fix for endless reconnect bug\\n \\nZeroTalk:\\n \\n - Added experimental P2P new bot\\n - Bumped size limit to 20k for every user :)\\n - Reply button\\n\\nExperimenting/researching possibilities of i2p/tor support (probably using DHT)\\n\\nAny help/suggestion/idea greatly welcomed: [github issue](https://github.com/HelloZeroNet/ZeroNet/issues/60)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 25,\n\t\t\t\"title\": \"Changelog: Apr 2, 2015\",\n\t\t\t\"date_published\": 1428022346.555,\n\t\t\t\"body\": \" - Better passive mode by making sure to keep 5 active connections\\n - Site connection and msgpack unpacker stats\\n - No more sha1 hash added to content.json (it was only for backward compatibility with old clients)\\n - Keep connection logger object to prevent some exception\\n - Retry upnp port opening 3 times\\n - Publish received content updates to more peers to make sure the better distribution\\n\\nZeroTalk:  \\n\\n - Changed edit icon to more clear pencil\\n - Single line breaks also breaks the line\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 24,\n\t\t\t\"title\": \"Changelog: Mar 29, 2015\",\n\t\t\t\"date_published\": 1427758356.109,\n\t\t\t\"body\": \" - Version 0.2.8\\n - Namecoin (.bit) domain support!\\n - Possible to disable backward compatibility with old version to save some memory\\n - Faster content publishing (commenting, posting etc.)\\n - Display error on internal server errors\\n - Better progress bar\\n - Crash and bugfixes\\n - Removed coppersurfer tracker (its down atm), added eddie4\\n - Sorry, the auto updater broken for this version: please overwrite your current `update.py` file with the [latest one from github](https://raw.githubusercontent.com/HelloZeroNet/ZeroNet/master/update.py), run it and restart ZeroNet.\\n - Fixed updater\\n\\n![domain](data/img/domain.png)\\n\\nZeroName\\n\\n - New site for resolving namecoin domains and display registered ones\\n\\n![ZeroName](data/img/zeroname.png)\\nZeroHello\\n\\n - Automatically links to site's domain names if its specificed in content.json `domain` field\\n\\n\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 22,\n\t\t\t\"title\": \"Changelog: Mar 23, 2015\",\n\t\t\t\"date_published\": 1427159576.994,\n\t\t\t\"body\": \" - Version 0.2.7\\n - Plugin system: Allows extend ZeroNet without modify the core source\\n - Comes with 3 plugin:\\n   - Multiuser: User login/logout based on BIP32 master seed, generate new master seed on visit (disabled by default to enable it just remove the disabled- from the directory name)\\n   - Stats: /Stats url moved to separate plugin for demonstration reasons\\n   - DonationMessage: Puts a little donation link to the bottom of every page (disabled by default)\\n - Reworked module import system\\n - Lazy user auth_address generatation\\n - Allow to send prompt dialog to user from server-side\\n - Update script remembers plugins enabled/disabled status\\n - Multiline notifications\\n - Cookie parser\\n\\nZeroHello in multiuser mode:\\n\\n - Logout button\\n - Identicon generated based on logined user xpub address\\n\\n![Multiuser](data/img/multiuser.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 21,\n\t\t\t\"title\": \"Changelog: Mar 19, 2015\",\n\t\t\t\"date_published\": 1426818095.915,\n\t\t\t\"body\": \" - Version 0.2.6\\n - SQL database support that allows easier site development and faster page load times\\n - Updated [ZeroFrame API Reference](http://zeronet.readthedocs.org/en/latest/site_development/zeroframe_api_reference/)\\n - Added description of new [dbschema.json](http://zeronet.readthedocs.org/en/latest/site_development/dbschema_json/) file\\n - SiteStorage class for file operations\\n - Incoming connection firstchar errorfix\\n - dbRebuild and dbQuery commandline actions\\n - [Goals donation page](http://zeronet.readthedocs.org/en/latest/zeronet_development/donate/)\\n\\nZeroTalk\\n\\n - Rewritten to use SQL queries (falls back nicely to use json files on older version)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 20,\n\t\t\t\"title\": \"Changelog: Mar 14, 2015\",\n\t\t\t\"date_published\": 1426386779.836,\n\t\t\t\"body\": \"\\n - Save significant amount of memory by remove unused msgpack unpackers\\n - Log unhandled exceptions\\n - Connection checker error bugfix\\n - Working on database support, you can follow the progress on [reddit](http://www.reddit.com/r/zeronet/comments/2yq7e8/a_json_caching_layer_for_quicker_development_and/)\\n\\n![memory usage](data/img/memory.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 19,\n\t\t\t\"title\": \"Changelog: Mar 10, 2015\",\n\t\t\t\"date_published\": 1426041044.008,\n\t\t\t\"body\": \" - Fixed ZeroBoard and ZeroTalk registration: It was down last days, sorry, I haven't tested it after recent modifications, but I promise I will from now :)\\n - Working hard on documentations, after trying some possibilities, I chosen readthedocs.org: http://zeronet.readthedocs.org\\n - The API reference is now up-to-date, documented demo sites working method and also updated other parts\\n\\n[Please, tell me what you want to see in the docs, Thanks!](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Topic:14@2/New+ZeroNet+documentation)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 18,\n\t\t\t\"title\": \"Changelog: Mar 8, 2015\",\n\t\t\t\"date_published\": 1425865493.306,\n\t\t\t\"body\": \" - [Better uPnp Puncher](https://github.com/HelloZeroNet/ZeroNet/blob/master/src/util/UpnpPunch.py), if you have problems with port opening please try this.\\n\\nZeroTalk:  \\n - Comment upvoting\\n - Topic groups, if you know any other article about ZeroNet please, post [here](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Topics:8@2/Articles+about+ZeroNet)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 17,\n\t\t\t\"title\": \"Changelog: Mar 5, 2015\",\n\t\t\t\"date_published\": 1425606285.111,\n\t\t\t\"body\": \" - Connection pinging and timeout\\n - Request timeout\\n - Verify content at signing (size, allowed files)\\n - Smarter coffeescript recompile\\n - More detailed stats\\n\\nZeroTalk:  \\n - Topic upvote\\n - Even more source code realign\\n\\n![ZeroTalk upvote](data/img/zerotalk-upvote.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 16,\n\t\t\t\"title\": \"Changelog: Mar 1, 2015\",\n\t\t\t\"date_published\": 1425259087.503,\n\t\t\t\"body\": \"ZeroTalk:  \\n - Reordered source code to allow more more feature in the future\\n - Links starting with http://127.0.0.1:43110/ automatically converted to relative links (proxy support)\\n - Comment reply (by clicking on comment's creation date)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 15,\n\t\t\t\"title\": \"Changelog: Feb 25, 2015\",\n\t\t\t\"date_published\": 1424913197.035,\n\t\t\t\"body\": \" - Version 0.2.5\\n - Pure-python upnp port opener (Thanks to sirMackk!)\\n - Site download progress bar\\n - We are also on [Gitter chat](https://gitter.im/HelloZeroNet/ZeroNet)\\n - More detailed connection statistics (ping, buff, idle, delay, sent, received)\\n - First char failed bugfix\\n - Webebsocket disconnect on slow connection bugfix\\n - Faster site update\\n\\n![Progressbar](data/img/progressbar.png)\\n\\nZeroTalk:  \\n\\n - Sort after 100ms idle\\n - Colored usernames\\n - Limit reload rate to 500ms\\n\\nZeroHello\\n\\n - [iframe render fps test](/1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr/test/render.html) ([more details on ZeroTalk](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Topic:7@2/Slow+rendering+in+Chrome))\\n\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 14,\n\t\t\t\"title\": \"Changelog: Feb 24, 2015\",\n\t\t\t\"date_published\": 1424734437.473,\n\t\t\t\"body\": \" - Version 0.2.4\\n - New, experimental network code and protocol\\n - peerPing and peerGetFile commands\\n - Connection share and reuse between sites\\n - Don't retry bad file more than 3 times in 20 min\\n - Multi-threaded include file download\\n - Really shuffle peers before publish\\n - Simple internal stats page: http://127.0.0.1:43110/Stats\\n - Publish bugfix for sites with more then 10 peers\\n\\n_If someone on very limited resources its recommended to wait some time until most of the peers is updates to new network code, because the backward compatibility is a little bit tricky and using more memory._\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 13,\n\t\t\t\"title\": \"Changelog: Feb 19, 2015\",\n\t\t\t\"date_published\": 1424394659.345,\n\t\t\t\"body\": \" - Version 0.2.3\\n - One click source code download from github, auto unpack and restart \\n - Randomize peers before publish and work start\\n - Switched to upnpc-shared.exe it has better virustotal reputation (4/53 vs 19/57)\\n\\n![Autoupdate](data/img/autoupdate.png)\\n\\nZeroTalk:\\n\\n - Topics also sorted by topic creation date\\n\\n_New content and file changes propagation is a bit broken yet. Already working on better network code that also allows passive content publishing. It will be out in 1-2 weeks._\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 12,\n\t\t\t\"title\": \"Changelog: Feb 16, 2015\",\n\t\t\t\"date_published\": 1424134864.167,\n\t\t\t\"body\": \"Feb 16:  \\n - Version 0.2.2\\n - LocalStorage support using WrapperAPI\\n - Bugfix in user management\\n\\nZeroTalk:  \\n - Topics ordered by date of last post\\n - Mark updated topics since your last visit\\n\\n![Mark](data/img/zerotalk-mark.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 11,\n\t\t\t\"title\": \"Changelog: Feb 14, 2015\",\n\t\t\t\"date_published\": 1423922572.778,\n\t\t\t\"body\": \" - Version 0.2.1\\n - Site size limit: Default 10MB, asks permission to store more, test it here: [ZeroNet windows requirement](/1ZeroPYmW4BGwmT6Z54jwPgTWpbKXtTra)\\n - Browser open wait until UiServer started\\n - Peer numbers stored in sites.json for faster warmup\\n - Silent WSGIHandler error\\n - siteSetLimit WrapperAPI command\\n - Grand ADMIN permission to wrapperframe\\n\\nZeroHello:  \\n\\n - Site modify time also include sub-file changes (ZeroTalk last comment)\\n - Better changetime date format\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 10,\n\t\t\t\"title\": \"Changelog: Feb 11, 2015\",\n\t\t\t\"date_published\": 1423701015.643,\n\t\t\t\"body\": \"ZeroTalk:\\n - Link-type posts\\n - You can Edit or Delete your previous Comments and Topics\\n - [Uploaded source code to github](https://github.com/HelloZeroNet/ZeroTalk)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 9,\n\t\t\t\"title\": \"Changelog: Feb 10, 2015\",\n\t\t\t\"date_published\": 1423532194.094,\n\t\t\t\"body\": \" - Progressive publish timeout based on file size\\n - Better tracker error log\\n - Viewport support in content.json and ZeroFrame API to allow better mobile device layout\\n - Escape ZeroFrame notification messages to avoid js injection\\n - Allow select all data in QueryJson\\n\\nZeroTalk:\\n - Display topic's comment number and last comment time (requires ZeroNet today's commits from github)\\n - Mobile device optimized layout\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 8,\n\t\t\t\"title\": \"Changelog: Feb 9, 2015\",\n\t\t\t\"date_published\": 1423522387.728,\n\t\t\t\"body\": \" - Version 0.2.0\\n - New bitcoin ECC lib (pybitcointools)\\n - Hide notify errors\\n - Include support for content.json\\n - File permissions (signer address, filesize, allowed filenames)\\n - Multisig ready, new, Bitcoincore compatible sign format\\n - Faster, multi threaded content publishing\\n - Multiuser, ready, BIP32 based site auth using bitcoin address/privatekey\\n - Simple json file query language\\n - Websocket api fileGet support\\n\\nZeroTalk:  \\n - [Decentralized forum demo](/1TaLk3zM7ZRskJvrh3ZNCDVGXvkJusPKQ/?Home)\\n - Permission request/username registration\\n - Everyone has an own file that he able to modify, sign and publish decentralized way, without contacting the site owner\\n - Topic creation\\n - Per topic commenting\\n\\n![ZeroTalk screenshot](data/img/zerotalk.png)\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 7,\n\t\t\t\"title\": \"Changelog: Jan 29, 2015\",\n\t\t\t\"date_published\": 1422664081.662,\n\t\t\t\"body\": \"The default tracker (tracker.pomf.se) is down since yesterday and its resulting some warning messages. To make it disappear please update to latest version from [GitHub](https://github.com/HelloZeroNet/ZeroNet).\\n\\nZeroNet:\\n- Added better tracker error handling\\n- Updated alive [trackers list](https://github.com/HelloZeroNet/ZeroNet/blob/master/src/Site/SiteManager.py) (if anyone have more, please [let us know](http://www.reddit.com/r/zeronet/comments/2sgjsp/changelog/co5y07h))\\n\\nIf you want to stay updated about the project status: <br>\\nWe have created a [@HelloZeronet](https://twitter.com/HelloZeroNet) Twitter account\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 6,\n\t\t\t\"title\": \"Changelog: Jan 27, 2015\",\n\t\t\t\"date_published\": 1422394676.432,\n\t\t\t\"body\": \"ZeroNet\\n* You can use `start.py` to start zeronet and open in browser automatically\\n* Send timeout 50sec (workaround for some strange problems until we rewrite the network code without zeromq)\\n* Reworked Websocket API to make it unified and allow named and unnamed parameters\\n* Reload `content.json` when changed using fileWrite API command\\n* Some typo fix\\n\\nZeroBlog\\n* Allow edit post on mainpage\\n* Also change blog title in `content.json` when modified using inline editor\\n\\nZeroHello\\n* Update failed warning changed to No peers found when seeding own site.\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 4,\n\t\t\t\"title\": \"Changelog: Jan 25, 2015\",\n\t\t\t\"date_published\": 1422224700.583,\n\t\t\t\"body\": \"ZeroNet\\n- Utf-8 site titles fixed\\n- Changes in DebugMedia merger to allow faster, node.js based coffeescript compiler\\n\\nZeroBlog\\n- Inline editor rewritten to simple textarea, so copy/paste, undo/redo now working correctly\\n- Read more button to folded posts with `---`\\n- ZeroBlog running in demo mode, so anyone can try the editing tools\\n- Base html tag fixed\\n- Markdown cheat-sheet\\n- Confirmation if you want to close the browser tab while editing\\n\\nHow to update your running blog?\\n- Backup your `content.json` and `data.json` files\\n- Copy the files in the `data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8` directory to your site.\\n\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 3,\n\t\t\t\"title\": \"How to have a blog like this\",\n\t\t\t\"date_published\": 1422140400,\n\t\t\t\"body\": \"* Stop ZeroNet\\n* Create a new site using `python zeronet.py siteCreate` command\\n* Copy all file from **data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8** to **data/[Your new site address displayed when executed siteCreate]** directory\\n* Delete **data** directory and rename **data-default** to **data** to get a clean, empty site\\n* Rename **data/users/content-default.json** file to **data/users/content.json**\\n* Execute `zeronet.py siteSign [yoursiteaddress] --inner_path data/users/content.json` to sign commenting rules\\n* Start ZeroNet\\n* Add/Modify content\\n* Click on the `Sign & Publish new content` button\\n* Congratulations! Your site is ready to access.\\n\\n_Note: You have to start commands with `..\\\\python\\\\python zeronet.py...` if you downloaded ZeroBundle package_\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 2,\n\t\t\t\"title\": \"Changelog: Jan 24, 2015\",\n\t\t\t\"date_published\": 1422105774.057,\n\t\t\t\"body\": \"* Version 0.1.6\\n* Only serve .html files with wrapper frame\\n* Http parameter support in url\\n* Customizable background-color for wrapper in content.json\\n* New Websocket API commands (only allowed on own sites):\\n  - fileWrite: Modify site's files  in hdd from javascript\\n  - sitePublish: Sign new content and Publish to peers\\n* Prompt value support in ZeroFrame (used for prompting privatekey for publishing in ZeroBlog)\\n\\n---\\n\\n## Previous changes:\\n\\n### Jan 20, 2014\\n- Version 0.1.5\\n- Detect computer wakeup from sleep and acts as startup (check open port, site changes)\\n- Announce interval changed from 10min to 20min\\n- Delete site files command support\\n- Stop unfinished downloads on pause, delete\\n- Confirm dialog support to WrapperApi\\n\\nZeroHello\\n- Site Delete menuitem\\n- Browser back button doesn't jumps to top\\n\\n### Jan 19, 2014:\\n- Version 0.1.4\\n- WIF compatible new private addresses\\n- Proper bitcoin address verification, vanity address support: http://127.0.0.1:43110/1ZEro9ZwiZeEveFhcnubFLiN3v7tDL4bz\\n- No hash error on worker kill\\n- Have you secured your private key? confirmation\\n\\n### Jan 18, 2014:\\n- Version 0.1.3\\n- content.json hashing changed from sha1 to sha512 (trimmed to 256bits) for better security, keep hasing to sha1 for backward compatiblility yet\\n- Fixed fileserver_port argument parsing\\n- Try to ping peer before asking any command if no communication for 20min\\n- Ping timeout / retry\\n- Reduce websocket bw usage\\n- Separate wrapper_key for websocket auth and auth_key to identify user\\n- Removed unnecessary from wrapper iframe url\\n\\nZeroHello:\\n- Compatiblilty with 0.1.3 websocket changes while maintaining backward compatibility\\n- Better error report on file update fail\\n\\nZeroBoard:\\n- Support for sha512 hashed auth_key, but keeping md5 key support for older versions yet\\n\\n### Jan 17, 2014:\\n- Version 0.1.2\\n- Better error message logging\\n- Kill workers on download done\\n- Retry on socket error\\n- Timestamping console messages\\n\\n### Jan 16:\\n- Version to 0.1.1\\n- Version info to websocket api\\n- Add publisher's zeronet version to content.json\\n- Still chasing network publish problems, added more debug info\\n\\nZeroHello:\\n- Your and the latest ZeroNet version added to top right corner (please update if you dont see it)\\n\"\n\t\t},\n\t\t{\n\t\t\t\"post_id\": 1,\n\t\t\t\"title\": \"ZeroBlog features\",\n\t\t\t\"date_published\": 1422105061,\n\t\t\t\"body\": \"Initial version (Jan 24, 2014):\\n\\n* Site avatar generated by site address\\n* Distraction-free inline edit: Post title, date, body, Site title, description, links\\n* Post format using [markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)\\n* Code block [syntax highlight](#code-highlight-demos) using [highlight.js](https://highlightjs.org/)\\n* Create & Delete post\\n* Sign & Publish from web\\n* Fold blog post: Content after first `---` won't appear at listing\\n* Shareable, friendly post urls\\n\\n\\nTodo:\\n\\n* ~~Better content editor (contenteditable seemed like a good idea, but tricky support of copy/paste makes it more pain than gain)~~\\n* Image upload to post & blog avatar\\n* Paging\\n* Searching\\n* ~~Quick cheat-sheet using markdown~~\\n\\n---\\n\\n## Code highlight demos\\n### Server-side site publishing (UiWebsocket.py):\\n```py\\ndef actionSitePublish(self, to, params):\\n\\tsite = self.site\\n\\tif not site.settings[\\\"own\\\"]: return self.response(to, \\\"Forbidden, you can only modify your own sites\\\")\\n\\n\\t# Signing\\n\\tsite.loadContent(True) # Reload content.json, ignore errors to make it up-to-date\\n\\tsigned = site.signContent(params[0]) # Sign using private key sent by user\\n\\tif signed:\\n\\t\\tself.cmd(\\\"notification\\\", [\\\"done\\\", \\\"Private key correct, site signed!\\\", 5000])  # Display message for 5 sec\\n\\telse:\\n\\t\\tself.cmd(\\\"notification\\\", [\\\"error\\\", \\\"Site sign failed: invalid private key.\\\"])\\n\\t\\tself.response(to, \\\"Site sign failed\\\")\\n\\t\\treturn\\n\\tsite.loadContent(True) # Load new content.json, ignore errors\\n\\n\\t# Publishing\\n\\tif not site.settings[\\\"serving\\\"]: # Enable site if paused\\n\\t\\tsite.settings[\\\"serving\\\"] = True\\n\\t\\tsite.saveSettings()\\n\\t\\tsite.announce()\\n\\n\\tpublished = site.publish(5) # Publish to 5 peer\\n\\n\\tif published>0: # Successfuly published\\n\\t\\tself.cmd(\\\"notification\\\", [\\\"done\\\", \\\"Site published to %s peers.\\\" % published, 5000])\\n\\t\\tself.response(to, \\\"ok\\\")\\n\\t\\tsite.updateWebsocket() # Send updated site data to local websocket clients\\n\\telse:\\n\\t\\tif len(site.peers) == 0:\\n\\t\\t\\tself.cmd(\\\"notification\\\", [\\\"info\\\", \\\"No peers found, but your site is ready to access.\\\"])\\n\\t\\t\\tself.response(to, \\\"No peers found, but your site is ready to access.\\\")\\n\\t\\telse:\\n\\t\\t\\tself.cmd(\\\"notification\\\", [\\\"error\\\", \\\"Site publish failed.\\\"])\\n\\t\\t\\tself.response(to, \\\"Site publish failed.\\\")\\n```\\n\\n\\n### Client-side site publish (ZeroBlog.coffee)\\n```coffee\\n# Sign and Publish site\\npublish: =>\\n\\tif not @server_info.ip_external # No port open\\n\\t\\t@cmd \\\"wrapperNotification\\\", [\\\"error\\\", \\\"To publish the site please open port <b>#{@server_info.fileserver_port}</b> on your router\\\"]\\n\\t\\treturn false\\n\\t@cmd \\\"wrapperPrompt\\\", [\\\"Enter your private key:\\\", \\\"password\\\"], (privatekey) => # Prompt the private key\\n\\t\\t$(\\\".publishbar .button\\\").addClass(\\\"loading\\\")\\n\\t\\t@cmd \\\"sitePublish\\\", [privatekey], (res) =>\\n\\t\\t\\t$(\\\".publishbar .button\\\").removeClass(\\\"loading\\\")\\n\\t\\t\\t@log \\\"Publish result:\\\", res\\n\\n\\treturn false # Ignore link default event\\n```\\n\\n\"\n\t\t}\n\t]\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/optional.txt",
    "content": "hello!"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/test_include/content.json",
    "content": "{\n \"address\": \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\",\n \"files\": {\n  \"data.json\": {\n   \"sha512\": \"369d4e780cc80504285f13774ca327fe725eed2d813aad229e62356b07365906\",\n   \"size\": 505\n  }\n },\n \"inner_path\": \"data/test_include/content.json\",\n \"modified\": 1470340816.513,\n \"signs\": {\n  \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": \"GxF2ZD0DaMx+CuxafnnRx+IkWTrXubcmTHaJIPyemFpzCvbSo6DyjstN8T3qngFhYIZI/MkcG4ogStG0PLv6p3w=\"\n }\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/test_include/data.json",
    "content": "{\n\t\"next_topic_id\": 1,\n\t\"topics\": [],\n\t\"next_message_id\": 5,\n\t\"comments\": {\n\t\t\"1@2\": [\n\t\t\t{\n\t\t\t\t\"comment_id\": 1,\n\t\t\t\t\"body\": \"New user test!\",\n\t\t\t\t\"added\": 1423442049\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"comment_id\": 2,\n\t\t\t\t\"body\": \"test 321\",\n\t\t\t\t\"added\": 1423531445\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"comment_id\": 3,\n\t\t\t\t\"body\": \"0.2.4 test.\",\n\t\t\t\t\"added\": 1424133003\n\t\t\t}\n\t\t]\n\t},\n\t\"topic_votes\": {\n\t\t\"1@2\": 1,\n\t\t\"1@6\": 1,\n\t\t\"1@69\": 1,\n\t\t\"607@69\": 1\n\t},\n\t\"comment_votes\": {\n\t\t\"35@2\": 1,\n\t\t\"7@64\": 1,\n\t\t\"8@64\": 1,\n\t\t\"50@2\": 1,\n\t\t\"13@77\": 1\n\t}\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/content.json",
    "content": "{\n  \"cert_auth_type\": \"web\", \n  \"cert_sign\": \"G4YB7y749GI6mJboyI7cNNfyMwOS0rcVXLmgq8qmCC4TCaRqup3TGWm8hzeru7+B5iXhq19Ruz286bNVKgNbnwU=\", \n  \"cert_user_id\": \"newzeroid@zeroid.bit\", \n  \"files\": {\n    \"data.json\": {\n      \"sha512\": \"2378ef20379f1db0c3e2a803bfbfda2b68515968b7e311ccc604406168969d34\", \n      \"size\": 161\n    }\n  }, \n  \"modified\": 1432554679.913, \n  \"signs\": {\n    \"1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q\": \"GzX/Ht6ms1dOnqB3kVENvDnxpH+mqA0Zlg3hWy0iwgxpyxWcA4zgmwxcEH41BN9RrvCaxgSd2m1SG1/8qbQPzDY=\"\n  }\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/1C5sgvWaSgfaTpV5kjBCnCiKtENNMYo69q/data.json",
    "content": "{\n\t\"next_comment_id\": 2,\n\t\"comment\": [\n\t\t{\n\t\t\t\"comment_id\": 1,\n\t\t\t\"body\": \"Test me!\",\n\t\t\t\"post_id\": 40,\n\t\t\t\"date_added\": 1432554679\n\t\t}\n\t],\n\t\"comment_vote\": {}\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/content.json",
    "content": "{\n \"address\": \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\",\n \"cert_auth_type\": \"web\",\n \"cert_sign\": \"HBsTrjTmv+zD1iY93tSci8n9DqdEtYwzxJmRppn4/b+RYktcANGm5tXPOb+Duw3AJcgWDcGUvQVgN1D9QAwIlCw=\",\n \"cert_user_id\": \"toruser@zeroid.bit\",\n \"files\": {\n  \"data.json\": {\n   \"sha512\": \"4868b5e6d70a55d137db71c2e276bda80437e0235ac670962acc238071296b45\",\n   \"size\": 168\n  }\n },\n \"files_optional\": {\n  \"peanut-butter-jelly-time.gif\": {\n   \"sha512\": \"a238fd27bda2a06f07f9f246954b34dcf82e6472aebdecc2c5dc1f01a50721ef\",\n   \"size\": 1606\n  }\n },\n \"inner_path\": \"data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/content.json\",\n \"modified\": 1470340817.676,\n \"optional\": \".*\\\\.(jpg|png|gif)\",\n \"signs\": {\n  \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": \"G6UOG3ne1hVe3mDGXHnWX8A1vKzH0XHD6LGMsshvNFVXGn003IFNLUL9dlb3XXJf3tyJGZncvGobzNpwBib08QY=\"\n }\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/1CjfbrbwtP8Y2QjPy12vpTATkUT7oSiPQ9/data.json",
    "content": "{\n\t\"next_comment_id\": 2,\n\t\"comment\": [\n\t\t{\n\t\t\t\"comment_id\": 1,\n\t\t\t\"body\": \"hello from Tor!\",\n\t\t\t\"post_id\": 38,\n\t\t\t\"date_added\": 1432491109\n\t\t}\n\t],\n\t\"comment_vote\": {}\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json",
    "content": "{\n \"address\": \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\",\n \"cert_auth_type\": \"web\",\n \"cert_sign\": \"HBsTrjTmv+zD1iY93tSci8n9DqdEtYwzxJmRppn4/b+RYktcANGm5tXPOb+Duw3AJcgWDcGUvQVgN1D9QAwIlCw=\",\n \"cert_user_id\": \"toruser@zeroid.bit\",\n \"files\": {\n  \"data.json\": {\n   \"sha512\": \"4868b5e6d70a55d137db71c2e276bda80437e0235ac670962acc238071296b45\",\n   \"size\": 168\n  }\n },\n \"inner_path\": \"data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/content.json\",\n \"modified\": 1470340818.389,\n \"signs\": {\n  \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": \"G6oCzql6KWKAq2aSmZ1pm4SqvwL3e3LRdWxsvILrDc6VWpGZmVgbNn5qW18bA7fewhtA/oKc5+yYjGlTLLOWrB4=\"\n }\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/1J6UrZMkarjVg5ax9W4qThir3BFUikbW6C/data.json",
    "content": "{\n\t\"next_comment_id\": 2,\n\t\"comment\": [\n\t\t{\n\t\t\t\"comment_id\": 1,\n\t\t\t\"body\": \"hello from Tor!\",\n\t\t\t\"post_id\": 38,\n\t\t\t\"date_added\": 1432491109\n\t\t}\n\t],\n\t\"comment_vote\": {}\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data/users/content.json",
    "content": "{\n \"address\": \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\",\n \"files\": {},\n \"ignore\": \".*\",\n \"inner_path\": \"data/users/content.json\",\n \"modified\": 1470340815.228,\n \"signs\": {\n  \"1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT\": \"G25hsrlyTOy8PHKuovKDRC7puoBj/OLIZ3U4OJ01izkhE1BBQ+TOgxX96+HXoZGme2/P4IdEnYjc1rqIZ6O+nFk=\"\n },\n \"user_contents\": {\n  \"cert_signers\": {\n   \"zeroid.bit\": [ \"1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz\" ]\n  },\n  \"permission_rules\": {\n   \".*\": {\n    \"files_allowed\": \"data.json\",\n    \"files_allowed_optional\": \".*\\\\.(png|jpg|gif)\",\n    \"max_size\": 10000,\n    \"max_size_optional\": 10000000,\n    \"signers\": [ \"14wgQ4VDDZNoRMFF4yCDuTrBSHmYhL3bet\" ]\n   },\n   \"bitid/.*@zeroid.bit\": { \"max_size\": 40000 },\n   \"bitmsg/.*@zeroid.bit\": { \"max_size\": 15000 }\n  },\n  \"permissions\": {\n   \"bad@zeroid.bit\": false,\n   \"nofish@zeroid.bit\": { \"max_size\": 100000 }\n  }\n }\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data-default/data.json",
    "content": "{\n\t\"title\": \"MyZeroBlog\",\n\t\"description\": \"My ZeroBlog.\",\n\t\"links\": \"- [Source code](https://github.com/HelloZeroNet)\",\n\t\"next_post_id\": 1,\n\t\"demo\": false,\n\t\"modified\": 1432515193,\n\t\"post\": [\n\t]\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/data-default/users/content-default.json",
    "content": "{\n  \"files\": {}, \n  \"ignore\": \".*\", \n  \"modified\": 1432466966.003, \n  \"signs\": {\n    \"1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8\": \"HChU28lG4MCnAiui6wDAaVCD4QUrgSy4zZ67+MMHidcUJRkLGnO3j4Eb1N0AWQ86nhSBwoOQf08Rha7gRyTDlAk=\"\n  }, \n  \"user_contents\": {\n    \"cert_signers\": {\n      \"zeroid.bit\": [ \"1iD5ZQJMNXu43w1qLB8sfdHVKppVMduGz\" ]\n    }, \n    \"permission_rules\": {\n      \".*\": {\n        \"files_allowed\": \"data.json\", \n        \"max_size\": 10000\n      }, \n      \"bitid/.*@zeroid.bit\": { \"max_size\": 40000 }, \n      \"bitmsg/.*@zeroid.bit\": { \"max_size\": 15000 }\n    }, \n    \"permissions\": {\n      \"banexample@zeroid.bit\": false, \n      \"nofish@zeroid.bit\": { \"max_size\": 20000 }\n    }\n  }\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/dbschema.json",
    "content": "{\n\t\"db_name\": \"ZeroBlog\",\n\t\"db_file\": \"data/zeroblog.db\",\n\t\"version\": 2,\n\t\"maps\": {\n\t\t\"users/.+/data.json\": {\n\t\t\t\"to_table\": [\n\t\t\t\t\"comment\",\n\t\t\t\t{\"node\": \"comment_vote\", \"table\": \"comment_vote\", \"key_col\": \"comment_uri\", \"val_col\": \"vote\"}\n\t\t\t]\n\t\t},\n\t\t\"users/.+/content.json\": {\n\t\t\t\"to_keyvalue\": [ \"cert_user_id\" ]\n\t\t},\n\t\t\"data.json\": {\n\t\t\t\"to_table\": [ \"post\" ],\n\t\t\t\"to_keyvalue\": [ \"title\", \"description\", \"links\", \"next_post_id\", \"demo\", \"modified\" ]\n\t\t}\n\n\t},\n\t\"tables\": {\n\t\t\"comment\": {\n\t\t\t\"cols\": [\n\t\t\t\t[\"comment_id\", \"INTEGER\"],\n\t\t\t\t[\"post_id\", \"INTEGER\"],\n\t\t\t\t[\"body\", \"TEXT\"],\n\t\t\t\t[\"date_added\", \"INTEGER\"],\n\t\t\t\t[\"json_id\", \"INTEGER REFERENCES json (json_id)\"]\n\t\t\t],\n\t\t\t\"indexes\": [\"CREATE UNIQUE INDEX comment_key ON comment(json_id, comment_id)\", \"CREATE INDEX comment_post_id ON comment(post_id)\"],\n\t\t\t\"schema_changed\": 1426195823\n\t\t},\n\t\t\"comment_vote\": {\n\t\t\t\"cols\": [\n\t\t\t\t[\"comment_uri\", \"TEXT\"],\n\t\t\t\t[\"vote\", \"INTEGER\"],\n\t\t\t\t[\"json_id\", \"INTEGER REFERENCES json (json_id)\"]\n\t\t\t],\n\t\t\t\"indexes\": [\"CREATE INDEX comment_vote_comment_uri ON comment_vote(comment_uri)\", \"CREATE INDEX comment_vote_json_id ON comment_vote(json_id)\"],\n\t\t\t\"schema_changed\": 1426195822\n\t\t},\n\t\t\"post\": {\n\t\t\t\"cols\": [\n\t\t\t\t[\"post_id\", \"INTEGER\"],\n\t\t\t\t[\"title\", \"TEXT\"],\n\t\t\t\t[\"body\", \"TEXT\"],\n\t\t\t\t[\"date_published\", \"INTEGER\"],\n\t\t\t\t[\"json_id\", \"INTEGER REFERENCES json (json_id)\"]\n\t\t\t],\n\t\t\t\"indexes\": [\"CREATE UNIQUE INDEX post_uri ON post(json_id, post_id)\", \"CREATE INDEX post_id ON post(post_id)\"],\n\t\t\t\"schema_changed\": 1426195823\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/index.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>ZeroBlog Demo</title>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <link rel=\"stylesheet\" href=\"css/all.css\" />\n <base href=\"\" target=\"_top\" id=\"base\">\n <script>base.href = document.location.href.replace(\"/media\", \"\").replace(\"index.html\", \"\").replace(/[&?]wrapper=False/, \"\") // Make hashtags work</script>\n\n</head>\n<body>\n\n<!-- editbar -->\n<div class=\"editbar bottombar\">\n <ul class=\"markdown-help\">\n  <li># H1</li>\n  <li>## H2</li>\n  <li>### H3</li>\n  <li><i>_italic_</i></li>\n  <li><b>**bold**</b></li>\n  <li>~~<s>strikethrough</s>~~</li>\n  <li>- Lists</li>\n  <li>1. Numbered lists</li>\n  <li>[Links](http://www.zeronet.io)</li>\n  <li>[References][1]<br>[1]: Can be used</li>\n  <li>![image alt](img/logo.png)</li>\n  <li>Inline <code>`code`</code></li>\n  <li><code>```python<br>print \"Code block\"<br>```</code></li>\n  <li>&gt; Quotes</li>\n  <li>--- Horizontal rule</li>\n </ul>\n <a href=\"#Markdown+help\" class=\"icon-help\">?</a> Editing: <span class=\"object\">Post:21.body</span> <a href=\"#Save\" class=\"button save\">Save</a> <a href=\"#Delete\" class=\"button button-delete button-outline delete\">Delete</a> <a href=\"#Cancel\" class=\"cancel\">Cancel</a>\n</div>\n<!-- EOF editbar -->\n\n\n<!-- publishbar -->\n<div class=\"publishbar bottombar\">\n <small>Content changed</small> <a href=\"#Publish\" class=\"button button-outline button-ok publish\">Sign &amp; Publish new content</a>\n</div>\n<!-- EOF publishbar -->\n\n\n<!-- left -->\n<div class=\"left\" data-object=\"Site\">\n <a href=\"?Home\" class=\"nolink\"><div class=\"avatar\"> </div></a>\n <h1><a href=\"?Home\" class=\"nolink\" data-editable=\"title\" data-editable-mode=\"simple\"></a></h1>\n <h2 data-editable=\"description\"></h2>\n <hr>\n <div class=\"links\" data-editable=\"links\">\n </div>\n</div>\n<!-- EOF left -->\n\n\n<!-- right -->\n<div class=\"right\">\n\n\n <!-- Post listing -->\n <div class=\"posts\">\n  <a href=\"#New+Post\" class=\"button button-outline new\">Add new post</a>\n  \n  <!-- Template: post -->\n  <div class=\"post template\" data-object=\"Post:23\" data-deletable=\"True\">\n   <h1 class=\"title\"><a href=\"?Post:23:Title\" data-editable=\"title\" data-editable-mode=\"simple\" class=\"editable\">Title</a></h1>\n   <div class=\"details\">\n    <span class=\"published\" data-editable=\"date_published\" data-editable-mode=\"timestamp\">21 hours ago &middot; 2 min read</span>\n    <a href=\"?Post:23:title\" class=\"comments-num\">&middot; <div class='icon-comment'></div> <span class=\"num\">3 comments</span></a>\n   </div>\n   <div class=\"body\" data-editable=\"body\">Body</div>\n   <a class=\"more\" href=\"#\"><span class='readmore'>Read more</span> →</a>\n  </div>\n  <!-- EOF Template: post -->\n \n </div>\n <!-- EOF Post listing -->\n\n <!-- Single Post show -->\n <div class=\"post post-full\" data-object=\"Post:23\" data-deletable=\"True\">\n  <h1 class=\"title\"><a href=\"?Post:23:Title\" data-editable=\"title\" data-editable-mode=\"simple\" class=\"editable\">Title</a></h1>\n  <div class=\"details\"> <span class=\"published\" data-editable=\"date_published\" data-editable-mode=\"timestamp\">21 hours ago &middot; 2 min read</span> </div>\n  <div class=\"body\" data-editable=\"body\"></div>\n\n  <h2 id=\"Comments\"><span class=\"comments-num\">0</span> Comments:</h2>\n  <!-- New comment -->\n  <div class=\"comment comment-new\">\n   <div class=\"info\">\n    <a class=\"user_name certselect\" href=\"#Change+user\" title='Change user'>Please sign in</a>\n    &#9473; \n    <span class=\"added\">new comment</span>\n   </div>\n   <div class=\"comment-body\">\n    <a class=\"button button-submit button-certselect certselect\" href=\"#Change+user\"><div class='icon-profile'></div>Sign in as...</a>\n   \t<textarea class=\"comment-textarea\"></textarea>\n   \t<a href=\"#Submit+comment\" class=\"button button-submit button-submit-comment\">Submit comment</a>\n   \t<div style='float: right; margin-top: -6px'>\n     <div class=\"user-size user-size-used\"></div>\n     <div class=\"user-size\"></div>\n    </div>\n   \t<div style=\"clear: both\"></div>\n   </div>\n  </div>\n  <!-- EOF New comment -->\n\n  <div class=\"comments\">\n   <!-- Template: Comment -->\n   <div class=\"comment template\">\n    <div class=\"info\">\n     <span class=\"user_name\">user_name</span>\n     <!--<span class=\"cert_domain\"></span>-->\n     &#9473; \n     <span class=\"added\">1 day ago</span>\n     <a href=\"#Reply\" class=\"reply\"><div class=\"icon icon-reply\"></div> <span class=\"reply-text\">Reply</span></a>\n    </div>\n    <div class=\"comment-body\">Body</div>\n   </div>\n   <!-- EOF Template: Comment -->\n  </div>\n </div>\n <!-- EOF Single Post sho -->\n\n\n\n</div>\n<!-- EOF right -->\n\n\n<div style=\"clear: both\"></div>\n\n\n<script type=\"text/javascript\" src=\"js/all.js\" async></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/Test/testdata/1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT-original/js/all.js",
    "content": "\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/00-jquery.min.js ---- */\n\n\n/*! jQuery v2.1.3 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */\n!function(a,b){\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error(\"jQuery requires a window with a document\");return b(a)}:b(a)}(\"undefined\"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m=\"2.1.3\",n=function(a,b){return new n.fn.init(a,b)},o=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,p=/^-ms-/,q=/-([\\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:\"\",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for(\"boolean\"==typeof g&&(j=g,g=arguments[h]||{},h++),\"object\"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:\"jQuery\"+(m+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return\"function\"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return\"object\"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,\"isPrototypeOf\")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+\"\":\"object\"==typeof a||\"function\"==typeof a?h[i.call(a)]||\"object\":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf(\"use strict\")?(b=l.createElement(\"script\"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,\"ms-\").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?\"\":(a+\"\").replace(o,\"\")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,\"string\"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return\"string\"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"),function(a,b){h[\"[object \"+b+\"]\"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return\"function\"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:\"array\"===c||0===b||\"number\"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u=\"sizzle\"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",L=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",M=\"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",N=M.replace(\"w\",\"w#\"),O=\"\\\\[\"+L+\"*(\"+M+\")(?:\"+L+\"*([*^$|!~]?=)\"+L+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+N+\"))|)\"+L+\"*\\\\]\",P=\":(\"+M+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+O+\")*)|.*)\\\\)|)\",Q=new RegExp(L+\"+\",\"g\"),R=new RegExp(\"^\"+L+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+L+\"+$\",\"g\"),S=new RegExp(\"^\"+L+\"*,\"+L+\"*\"),T=new RegExp(\"^\"+L+\"*([>+~]|\"+L+\")\"+L+\"*\"),U=new RegExp(\"=\"+L+\"*([^\\\\]'\\\"]*?)\"+L+\"*\\\\]\",\"g\"),V=new RegExp(P),W=new RegExp(\"^\"+N+\"$\"),X={ID:new RegExp(\"^#(\"+M+\")\"),CLASS:new RegExp(\"^\\\\.(\"+M+\")\"),TAG:new RegExp(\"^(\"+M.replace(\"w\",\"w*\")+\")\"),ATTR:new RegExp(\"^\"+O),PSEUDO:new RegExp(\"^\"+P),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+L+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+L+\"*(?:([+-]|)\"+L+\"*(\\\\d+)|))\"+L+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+K+\")$\",\"i\"),needsContext:new RegExp(\"^\"+L+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+L+\"*((?:-\\\\d)?\\\\d*)\"+L+\"*\\\\)|)(?=[^-]|$)\",\"i\")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\\d$/i,$=/^[^{]+\\{\\s*\\[native \\w/,_=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,ab=/[+~]/,bb=/'|\\\\/g,cb=new RegExp(\"\\\\\\\\([\\\\da-f]{1,6}\"+L+\"?|(\"+L+\")|.)\",\"ig\"),db=function(a,b,c){var d=\"0x\"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,\"string\"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&\"object\"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute(\"id\"))?s=r.replace(bb,\"\\\\$&\"):b.setAttribute(\"id\",s),s=\"[id='\"+s+\"'] \",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(\",\")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute(\"id\")}}}return i(a.replace(R,\"$1\"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+\" \")>d.cacheLength&&delete b[a.shift()],b[c+\" \"]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement(\"div\");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split(\"|\"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return\"input\"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return(\"input\"===c||\"button\"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&\"undefined\"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?\"HTML\"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener(\"unload\",eb,!1):e.attachEvent&&e.attachEvent(\"onunload\",eb)),p=!f(g),c.attributes=jb(function(a){return a.className=\"i\",!a.getAttribute(\"className\")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment(\"\")),!a.getElementsByTagName(\"*\").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(\"undefined\"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute(\"id\")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=\"undefined\"!=typeof a.getAttributeNode&&a.getAttributeNode(\"id\");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return\"undefined\"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if(\"*\"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML=\"<a id='\"+u+\"'></a><select id='\"+u+\"-\\f]' msallowcapture=''><option selected=''></option></select>\",a.querySelectorAll(\"[msallowcapture^='']\").length&&q.push(\"[*^$]=\"+L+\"*(?:''|\\\"\\\")\"),a.querySelectorAll(\"[selected]\").length||q.push(\"\\\\[\"+L+\"*(?:value|\"+K+\")\"),a.querySelectorAll(\"[id~=\"+u+\"-]\").length||q.push(\"~=\"),a.querySelectorAll(\":checked\").length||q.push(\":checked\"),a.querySelectorAll(\"a#\"+u+\"+*\").length||q.push(\".#.+[+~]\")}),jb(function(a){var b=g.createElement(\"input\");b.setAttribute(\"type\",\"hidden\"),a.appendChild(b).setAttribute(\"name\",\"D\"),a.querySelectorAll(\"[name=d]\").length&&q.push(\"name\"+L+\"*[*^$|!~]?=\"),a.querySelectorAll(\":enabled\").length||q.push(\":enabled\",\":disabled\"),a.querySelectorAll(\"*,:x\"),q.push(\",.*:\")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,\"div\"),s.call(a,\"[s!='']:x\"),r.push(\"!=\",P)}),q=q.length&&new RegExp(q.join(\"|\")),r=r.length&&new RegExp(r.join(\"|\")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,\"='$1']\"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error(\"Syntax error, unrecognized expression: \"+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c=\"\",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if(\"string\"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||\"\").replace(cb,db),\"~=\"===a[2]&&(a[3]=\" \"+a[3]+\" \"),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),\"nth\"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*(\"even\"===a[3]||\"odd\"===a[3])),a[5]=+(a[7]+a[8]||\"odd\"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||\"\":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(\")\",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return\"*\"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+\" \"];return b||(b=new RegExp(\"(^|\"+L+\")\"+a+\"(\"+L+\"|$)\"))&&y(a,function(a){return b.test(\"string\"==typeof a.className&&a.className||\"undefined\"!=typeof a.getAttribute&&a.getAttribute(\"class\")||\"\")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?\"!=\"===b:b?(e+=\"\",\"=\"===b?e===c:\"!=\"===b?e!==c:\"^=\"===b?c&&0===e.indexOf(c):\"*=\"===b?c&&e.indexOf(c)>-1:\"$=\"===b?c&&e.slice(-c.length)===c:\"~=\"===b?(\" \"+e.replace(Q,\" \")+\" \").indexOf(c)>-1:\"|=\"===b?e===c||e.slice(0,c.length+1)===c+\"-\":!1):!0}},CHILD:function(a,b,c,d,e){var f=\"nth\"!==a.slice(0,3),g=\"last\"!==a.slice(-4),h=\"of-type\"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?\"nextSibling\":\"previousSibling\",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p=\"only\"===a&&!o&&\"nextSibling\"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error(\"unsupported pseudo: \"+a);return e[u]?e(b):e.length>1?(c=[a,a,\"\",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,\"$1\"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||\"\")||gb.error(\"unsupported lang: \"+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute(\"xml:lang\")||b.getAttribute(\"lang\"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+\"-\");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return\"input\"===b&&!!a.checked||\"option\"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return\"input\"===b&&\"button\"===a.type||\"button\"===b},text:function(a){var b;return\"input\"===a.nodeName.toLowerCase()&&\"text\"===a.type&&(null==(b=a.getAttribute(\"type\"))||\"text\"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=mb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=nb(b);function qb(){}qb.prototype=d.filters=d.pseudos,d.setFilters=new qb,g=gb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+\" \"];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R,\" \")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?gb.error(a):z(a,i).slice(0)};function rb(a){for(var b=0,c=a.length,d=\"\";c>b;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&\"parentNode\"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||\"*\",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[\" \"],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:\" \"===a[i-2].type?\"*\":\"\"})).replace(R,\"$1\"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q=\"0\",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG(\"*\",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+\" \"];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n=\"function\"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&\"ID\"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split(\"\").sort(B).join(\"\")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement(\"div\"))}),jb(function(a){return a.innerHTML=\"<a href='#'></a>\",\"#\"===a.firstChild.getAttribute(\"href\")})||kb(\"type|href|height|width\",function(a,b,c){return c?void 0:a.getAttribute(b,\"type\"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML=\"<input/>\",a.firstChild.setAttribute(\"value\",\"\"),\"\"===a.firstChild.getAttribute(\"value\")})||kb(\"value\",function(a,b,c){return c||\"input\"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute(\"disabled\")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);n.find=t,n.expr=t.selectors,n.expr[\":\"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/,w=/^.[^:#\\[\\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if(\"string\"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=\":not(\"+a+\")\"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if(\"string\"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+\" \"+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,\"string\"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if(\"string\"==typeof a){if(c=\"<\"===a[0]&&\">\"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?\"undefined\"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||\"string\"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?\"string\"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,\"parentNode\")},parentsUntil:function(a,b,c){return n.dir(a,\"parentNode\",c)},next:function(a){return D(a,\"nextSibling\")},prev:function(a){return D(a,\"previousSibling\")},nextAll:function(a){return n.dir(a,\"nextSibling\")},prevAll:function(a){return n.dir(a,\"previousSibling\")},nextUntil:function(a,b,c){return n.dir(a,\"nextSibling\",c)},prevUntil:function(a,b,c){return n.dir(a,\"previousSibling\",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return\"Until\"!==a.slice(-5)&&(d=c),d&&\"string\"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a=\"string\"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);\"function\"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&\"string\"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[[\"resolve\",\"done\",n.Callbacks(\"once memory\"),\"resolved\"],[\"reject\",\"fail\",n.Callbacks(\"once memory\"),\"rejected\"],[\"notify\",\"progress\",n.Callbacks(\"memory\")]],c=\"pending\",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+\"With\"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+\"With\"](this===e?d:this,arguments),this},e[f[0]+\"With\"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler(\"ready\"),n(l).off(\"ready\"))))}});function I(){l.removeEventListener(\"DOMContentLoaded\",I,!1),a.removeEventListener(\"load\",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),\"complete\"===l.readyState?setTimeout(n.ready):(l.addEventListener(\"DOMContentLoaded\",I,!1),a.addEventListener(\"load\",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if(\"object\"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if(\"string\"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&\"string\"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d=\"data-\"+b.replace(O,\"-$1\").toLowerCase(),c=a.getAttribute(d),\"string\"==typeof c){try{c=\"true\"===c?!0:\"false\"===c?!1:\"null\"===c?null:+c+\"\"===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)\n},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,\"hasDataAttrs\"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf(\"data-\")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,\"hasDataAttrs\",!0)}return e}return\"object\"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf(\"-\")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||\"fx\")+\"queue\",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||\"fx\";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};\"inprogress\"===e&&(e=c.shift(),d--),e&&(\"fx\"===b&&c.unshift(\"inprogress\"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+\"queueHooks\";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks(\"once memory\").add(function(){L.remove(a,[b+\"queue\",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return\"string\"!=typeof a&&(b=a,a=\"fx\",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),\"fx\"===a&&\"inprogress\"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||\"fx\",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};\"string\"!=typeof a&&(b=a,a=void 0),a=a||\"fx\";while(g--)c=L.get(f[g],a+\"queueHooks\"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,R=[\"Top\",\"Right\",\"Bottom\",\"Left\"],S=function(a,b){return a=b||a,\"none\"===n.css(a,\"display\")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement(\"div\")),c=l.createElement(\"input\");c.setAttribute(\"type\",\"radio\"),c.setAttribute(\"checked\",\"checked\"),c.setAttribute(\"name\",\"t\"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML=\"<textarea>x</textarea>\",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U=\"undefined\";k.focusinBubbles=\"onfocusin\"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||\"\").match(E)||[\"\"],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||\"\").split(\".\").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(\".\")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||\"\").match(E)||[\"\"],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||\"\").split(\".\").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp(\"(^|\\\\.)\"+p.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&(\"**\"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,\"events\"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,\"type\")?b.type:b,r=j.call(b,\"namespace\")?b.namespace.split(\".\"):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(\".\")>=0&&(r=q.split(\".\"),q=r.shift(),r.sort()),k=q.indexOf(\":\")<0&&\"on\"+q,b=b[n.expando]?b:new n.Event(q,\"object\"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join(\".\"),b.namespace_re=b.namespace?new RegExp(\"(^|\\\\.)\"+r.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,\"events\")||{})[b.type]&&L.get(g,\"handle\"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,\"events\")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||\"click\"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||\"click\"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+\" \",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:\"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),fixHooks:{},keyHooks:{props:\"char charCode key keyCode\".split(\" \"),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:\"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:\"focusin\"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:\"focusout\"},click:{trigger:function(){return\"checkbox\"===this.type&&this.click&&n.nodeName(this,\"input\")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,\"a\")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:\"focusin\",blur:\"focusout\"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if(\"object\"==typeof a){\"string\"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&(\"string\"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+\".\"+d.namespace:d.origType,d.selector,d.handler),this;if(\"object\"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||\"function\"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,bb=/<([\\w:]+)/,cb=/<|&#?\\w+;/,db=/<(?:script|style|link)/i,eb=/checked\\s*(?:[^=]|=\\s*.checked.)/i,fb=/^$|\\/(?:java|ecma)script/i,gb=/^true\\/(.*)/,hb=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,ib={option:[1,\"<select multiple='multiple'>\",\"</select>\"],thead:[1,\"<table>\",\"</table>\"],col:[2,\"<table><colgroup>\",\"</colgroup></table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:[0,\"\",\"\"]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,\"table\")&&n.nodeName(11!==b.nodeType?b:b.firstChild,\"tr\")?a.getElementsByTagName(\"tbody\")[0]||a.appendChild(a.ownerDocument.createElement(\"tbody\")):a}function kb(a){return a.type=(null!==a.getAttribute(\"type\"))+\"/\"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute(\"type\"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],\"globalEval\",!b||L.get(b[c],\"globalEval\"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||\"*\"):a.querySelectorAll?a.querySelectorAll(b||\"*\"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();\"input\"===c&&T.test(a.type)?b.checked=a.checked:(\"input\"===c||\"textarea\"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,\"script\"),g.length>0&&mb(g,!i&&ob(a,\"script\")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if(\"object\"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement(\"div\")),g=(bb.exec(e)||[\"\",\"\"])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,\"<$1></$2>\")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=\"\"}else l.push(b.createTextNode(e));k.textContent=\"\",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),\"script\"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||\"\")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,\"script\")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent=\"\");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if(\"string\"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||[\"\",\"\"])[1].toLowerCase()]){a=a.replace(ab,\"<$1></$2>\");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&\"string\"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,\"script\"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,\"script\"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||\"\")&&!L.access(h,\"globalEval\")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,\"\")))}return this}}),n.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],\"display\");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),\"none\"!==c&&c||(qb=(qb||n(\"<iframe frameborder='0' width='0' height='0'/>\")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp(\"^(\"+Q+\")(?!px)[a-z%]+$\",\"i\"),wb=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(\"\"!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+\"\":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement(\"div\"),f=l.createElement(\"div\");if(f.style){f.style.backgroundClip=\"content-box\",f.cloneNode(!0).style.backgroundClip=\"\",k.clearCloneStyle=\"content-box\"===f.style.backgroundClip,e.style.cssText=\"border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute\",e.appendChild(f);function g(){f.style.cssText=\"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute\",f.innerHTML=\"\",d.appendChild(e);var g=a.getComputedStyle(f,null);b=\"1%\"!==g.top,c=\"4px\"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement(\"div\"));return c.style.cssText=f.style.cssText=\"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0\",c.style.marginRight=c.style.width=\"0\",f.style.width=\"1px\",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),f.removeChild(c),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp(\"^(\"+Q+\")(.*)$\",\"i\"),Bb=new RegExp(\"^([+-])=(\"+Q+\")\",\"i\"),Cb={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Db={letterSpacing:\"0\",fontWeight:\"400\"},Eb=[\"Webkit\",\"O\",\"Moz\",\"ms\"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||\"px\"):b}function Hb(a,b,c,d,e){for(var f=c===(d?\"border\":\"content\")?4:\"width\"===b?1:0,g=0;4>f;f+=2)\"margin\"===c&&(g+=n.css(a,c+R[f],!0,e)),d?(\"content\"===c&&(g-=n.css(a,\"padding\"+R[f],!0,e)),\"margin\"!==c&&(g-=n.css(a,\"border\"+R[f]+\"Width\",!0,e))):(g+=n.css(a,\"padding\"+R[f],!0,e),\"padding\"!==c&&(g+=n.css(a,\"border\"+R[f]+\"Width\",!0,e)));return g}function Ib(a,b,c){var d=!0,e=\"width\"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g=\"border-box\"===n.css(a,\"boxSizing\",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?\"border\":\"content\"),d,f)+\"px\"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,\"olddisplay\"),c=d.style.display,b?(f[g]||\"none\"!==c||(d.style.display=\"\"),\"\"===d.style.display&&S(d)&&(f[g]=L.access(d,\"olddisplay\",tb(d.nodeName)))):(e=S(d),\"none\"===c&&e||L.set(d,\"olddisplay\",e?c:n.css(d,\"display\"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&\"none\"!==d.style.display&&\"\"!==d.style.display||(d.style.display=b?f[g]||\"\":\"none\"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,\"opacity\");return\"\"===c?\"1\":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{\"float\":\"cssFloat\"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fb(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&\"get\"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,\"string\"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f=\"number\"),null!=c&&c===c&&(\"number\"!==f||n.cssNumber[h]||(c+=\"px\"),k.clearCloneStyle||\"\"!==c||0!==b.indexOf(\"background\")||(i[b]=\"inherit\"),g&&\"set\"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fb(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&\"get\"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),\"normal\"===e&&b in Db&&(e=Db[b]),\"\"===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each([\"height\",\"width\"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?zb.test(n.css(a,\"display\"))&&0===a.offsetWidth?n.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,\"border-box\"===n.css(a,\"boxSizing\",!1,e),e):0)}}}),n.cssHooks.marginRight=yb(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:\"inline-block\"},xb,[a,\"marginRight\"]):void 0}),n.each({margin:\"\",padding:\"\",border:\"Width\"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f=\"string\"==typeof c?c.split(\" \"):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(n.cssHooks[a+b].set=Gb)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return\"boolean\"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}n.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||\"swing\",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?\"\":\"px\")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,\"\"),b&&\"auto\"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Kb.prototype.init,n.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp(\"^(?:([+-])=|)(\"+Q+\")([a-z%]*)$\",\"i\"),Pb=/queueHooks$/,Qb=[Vb],Rb={\"*\":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(n.cssNumber[a]?\"\":\"px\"),g=(n.cssNumber[a]||\"px\"!==f&&+d)&&Ob.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||\".5\",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=n.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e[\"margin\"+c]=e[\"padding\"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb[\"*\"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,\"fxshow\");c.queue||(h=n._queueHooks(a,\"fx\"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,\"fx\").length||h.empty.fire()})})),1===a.nodeType&&(\"height\"in b||\"width\"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,\"display\"),k=\"none\"===j?L.get(a,\"olddisplay\")||tb(a.nodeName):j,\"inline\"===k&&\"none\"===n.css(a,\"float\")&&(o.display=\"inline-block\")),c.overflow&&(o.overflow=\"hidden\",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||\"toggle\"===e,e===(p?\"hide\":\"show\")){if(\"show\"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))\"inline\"===(\"none\"===j?tb(a.nodeName):j)&&(o.display=j);else{q?\"hidden\"in q&&(p=q.hidden):q=L.access(a,\"fxshow\",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,\"fxshow\");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ub(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start=\"width\"===d||\"height\"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&\"expand\"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return n.map(k,Ub,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xb,{tweener:function(a,b){n.isFunction(a)?(b=a,a=[\"*\"]):a=a.split(\" \");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),n.speed=function(a,b,c){var d=a&&\"object\"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:\"number\"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue=\"fx\"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css(\"opacity\",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xb(this,n.extend({},a),f);(e||L.get(this,\"finish\"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return\"string\"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||\"fx\",[]),this.each(function(){var b=!0,e=null!=a&&a+\"queueHooks\",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||\"fx\"),this.each(function(){var b,c=L.get(this),d=c[a+\"queue\"],e=c[a+\"queueHooks\"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each([\"toggle\",\"show\",\"hide\"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||\"boolean\"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),n.each({slideDown:Tb(\"show\"),slideUp:Tb(\"hide\"),slideToggle:Tb(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Lb=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Lb=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Mb||(Mb=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Mb),Mb=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||\"fx\",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement(\"input\"),b=l.createElement(\"select\"),c=b.appendChild(l.createElement(\"option\"));a.type=\"checkbox\",k.checkOn=\"\"!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement(\"input\"),a.value=\"t\",a.type=\"radio\",k.radioValue=\"t\"===a.value}();var Yb,Zb,$b=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&\"get\"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&\"set\"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+\"\"),c):void n.removeAttr(a,b))\n},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&\"radio\"===b&&n.nodeName(a,\"input\")){var c=a.value;return a.setAttribute(\"type\",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\\w+/g),function(a,b){var c=$b[b]||n.find.attr;$b[b]=function(a,b,d){var e,f;return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{\"for\":\"htmlFor\",\"class\":\"className\"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&\"set\"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&\"get\"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute(\"tabindex\")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){n.propFix[this.toLowerCase()]=this});var ac=/[\\t\\r\\n\\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=\"string\"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||\"\").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(\" \"+c.className+\" \").replace(ac,\" \"):\" \")){f=0;while(e=b[f++])d.indexOf(\" \"+e+\" \")<0&&(d+=e+\" \");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||\"string\"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||\"\").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(\" \"+c.className+\" \").replace(ac,\" \"):\"\")){f=0;while(e=b[f++])while(d.indexOf(\" \"+e+\" \")>=0)d=d.replace(\" \"+e+\" \",\" \");g=a?n.trim(d):\"\",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return\"boolean\"==typeof b&&\"string\"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if(\"string\"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||\"boolean\"===c)&&(this.className&&L.set(this,\"__className__\",this.className),this.className=this.className||a===!1?\"\":L.get(this,\"__className__\")||\"\")})},hasClass:function(a){for(var b=\" \"+a+\" \",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(\" \"+this[c].className+\" \").replace(ac,\" \").indexOf(b)>=0)return!0;return!1}});var bc=/\\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e=\"\":\"number\"==typeof e?e+=\"\":n.isArray(e)&&(e=n.map(e,function(a){return null==a?\"\":a+\"\"})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&\"set\"in b&&void 0!==b.set(this,e,\"value\")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&\"get\"in b&&void 0!==(c=b.get(e,\"value\"))?c:(c=e.value,\"string\"==typeof c?c.replace(bc,\"\"):null==c?\"\":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,\"value\");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f=\"select-one\"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute(\"disabled\"))||c.parentNode.disabled&&n.nodeName(c.parentNode,\"optgroup\"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each([\"radio\",\"checkbox\"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute(\"value\")?\"on\":a.value})}),n.each(\"blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu\".split(\" \"),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,\"**\"):this.off(b,a||\"**\",c)}});var cc=n.now(),dc=/\\?/;n.parseJSON=function(a){return JSON.parse(a+\"\")},n.parseXML=function(a){var b,c;if(!a||\"string\"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,\"text/xml\")}catch(d){b=void 0}return(!b||b.getElementsByTagName(\"parsererror\").length)&&n.error(\"Invalid XML: \"+a),b};var ec=/#.*$/,fc=/([?&])_=[^&]*/,gc=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,hc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,ic=/^(?:GET|HEAD)$/,jc=/^\\/\\//,kc=/^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,lc={},mc={},nc=\"*/\".concat(\"*\"),oc=a.location.href,pc=kc.exec(oc.toLowerCase())||[];function qc(a){return function(b,c){\"string\"!=typeof b&&(c=b,b=\"*\");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])\"+\"===d[0]?(d=d.slice(1)||\"*\",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function rc(a,b,c,d){var e={},f=a===mc;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return\"string\"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e[\"*\"]&&g(\"*\")}function sc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function tc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while(\"*\"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader(\"Content-Type\"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+\" \"+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function uc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if(\"*\"===f)f=i;else if(\"*\"!==i&&i!==f){if(g=j[i+\" \"+f]||j[\"* \"+f],!g)for(e in j)if(h=e.split(\" \"),h[1]===f&&(g=j[i+\" \"+h[0]]||j[\"* \"+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a[\"throws\"])b=g(b);else try{b=g(b)}catch(l){return{state:\"parsererror\",error:g?l:\"No conversion from \"+i+\" to \"+f}}}return{state:\"success\",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:oc,type:\"GET\",isLocal:hc.test(pc[1]),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":nc,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":n.parseJSON,\"text xml\":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?sc(sc(a,n.ajaxSettings),b):sc(n.ajaxSettings,a)},ajaxPrefilter:qc(lc),ajaxTransport:qc(mc),ajax:function(a,b){\"object\"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks(\"once memory\"),q=k.statusCode||{},r={},s={},t=0,u=\"canceled\",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=gc.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||oc)+\"\").replace(ec,\"\").replace(jc,pc[1]+\"//\"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||\"*\").toLowerCase().match(E)||[\"\"],null==k.crossDomain&&(h=kc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===pc[1]&&h[2]===pc[2]&&(h[3]||(\"http:\"===h[1]?\"80\":\"443\"))===(pc[3]||(\"http:\"===pc[1]?\"80\":\"443\")))),k.data&&k.processData&&\"string\"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),rc(lc,k,b,v),2===t)return v;i=n.event&&k.global,i&&0===n.active++&&n.event.trigger(\"ajaxStart\"),k.type=k.type.toUpperCase(),k.hasContent=!ic.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?\"&\":\"?\")+k.data,delete k.data),k.cache===!1&&(k.url=fc.test(d)?d.replace(fc,\"$1_=\"+cc++):d+(dc.test(d)?\"&\":\"?\")+\"_=\"+cc++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader(\"If-Modified-Since\",n.lastModified[d]),n.etag[d]&&v.setRequestHeader(\"If-None-Match\",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader(\"Content-Type\",k.contentType),v.setRequestHeader(\"Accept\",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+(\"*\"!==k.dataTypes[0]?\", \"+nc+\"; q=0.01\":\"\"):k.accepts[\"*\"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u=\"abort\";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=rc(mc,k,b,v)){v.readyState=1,i&&m.trigger(\"ajaxSend\",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort(\"timeout\")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,\"No Transport\");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||\"\",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=tc(k,v,f)),u=uc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader(\"Last-Modified\"),w&&(n.lastModified[d]=w),w=v.getResponseHeader(\"etag\"),w&&(n.etag[d]=w)),204===a||\"HEAD\"===k.type?x=\"nocontent\":304===a?x=\"notmodified\":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x=\"error\",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+\"\",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?\"ajaxSuccess\":\"ajaxError\",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger(\"ajaxComplete\",[v,k]),--n.active||n.event.trigger(\"ajaxStop\")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,\"json\")},getScript:function(a,b){return n.get(a,void 0,b,\"script\")}}),n.each([\"get\",\"post\"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n._evalUrl=function(a){return n.ajax({url:a,type:\"GET\",dataType:\"script\",async:!1,global:!1,\"throws\":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,\"body\")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var vc=/%20/g,wc=/\\[\\]$/,xc=/\\r?\\n/g,yc=/^(?:submit|button|image|reset|file)$/i,zc=/^(?:input|select|textarea|keygen)/i;function Ac(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||wc.test(a)?d(a,e):Ac(a+\"[\"+(\"object\"==typeof e?b:\"\")+\"]\",e,c,d)});else if(c||\"object\"!==n.type(b))d(a,b);else for(e in b)Ac(a+\"[\"+e+\"]\",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?\"\":b,d[d.length]=encodeURIComponent(a)+\"=\"+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Ac(c,a[c],b,e);return d.join(\"&\").replace(vc,\"+\")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,\"elements\");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(\":disabled\")&&zc.test(this.nodeName)&&!yc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(xc,\"\\r\\n\")}}):{name:b.name,value:c.replace(xc,\"\\r\\n\")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Bc=0,Cc={},Dc={0:200,1223:204},Ec=n.ajaxSettings.xhr();a.attachEvent&&a.attachEvent(\"onunload\",function(){for(var a in Cc)Cc[a]()}),k.cors=!!Ec&&\"withCredentials\"in Ec,k.ajax=Ec=!!Ec,n.ajaxTransport(function(a){var b;return k.cors||Ec&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Bc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c[\"X-Requested-With\"]||(c[\"X-Requested-With\"]=\"XMLHttpRequest\");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Cc[g],b=f.onload=f.onerror=null,\"abort\"===a?f.abort():\"error\"===a?d(f.status,f.statusText):d(Dc[f.status]||f.status,f.statusText,\"string\"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b(\"error\"),b=Cc[g]=b(\"abort\");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/(?:java|ecma)script/},converters:{\"text script\":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter(\"script\",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type=\"GET\")}),n.ajaxTransport(\"script\",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n(\"<script>\").prop({async:!0,charset:a.scriptCharset,src:a.url}).on(\"load error\",c=function(a){b.remove(),c=null,a&&e(\"error\"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Fc=[],Gc=/(=)\\?(?=&|$)|\\?\\?/;n.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var a=Fc.pop()||n.expando+\"_\"+cc++;return this[a]=!0,a}}),n.ajaxPrefilter(\"json jsonp\",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Gc.test(b.url)?\"url\":\"string\"==typeof b.data&&!(b.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Gc.test(b.data)&&\"data\");return h||\"jsonp\"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Gc,\"$1\"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?\"&\":\"?\")+b.jsonp+\"=\"+e),b.converters[\"script json\"]=function(){return g||n.error(e+\" was not called\"),g[0]},b.dataTypes[0]=\"json\",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Fc.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),\"script\"):void 0}),n.parseHTML=function(a,b,c){if(!a||\"string\"!=typeof a)return null;\"boolean\"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Hc=n.fn.load;n.fn.load=function(a,b,c){if(\"string\"!=typeof a&&Hc)return Hc.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(\" \");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&\"object\"==typeof b&&(e=\"POST\"),g.length>0&&n.ajax({url:a,type:e,dataType:\"html\",data:b}).done(function(a){f=arguments,g.html(d?n(\"<div>\").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Ic=a.document.documentElement;function Jc(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,\"position\"),l=n(a),m={};\"static\"===k&&(a.style.position=\"relative\"),h=l.offset(),f=n.css(a,\"top\"),i=n.css(a,\"left\"),j=(\"absolute\"===k||\"fixed\"===k)&&(f+i).indexOf(\"auto\")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),\"using\"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Jc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return\"fixed\"===n.css(c,\"position\")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],\"html\")||(d=a.offset()),d.top+=n.css(a[0],\"borderTopWidth\",!0),d.left+=n.css(a[0],\"borderLeftWidth\",!0)),{top:b.top-d.top-n.css(c,\"marginTop\",!0),left:b.left-d.left-n.css(c,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Ic;while(a&&!n.nodeName(a,\"html\")&&\"static\"===n.css(a,\"position\"))a=a.offsetParent;return a||Ic})}}),n.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(b,c){var d=\"pageYOffset\"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Jc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each([\"top\",\"left\"],function(a,b){n.cssHooks[b]=yb(k.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?n(a).position()[b]+\"px\":c):void 0})}),n.each({Height:\"height\",Width:\"width\"},function(a,b){n.each({padding:\"inner\"+a,content:b,\"\":\"outer\"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||\"boolean\"!=typeof d),g=c||(d===!0||e===!0?\"margin\":\"border\");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement[\"client\"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body[\"scroll\"+a],e[\"scroll\"+a],b.body[\"offset\"+a],e[\"offset\"+a],e[\"client\"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return n});var Kc=a.jQuery,Lc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Lc),b&&a.jQuery===n&&(a.jQuery=Kc),n},typeof b===U&&(a.jQuery=a.$=n),n});\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/highlight.pack.js ---- */\n\n\n!function(e){\"undefined\"!=typeof exports?e(exports):(window.hljs=e({}),\"function\"==typeof define&&define.amd&&define([],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,\"&amp;\").replace(/</gm,\"&lt;\").replace(/>/gm,\"&gt;\")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){var n=(e.className+\" \"+(e.parentNode?e.parentNode.className:\"\")).split(/\\s+/);return n=n.map(function(e){return e.replace(/^lang(uage)?-/,\"\")}),n.filter(function(e){return N(e)||/no(-?)highlight/.test(e)})[0]}function o(e,n){var t={};for(var r in e)t[r]=e[r];if(n)for(var r in n)t[r]=n[r];return t}function i(e){var n=[];return function r(e,a){for(var o=e.firstChild;o;o=o.nextSibling)3==o.nodeType?a+=o.nodeValue.length:1==o.nodeType&&(n.push({event:\"start\",offset:a,node:o}),a=r(o,a),t(o).match(/br|hr|img|input/)||n.push({event:\"stop\",offset:a,node:o}));return a}(e,0),n}function c(e,r,a){function o(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset<r[0].offset?e:r:\"start\"==r[0].event?e:r:e.length?e:r}function i(e){function r(e){return\" \"+e.nodeName+'=\"'+n(e.value)+'\"'}l+=\"<\"+t(e)+Array.prototype.map.call(e.attributes,r).join(\"\")+\">\"}function c(e){l+=\"</\"+t(e)+\">\"}function u(e){(\"start\"==e.event?i:c)(e.node)}for(var s=0,l=\"\",f=[];e.length||r.length;){var g=o();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(c);do u(g.splice(0,1)[0]),g=o();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(i)}else\"start\"==g[0].event?f.push(g[0].node):f.pop(),u(g.splice(0,1)[0])}return l+n(a.substr(s))}function u(e){function n(e){return e&&e.source||e}function t(t,r){return RegExp(n(t),\"m\"+(e.cI?\"i\":\"\")+(r?\"g\":\"\"))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var c={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(\" \").forEach(function(e){var t=e.split(\"|\");c[t[0]]=[n,t[1]?Number(t[1]):1]})};\"string\"==typeof a.k?u(\"keyword\",a.k):Object.keys(a.k).forEach(function(e){u(e,a.k[e])}),a.k=c}a.lR=t(a.l||/\\b[A-Za-z0-9_]+\\b/,!0),i&&(a.bK&&(a.b=\"\\\\b(\"+a.bK.split(\" \").join(\"|\")+\")\\\\b\"),a.b||(a.b=/\\B|\\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\\B|\\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||\"\",a.eW&&i.tE&&(a.tE+=(a.e?\"|\":\"\")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push(\"self\"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?\"\\\\.?(\"+e.b+\")\\\\.?\":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join(\"|\"),!0):{exec:function(){return null}}}}r(e)}function s(e,t,a,o){function i(e,n){for(var t=0;t<n.c.length;t++)if(r(n.c[t].bR,e))return n.c[t]}function c(e,n){return r(e.eR,n)?e:e.eW?c(e.parent,n):void 0}function f(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=x.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?\"\":E.classPrefix,o='<span class=\"'+a,i=t?\"\":\"</span>\";return o+=e+'\">',o+n+i}function d(){if(!w.k)return n(y);var e=\"\",t=0;w.lR.lastIndex=0;for(var r=w.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(w,r);a?(B+=a[1],e+=p(a[0],n(r[0]))):e+=n(r[0]),t=w.lR.lastIndex,r=w.lR.exec(y)}return e+n(y.substr(t))}function h(){if(w.sL&&!R[w.sL])return n(y);var e=w.sL?s(w.sL,y,!0,L[w.sL]):l(y);return w.r>0&&(B+=e.r),\"continuous\"==w.subLanguageMode&&(L[w.sL]=e.top),p(e.language,e.value,!1,!0)}function v(){return void 0!==w.sL?h():d()}function b(e,t){var r=e.cN?p(e.cN,\"\",!0):\"\";e.rB?(M+=r,y=\"\"):e.eB?(M+=n(t)+r,y=\"\"):(M+=r,y=t),w=Object.create(e,{parent:{value:w}})}function m(e,t){if(y+=e,void 0===t)return M+=v(),0;var r=i(t,w);if(r)return M+=v(),b(r,t),r.rB?0:t.length;var a=c(w,t);if(a){var o=w;o.rE||o.eE||(y+=t),M+=v();do w.cN&&(M+=\"</span>\"),B+=w.r,w=w.parent;while(w!=a.parent);return o.eE&&(M+=n(t)),y=\"\",a.starts&&b(a.starts,\"\"),o.rE?0:t.length}if(f(t,w))throw new Error('Illegal lexeme \"'+t+'\" for mode \"'+(w.cN||\"<unnamed>\")+'\"');return y+=t,t.length||1}var x=N(e);if(!x)throw new Error('Unknown language: \"'+e+'\"');u(x);for(var w=o||x,L={},M=\"\",k=w;k!=x;k=k.parent)k.cN&&(M=p(k.cN,\"\",!0)+M);var y=\"\",B=0;try{for(var C,j,I=0;;){if(w.t.lastIndex=I,C=w.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}m(t.substr(I));for(var k=w;k.parent;k=k.parent)k.cN&&(M+=\"</span>\");return{r:B,value:M,language:e,top:w}}catch(A){if(-1!=A.message.indexOf(\"Illegal\"))return{r:0,value:n(t)};throw A}}function l(e,t){t=t||E.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(N(n)){var t=s(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function f(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\\t)+)/gm,function(e,n){return n.replace(/\\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\\n/g,\"<br>\")),e}function g(e,n,t){var r=n?x[n]:t,a=[e.trim()];return e.match(/(\\s|^)hljs(\\s|$)/)||a.push(\"hljs\"),r&&a.push(r),a.join(\" \").trim()}function p(e){var n=a(e);if(!/no(-?)highlight/.test(n)){var t;E.useBR?(t=document.createElementNS(\"http://www.w3.org/1999/xhtml\",\"div\"),t.innerHTML=e.innerHTML.replace(/\\n/g,\"\").replace(/<br[ \\/]*>/g,\"\\n\")):t=e;var r=t.textContent,o=n?s(n,r,!0):l(r),u=i(t);if(u.length){var p=document.createElementNS(\"http://www.w3.org/1999/xhtml\",\"div\");p.innerHTML=o.value,o.value=c(u,i(p),r)}o.value=f(o.value),e.innerHTML=o.value,e.className=g(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll(\"pre code\");Array.prototype.forEach.call(e,p)}}function v(){addEventListener(\"DOMContentLoaded\",h,!1),addEventListener(\"load\",h,!1)}function b(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){x[e]=n})}function m(){return Object.keys(R)}function N(e){return R[e]||R[x[e]]}var E={classPrefix:\"hljs-\",tabReplace:null,useBR:!1,languages:void 0},R={},x={};return e.highlight=s,e.highlightAuto=l,e.fixMarkup=f,e.highlightBlock=p,e.configure=d,e.initHighlighting=h,e.initHighlightingOnLoad=v,e.registerLanguage=b,e.listLanguages=m,e.getLanguage=N,e.inherit=o,e.IR=\"[a-zA-Z][a-zA-Z0-9_]*\",e.UIR=\"[a-zA-Z_][a-zA-Z0-9_]*\",e.NR=\"\\\\b\\\\d+(\\\\.\\\\d+)?\",e.CNR=\"(\\\\b0[xX][a-fA-F0-9]+|(\\\\b\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)([eE][-+]?\\\\d+)?)\",e.BNR=\"\\\\b(0b[01]+)\",e.RSR=\"!|!=|!==|%|%=|&|&&|&=|\\\\*|\\\\*=|\\\\+|\\\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\\\?|\\\\[|\\\\{|\\\\(|\\\\^|\\\\^=|\\\\||\\\\|=|\\\\|\\\\||~\",e.BE={b:\"\\\\\\\\[\\\\s\\\\S]\",r:0},e.ASM={cN:\"string\",b:\"'\",e:\"'\",i:\"\\\\n\",c:[e.BE]},e.QSM={cN:\"string\",b:'\"',e:'\"',i:\"\\\\n\",c:[e.BE]},e.PWM={b:/\\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\\b/},e.CLCM={cN:\"comment\",b:\"//\",e:\"$\",c:[e.PWM]},e.CBCM={cN:\"comment\",b:\"/\\\\*\",e:\"\\\\*/\",c:[e.PWM]},e.HCM={cN:\"comment\",b:\"#\",e:\"$\",c:[e.PWM]},e.NM={cN:\"number\",b:e.NR,r:0},e.CNM={cN:\"number\",b:e.CNR,r:0},e.BNM={cN:\"number\",b:e.BNR,r:0},e.CSSNM={cN:\"number\",b:e.NR+\"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?\",r:0},e.RM={cN:\"regexp\",b:/\\//,e:/\\/[gimuy]*/,i:/\\n/,c:[e.BE,{b:/\\[/,e:/\\]/,r:0,c:[e.BE]}]},e.TM={cN:\"title\",b:e.IR,r:0},e.UTM={cN:\"title\",b:e.UIR,r:0},e});hljs.registerLanguage(\"cpp\",function(t){var i={keyword:\"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginaryintmax_t uintmax_t int8_t uint8_t int16_t uint16_t int32_t uint32_t  int64_t uint64_tint_least8_t uint_least8_t int_least16_t uint_least16_t int_least32_t uint_least32_tint_least64_t uint_least64_t int_fast8_t uint_fast8_t int_fast16_t uint_fast16_t int_fast32_tuint_fast32_t int_fast64_t uint_fast64_t intptr_t uintptr_t atomic_bool atomic_char atomic_scharatomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llongatomic_ullong atomic_wchar_t atomic_char16_t atomic_char32_t atomic_intmax_t atomic_uintmax_tatomic_intptr_t atomic_uintptr_t atomic_size_t atomic_ptrdiff_t atomic_int_least8_t atomic_int_least16_tatomic_int_least32_t atomic_int_least64_t atomic_uint_least8_t atomic_uint_least16_t atomic_uint_least32_tatomic_uint_least64_t atomic_int_fast8_t atomic_int_fast16_t atomic_int_fast32_t atomic_int_fast64_tatomic_uint_fast8_t atomic_uint_fast16_t atomic_uint_fast32_t atomic_uint_fast64_t\",built_in:\"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf\"};return{aliases:[\"c\",\"h\",\"c++\",\"h++\"],k:i,i:\"</\",c:[t.CLCM,t.CBCM,t.QSM,{cN:\"string\",b:\"'\\\\\\\\?.\",e:\"'\",i:\".\"},{cN:\"number\",b:\"\\\\b(\\\\d+(\\\\.\\\\d*)?|\\\\.\\\\d+)(u|U|l|L|ul|UL|f|F)\"},t.CNM,{cN:\"preprocessor\",b:\"#\",e:\"$\",k:\"if else elif endif define undef warning error line pragma\",c:[{b:'include\\\\s*[<\"]',e:'[>\"]',k:\"include\",i:\"\\\\n\"},t.CLCM]},{cN:\"stl_container\",b:\"\\\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\\\s*<\",e:\">\",k:i,c:[\"self\"]},{b:t.IR+\"::\"},{bK:\"new throw return\",r:0},{cN:\"function\",b:\"(\"+t.IR+\"\\\\s+)+\"+t.IR+\"\\\\s*\\\\(\",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:t.IR+\"\\\\s*\\\\(\",rB:!0,c:[t.TM],r:0},{cN:\"params\",b:/\\(/,e:/\\)/,k:i,r:0,c:[t.CBCM]},t.CLCM,t.CBCM]}]}});hljs.registerLanguage(\"ruby\",function(e){var b=\"[a-zA-Z_]\\\\w*[!?=]?|[-+~]\\\\@|<<|>>|=~|===?|<=>|[<>]=?|\\\\*\\\\*|[-/+%^&*~`|]|\\\\[\\\\]=?\",r=\"and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor\",c={cN:\"yardoctag\",b:\"@[A-Za-z]+\"},a={cN:\"value\",b:\"#<\",e:\">\"},s={cN:\"comment\",v:[{b:\"#\",e:\"$\",c:[c]},{b:\"^\\\\=begin\",e:\"^\\\\=end\",c:[c],r:10},{b:\"^__END__\",e:\"\\\\n$\"}]},n={cN:\"subst\",b:\"#\\\\{\",e:\"}\",k:r},t={cN:\"string\",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/\"/,e:/\"/},{b:/`/,e:/`/},{b:\"%[qQwWx]?\\\\(\",e:\"\\\\)\"},{b:\"%[qQwWx]?\\\\[\",e:\"\\\\]\"},{b:\"%[qQwWx]?{\",e:\"}\"},{b:\"%[qQwWx]?<\",e:\">\"},{b:\"%[qQwWx]?/\",e:\"/\"},{b:\"%[qQwWx]?%\",e:\"%\"},{b:\"%[qQwWx]?-\",e:\"-\"},{b:\"%[qQwWx]?\\\\|\",e:\"\\\\|\"},{b:/\\B\\?(\\\\\\d{1,3}|\\\\x[A-Fa-f0-9]{1,2}|\\\\u[A-Fa-f0-9]{4}|\\\\?\\S)\\b/}]},i={cN:\"params\",b:\"\\\\(\",e:\"\\\\)\",k:r},d=[t,a,s,{cN:\"class\",bK:\"class module\",e:\"$|;\",i:/=/,c:[e.inherit(e.TM,{b:\"[A-Za-z_]\\\\w*(::\\\\w+)*(\\\\?|\\\\!)?\"}),{cN:\"inheritance\",b:\"<\\\\s*\",c:[{cN:\"parent\",b:\"(\"+e.IR+\"::)?\"+e.IR}]},s]},{cN:\"function\",bK:\"def\",e:\" |$|;\",r:0,c:[e.inherit(e.TM,{b:b}),i,s]},{cN:\"constant\",b:\"(::)?(\\\\b[A-Z]\\\\w*(::)?)+\",r:0},{cN:\"symbol\",b:e.UIR+\"(\\\\!|\\\\?)?:\",r:0},{cN:\"symbol\",b:\":\",c:[t,{b:b}],r:0},{cN:\"number\",b:\"(\\\\b0[0-7_]+)|(\\\\b0x[0-9a-fA-F_]+)|(\\\\b[1-9][0-9_]*(\\\\.[0-9_]+)?)|[0_]\\\\b\",r:0},{cN:\"variable\",b:\"(\\\\$\\\\W)|((\\\\$|\\\\@\\\\@?)(\\\\w+))\"},{b:\"(\"+e.RSR+\")\\\\s*\",c:[a,s,{cN:\"regexp\",c:[e.BE,n],i:/\\n/,v:[{b:\"/\",e:\"/[a-z]*\"},{b:\"%r{\",e:\"}[a-z]*\"},{b:\"%r\\\\(\",e:\"\\\\)[a-z]*\"},{b:\"%r!\",e:\"![a-z]*\"},{b:\"%r\\\\[\",e:\"\\\\][a-z]*\"}]}],r:0}];n.c=d,i.c=d;var l=\"[>?]>\",u=\"[\\\\w#]+\\\\(\\\\w+\\\\):\\\\d+:\\\\d+>\",N=\"(\\\\w+-)?\\\\d+\\\\.\\\\d+\\\\.\\\\d(p\\\\d+)?[^>]+>\",o=[{b:/^\\s*=>/,cN:\"status\",starts:{e:\"$\",c:d}},{cN:\"prompt\",b:\"^(\"+l+\"|\"+u+\"|\"+N+\")\",starts:{e:\"$\",c:d}}];return{aliases:[\"rb\",\"gemspec\",\"podspec\",\"thor\",\"irb\"],k:r,c:[s].concat(o).concat(d)}});hljs.registerLanguage(\"apache\",function(e){var r={cN:\"number\",b:\"[\\\\$%]\\\\d+\"};return{aliases:[\"apacheconf\"],cI:!0,c:[e.HCM,{cN:\"tag\",b:\"</?\",e:\">\"},{cN:\"keyword\",b:/\\w+/,r:0,k:{common:\"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername\"},starts:{e:/$/,r:0,k:{literal:\"on off all\"},c:[{cN:\"sqbracket\",b:\"\\\\s\\\\[\",e:\"\\\\]$\"},{cN:\"cbracket\",b:\"[\\\\$%]\\\\{\",e:\"\\\\}\",c:[\"self\",r]},r,e.QSM]}}],i:/\\S/}});hljs.registerLanguage(\"python\",function(e){var r={cN:\"prompt\",b:/^(>>>|\\.\\.\\.) /},b={cN:\"string\",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?\"\"\"/,e:/\"\"\"/,c:[r],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)\"/,e:/\"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)\"/,e:/\"/},e.ASM,e.QSM]},l={cN:\"number\",r:0,v:[{b:e.BNR+\"[lLjJ]?\"},{b:\"\\\\b(0o[0-7]+)[lLjJ]?\"},{b:e.CNR+\"[lLjJ]?\"}]},c={cN:\"params\",b:/\\(/,e:/\\)/,c:[\"self\",r,l,b]};return{aliases:[\"py\",\"gyp\"],k:{keyword:\"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False\",built_in:\"Ellipsis NotImplemented\"},i:/(<\\/|->|\\?)/,c:[r,l,b,e.HCM,{v:[{cN:\"function\",bK:\"def\",r:10},{cN:\"class\",bK:\"class\"}],e:/:/,i:/[${=;\\n]/,c:[e.UTM,c]},{cN:\"decorator\",b:/@/,e:/$/},{b:/\\b(print|exec)\\(/}]}});hljs.registerLanguage(\"javascript\",function(r){return{aliases:[\"js\"],k:{keyword:\"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class\",literal:\"true false null undefined NaN Infinity\",built_in:\"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document\"},c:[{cN:\"pi\",r:10,v:[{b:/^\\s*('|\")use strict('|\")/},{b:/^\\s*('|\")use asm('|\")/}]},r.ASM,r.QSM,r.CLCM,r.CBCM,r.CNM,{b:\"(\"+r.RSR+\"|\\\\b(case|return|throw)\\\\b)\\\\s*\",k:\"return throw case\",c:[r.CLCM,r.CBCM,r.RM,{b:/</,e:/>;/,r:0,sL:\"xml\"}],r:0},{cN:\"function\",bK:\"function\",e:/\\{/,eE:!0,c:[r.inherit(r.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:\"params\",b:/\\(/,e:/\\)/,c:[r.CLCM,r.CBCM],i:/[\"'\\(]/}],i:/\\[|%/},{b:/\\$[(.]/},{b:\"\\\\.\"+r.IR,r:0}]}});hljs.registerLanguage(\"coffeescript\",function(e){var c={keyword:\"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not\",literal:\"true false null undefined yes no on off\",reserved:\"case default function var void with const let enum export import native __hasProp __extends __slice __bind __indexOf\",built_in:\"npm require console print module global window document\"},n=\"[A-Za-z$_][0-9A-Za-z$_]*\",t={cN:\"subst\",b:/#\\{/,e:/}/,k:c},r=[e.BNM,e.inherit(e.CNM,{starts:{e:\"(\\\\s*/)?\",r:0}}),{cN:\"string\",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/\"\"\"/,e:/\"\"\"/,c:[e.BE,t]},{b:/\"/,e:/\"/,c:[e.BE,t]}]},{cN:\"regexp\",v:[{b:\"///\",e:\"///\",c:[t,e.HCM]},{b:\"//[gim]*\",r:0},{b:/\\/(?![ *])(\\\\\\/|.)*?\\/[gim]*(?=\\W|$)/}]},{cN:\"property\",b:\"@\"+n},{b:\"`\",e:\"`\",eB:!0,eE:!0,sL:\"javascript\"}];t.c=r;var i=e.inherit(e.TM,{b:n}),s=\"(\\\\(.*\\\\))?\\\\s*\\\\B[-=]>\",o={cN:\"params\",b:\"\\\\([^\\\\(]\",rB:!0,c:[{b:/\\(/,e:/\\)/,k:c,c:[\"self\"].concat(r)}]};return{aliases:[\"coffee\",\"cson\",\"iced\"],k:c,i:/\\/\\*/,c:r.concat([{cN:\"comment\",b:\"###\",e:\"###\",c:[e.PWM]},e.HCM,{cN:\"function\",b:\"^\\\\s*\"+n+\"\\\\s*=\\\\s*\"+s,e:\"[-=]>\",rB:!0,c:[i,o]},{b:/[:\\(,=]\\s*/,r:0,c:[{cN:\"function\",b:s,e:\"[-=]>\",rB:!0,c:[o]}]},{cN:\"class\",bK:\"class\",e:\"$\",i:/[:=\"\\[\\]]/,c:[{bK:\"extends\",eW:!0,i:/[:=\"\\[\\]]/,c:[i]},i]},{cN:\"attribute\",b:n+\":\",e:\":\",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage(\"http\",function(){return{i:\"\\\\S\",c:[{cN:\"status\",b:\"^HTTP/[0-9\\\\.]+\",e:\"$\",c:[{cN:\"number\",b:\"\\\\b\\\\d{3}\\\\b\"}]},{cN:\"request\",b:\"^[A-Z]+ (.*?) HTTP/[0-9\\\\.]+$\",rB:!0,e:\"$\",c:[{cN:\"string\",b:\" \",e:\" \",eB:!0,eE:!0}]},{cN:\"attribute\",b:\"^\\\\w\",e:\": \",eE:!0,i:\"\\\\n|\\\\s|=\",starts:{cN:\"string\",e:\"$\"}},{b:\"\\\\n\\\\n\",starts:{sL:\"\",eW:!0}}]}});hljs.registerLanguage(\"css\",function(e){var c=\"[a-zA-Z-][a-zA-Z0-9_-]*\",a={cN:\"function\",b:c+\"\\\\(\",rB:!0,eE:!0,e:\"\\\\(\"};return{cI:!0,i:\"[=/|']\",c:[e.CBCM,{cN:\"id\",b:\"\\\\#[A-Za-z0-9_-]+\"},{cN:\"class\",b:\"\\\\.[A-Za-z0-9_-]+\",r:0},{cN:\"attr_selector\",b:\"\\\\[\",e:\"\\\\]\",i:\"$\"},{cN:\"pseudo\",b:\":(:)?[a-zA-Z0-9\\\\_\\\\-\\\\+\\\\(\\\\)\\\\\\\"\\\\']+\"},{cN:\"at_rule\",b:\"@(font-face|page)\",l:\"[a-z-]+\",k:\"font-face page\"},{cN:\"at_rule\",b:\"@\",e:\"[{;]\",c:[{cN:\"keyword\",b:/\\S+/},{b:/\\s/,eW:!0,eE:!0,r:0,c:[a,e.ASM,e.QSM,e.CSSNM]}]},{cN:\"tag\",b:c,r:0},{cN:\"rules\",b:\"{\",e:\"}\",i:\"[^\\\\s]\",r:0,c:[e.CBCM,{cN:\"rule\",b:\"[^\\\\s]\",rB:!0,e:\";\",eW:!0,c:[{cN:\"attribute\",b:\"[A-Z\\\\_\\\\.\\\\-]+\",e:\":\",eE:!0,i:\"[^\\\\s]\",starts:{cN:\"value\",eW:!0,eE:!0,c:[a,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:\"hexcolor\",b:\"#[0-9A-Fa-f]+\"},{cN:\"important\",b:\"!important\"}]}}]}]}]}});hljs.registerLanguage(\"ini\",function(e){return{cI:!0,i:/\\S/,c:[{cN:\"comment\",b:\";\",e:\"$\"},{cN:\"title\",b:\"^\\\\[\",e:\"\\\\]\"},{cN:\"setting\",b:\"^[a-z0-9\\\\[\\\\]_-]+[ \\\\t]*=[ \\\\t]*\",e:\"$\",c:[{cN:\"value\",eW:!0,k:\"on off true false yes no\",c:[e.QSM,e.NM],r:0}]}]}});hljs.registerLanguage(\"objectivec\",function(e){var t={keyword:\"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required\",literal:\"false true FALSE TRUE nil YES NO NULL\",built_in:\"NSString NSData NSDictionary CGRect CGPoint UIButton UILabel UITextView UIWebView MKMapView NSView NSViewController NSWindow NSWindowController NSSet NSUUID NSIndexSet UISegmentedControl NSObject UITableViewDelegate UITableViewDataSource NSThread UIActivityIndicator UITabbar UIToolBar UIBarButtonItem UIImageView NSAutoreleasePool UITableView BOOL NSInteger CGFloat NSException NSLog NSMutableString NSMutableArray NSMutableDictionary NSURL NSIndexPath CGSize UITableViewCell UIView UIViewController UINavigationBar UINavigationController UITabBarController UIPopoverController UIPopoverControllerDelegate UIImage NSNumber UISearchBar NSFetchedResultsController NSFetchedResultsChangeType UIScrollView UIScrollViewDelegate UIEdgeInsets UIColor UIFont UIApplication NSNotFound NSNotificationCenter NSNotification UILocalNotification NSBundle NSFileManager NSTimeInterval NSDate NSCalendar NSUserDefaults UIWindow NSRange NSArray NSError NSURLRequest NSURLConnection NSURLSession NSURLSessionDataTask NSURLSessionDownloadTask NSURLSessionUploadTask NSURLResponseUIInterfaceOrientation MPMoviePlayerController dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once\"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,a=\"@interface @class @protocol @implementation\";return{aliases:[\"m\",\"mm\",\"objc\",\"obj-c\"],k:t,l:o,i:\"</\",c:[e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:\"string\",v:[{b:'@\"',e:'\"',i:\"\\\\n\",c:[e.BE]},{b:\"'\",e:\"[^\\\\\\\\]'\",i:\"[^\\\\\\\\][^']\"}]},{cN:\"preprocessor\",b:\"#\",e:\"$\",c:[{cN:\"title\",v:[{b:'\"',e:'\"'},{b:\"<\",e:\">\"}]}]},{cN:\"class\",b:\"(\"+a.split(\" \").join(\"|\")+\")\\\\b\",e:\"({|$)\",eE:!0,k:a,l:o,c:[e.UTM]},{cN:\"variable\",b:\"\\\\.\"+e.UIR,r:0}]}});hljs.registerLanguage(\"bash\",function(e){var t={cN:\"variable\",v:[{b:/\\$[\\w\\d#@][\\w\\d_]*/},{b:/\\$\\{(.*?)\\}/}]},s={cN:\"string\",b:/\"/,e:/\"/,c:[e.BE,t,{cN:\"variable\",b:/\\$\\(/,e:/\\)/,c:[e.BE]}]},a={cN:\"string\",b:/'/,e:/'/};return{aliases:[\"sh\",\"zsh\"],l:/-?[a-z\\.]+/,k:{keyword:\"if then else elif fi for while in do done case esac function\",literal:\"true false\",built_in:\"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp\",operator:\"-ne -eq -lt -gt -f -d -e -s -l -a\"},c:[{cN:\"shebang\",b:/^#![^\\n]+sh\\s*$/,r:10},{cN:\"function\",b:/\\w[\\w\\d_]*\\s*\\(\\s*\\)\\s*\\{/,rB:!0,c:[e.inherit(e.TM,{b:/\\w[\\w\\d_]*/})],r:0},e.HCM,e.NM,s,a,t]}});hljs.registerLanguage(\"markdown\",function(){return{aliases:[\"md\",\"mkdown\",\"mkd\"],c:[{cN:\"header\",v:[{b:\"^#{1,6}\",e:\"$\"},{b:\"^.+?\\\\n[=-]{2,}$\"}]},{b:\"<\",e:\">\",sL:\"xml\",r:0},{cN:\"bullet\",b:\"^([*+-]|(\\\\d+\\\\.))\\\\s+\"},{cN:\"strong\",b:\"[*_]{2}.+?[*_]{2}\"},{cN:\"emphasis\",v:[{b:\"\\\\*.+?\\\\*\"},{b:\"_.+?_\",r:0}]},{cN:\"blockquote\",b:\"^>\\\\s+\",e:\"$\"},{cN:\"code\",v:[{b:\"`.+?`\"},{b:\"^( {4}|\t)\",e:\"$\",r:0}]},{cN:\"horizontal_rule\",b:\"^[-\\\\*]{3,}\",e:\"$\"},{b:\"\\\\[.+?\\\\][\\\\(\\\\[].*?[\\\\)\\\\]]\",rB:!0,c:[{cN:\"link_label\",b:\"\\\\[\",e:\"\\\\]\",eB:!0,rE:!0,r:0},{cN:\"link_url\",b:\"\\\\]\\\\(\",e:\"\\\\)\",eB:!0,eE:!0},{cN:\"link_reference\",b:\"\\\\]\\\\[\",e:\"\\\\]\",eB:!0,eE:!0}],r:10},{b:\"^\\\\[.+\\\\]:\",rB:!0,c:[{cN:\"link_reference\",b:\"\\\\[\",e:\"\\\\]:\",eB:!0,eE:!0,starts:{cN:\"link_url\",e:\"$\"}}]}]}});hljs.registerLanguage(\"java\",function(e){var a=e.UIR+\"(<\"+e.UIR+\">)?\",t=\"false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private\",c=\"(\\\\b(0b[01_]+)|\\\\b0[xX][a-fA-F0-9_]+|(\\\\b[\\\\d_]+(\\\\.[\\\\d_]*)?|\\\\.[\\\\d_]+)([eE][-+]?\\\\d+)?)[lLfF]?\",r={cN:\"number\",b:c,r:0};return{aliases:[\"jsp\"],k:t,i:/<\\//,c:[{cN:\"javadoc\",b:\"/\\\\*\\\\*\",e:\"\\\\*/\",r:0,c:[{cN:\"javadoctag\",b:\"(^|\\\\s)@[A-Za-z]+\"}]},e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:\"class\",bK:\"class interface\",e:/[{;=]/,eE:!0,k:\"class interface\",i:/[:\"\\[\\]]/,c:[{bK:\"extends implements\"},e.UTM]},{bK:\"new throw return\",r:0},{cN:\"function\",b:\"(\"+a+\"\\\\s+)+\"+e.UIR+\"\\\\s*\\\\(\",rB:!0,e:/[{;=]/,eE:!0,k:t,c:[{b:e.UIR+\"\\\\s*\\\\(\",rB:!0,r:0,c:[e.UTM]},{cN:\"params\",b:/\\(/,e:/\\)/,k:t,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},r,{cN:\"annotation\",b:\"@[A-Za-z]+\"}]}});hljs.registerLanguage(\"diff\",function(){return{aliases:[\"patch\"],c:[{cN:\"chunk\",r:10,v:[{b:/^\\@\\@ +\\-\\d+,\\d+ +\\+\\d+,\\d+ +\\@\\@$/},{b:/^\\*\\*\\* +\\d+,\\d+ +\\*\\*\\*\\*$/},{b:/^\\-\\-\\- +\\d+,\\d+ +\\-\\-\\-\\-$/}]},{cN:\"header\",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\\-\\-\\-/,e:/$/},{b:/^\\*{3} /,e:/$/},{b:/^\\+\\+\\+/,e:/$/},{b:/\\*{5}/,e:/\\*{5}$/}]},{cN:\"addition\",b:\"^\\\\+\",e:\"$\"},{cN:\"deletion\",b:\"^\\\\-\",e:\"$\"},{cN:\"change\",b:\"^\\\\!\",e:\"$\"}]}});hljs.registerLanguage(\"perl\",function(e){var t=\"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when\",r={cN:\"subst\",b:\"[$@]\\\\{\",e:\"\\\\}\",k:t},s={b:\"->{\",e:\"}\"},n={cN:\"variable\",v:[{b:/\\$\\d/},{b:/[\\$\\%\\@](\\^\\w\\b|#\\w+(\\:\\:\\w+)*|{\\w+}|\\w+(\\:\\:\\w*)*)/},{b:/[\\$\\%\\@][^\\s\\w{]/,r:0}]},o={cN:\"comment\",b:\"^(__END__|__DATA__)\",e:\"\\\\n$\",r:5},i=[e.BE,r,n],c=[n,e.HCM,o,{cN:\"comment\",b:\"^\\\\=\\\\w\",e:\"\\\\=cut\",eW:!0},s,{cN:\"string\",c:i,v:[{b:\"q[qwxr]?\\\\s*\\\\(\",e:\"\\\\)\",r:5},{b:\"q[qwxr]?\\\\s*\\\\[\",e:\"\\\\]\",r:5},{b:\"q[qwxr]?\\\\s*\\\\{\",e:\"\\\\}\",r:5},{b:\"q[qwxr]?\\\\s*\\\\|\",e:\"\\\\|\",r:5},{b:\"q[qwxr]?\\\\s*\\\\<\",e:\"\\\\>\",r:5},{b:\"qw\\\\s+q\",e:\"q\",r:5},{b:\"'\",e:\"'\",c:[e.BE]},{b:'\"',e:'\"'},{b:\"`\",e:\"`\",c:[e.BE]},{b:\"{\\\\w+}\",c:[],r:0},{b:\"-?\\\\w+\\\\s*\\\\=\\\\>\",c:[],r:0}]},{cN:\"number\",b:\"(\\\\b0[0-7_]+)|(\\\\b0x[0-9a-fA-F_]+)|(\\\\b[1-9][0-9_]*(\\\\.[0-9_]+)?)|[0_]\\\\b\",r:0},{b:\"(\\\\/\\\\/|\"+e.RSR+\"|\\\\b(split|return|print|reverse|grep)\\\\b)\\\\s*\",k:\"split return print reverse grep\",r:0,c:[e.HCM,o,{cN:\"regexp\",b:\"(s|tr|y)/(\\\\\\\\.|[^/])*/(\\\\\\\\.|[^/])*/[a-z]*\",r:10},{cN:\"regexp\",b:\"(m|qr)?/\",e:\"/[a-z]*\",c:[e.BE],r:0}]},{cN:\"sub\",bK:\"sub\",e:\"(\\\\s*\\\\(.*?\\\\))?[;{]\",r:5},{cN:\"operator\",b:\"-\\\\w\\\\b\",r:0}];return r.c=c,s.c=c,{aliases:[\"pl\"],k:t,c:c}});hljs.registerLanguage(\"makefile\",function(e){var a={cN:\"variable\",b:/\\$\\(/,e:/\\)/,c:[e.BE]};return{aliases:[\"mk\",\"mak\"],c:[e.HCM,{b:/^\\w+\\s*\\W*=/,rB:!0,r:0,starts:{cN:\"constant\",e:/\\s*\\W*=/,eE:!0,starts:{e:/$/,r:0,c:[a]}}},{cN:\"title\",b:/^[\\w]+:\\s*$/},{cN:\"phony\",b:/^\\.PHONY:/,e:/$/,k:\".PHONY\",l:/[\\.\\w]+/},{b:/^\\t+/,e:/$/,r:0,c:[e.QSM,a]}]}});hljs.registerLanguage(\"cs\",function(e){var r=\"abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long null object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async protected public private internal ascending descending from get group into join let orderby partial select set value var where yield\",t=e.IR+\"(<\"+e.IR+\">)?\";return{aliases:[\"csharp\"],k:r,i:/::/,c:[{cN:\"comment\",b:\"///\",e:\"$\",rB:!0,c:[{cN:\"xmlDocTag\",v:[{b:\"///\",r:0},{b:\"<!--|-->\"},{b:\"</?\",e:\">\"}]}]},e.CLCM,e.CBCM,{cN:\"preprocessor\",b:\"#\",e:\"$\",k:\"if else elif endif define undef warning error line region endregion pragma checksum\"},{cN:\"string\",b:'@\"',e:'\"',c:[{b:'\"\"'}]},e.ASM,e.QSM,e.CNM,{bK:\"class namespace interface\",e:/[{;=]/,i:/[^\\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:\"new return throw await\",r:0},{cN:\"function\",b:\"(\"+t+\"\\\\s+)+\"+e.IR+\"\\\\s*\\\\(\",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.IR+\"\\\\s*\\\\(\",rB:!0,c:[e.TM],r:0},{cN:\"params\",b:/\\(/,e:/\\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage(\"json\",function(e){var t={literal:\"true false null\"},i=[e.QSM,e.CNM],l={cN:\"value\",e:\",\",eW:!0,eE:!0,c:i,k:t},c={b:\"{\",e:\"}\",c:[{cN:\"attribute\",b:'\\\\s*\"',e:'\"\\\\s*:\\\\s*',eB:!0,eE:!0,c:[e.BE],i:\"\\\\n\",starts:l}],i:\"\\\\S\"},n={b:\"\\\\[\",e:\"\\\\]\",c:[e.inherit(l,{cN:null})],i:\"\\\\S\"};return i.splice(i.length,0,c,n),{c:i,k:t,i:\"\\\\S\"}});hljs.registerLanguage(\"nginx\",function(e){var r={cN:\"variable\",v:[{b:/\\$\\d+/},{b:/\\$\\{/,e:/}/},{b:\"[\\\\$\\\\@]\"+e.UIR}]},b={eW:!0,l:\"[a-z/_]+\",k:{built_in:\"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll\"},r:0,i:\"=>\",c:[e.HCM,{cN:\"string\",c:[e.BE,r],v:[{b:/\"/,e:/\"/},{b:/'/,e:/'/}]},{cN:\"url\",b:\"([a-z]+):/\",e:\"\\\\s\",eW:!0,eE:!0,c:[r]},{cN:\"regexp\",c:[e.BE,r],v:[{b:\"\\\\s\\\\^\",e:\"\\\\s|{|;\",rE:!0},{b:\"~\\\\*?\\\\s+\",e:\"\\\\s|{|;\",rE:!0},{b:\"\\\\*(\\\\.[a-z\\\\-]+)+\"},{b:\"([a-z\\\\-]+\\\\.)+\\\\*\"}]},{cN:\"number\",b:\"\\\\b\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}(:\\\\d{1,5})?\\\\b\"},{cN:\"number\",b:\"\\\\b\\\\d+[kKmMgGdshdwy]*\\\\b\",r:0},r]};return{aliases:[\"nginxconf\"],c:[e.HCM,{b:e.UIR+\"\\\\s\",e:\";|{\",rB:!0,c:[{cN:\"title\",b:e.UIR,starts:b}],r:0}],i:\"[^\\\\s\\\\}]\"}});hljs.registerLanguage(\"sql\",function(e){var t={cN:\"comment\",b:\"--\",e:\"$\"};return{cI:!0,i:/[<>]/,c:[{cN:\"operator\",bK:\"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate savepoint release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup\",e:/;/,eW:!0,k:{keyword:\"abs absolute acos action add adddate addtime aes_decrypt aes_encrypt after aggregate all allocate alter analyze and any are as asc ascii asin assertion at atan atan2 atn2 authorization authors avg backup before begin benchmark between bin binlog bit_and bit_count bit_length bit_or bit_xor both by cache call cascade cascaded case cast catalog ceil ceiling chain change changed char_length character_length charindex charset check checksum checksum_agg choose close coalesce coercibility collate collation collationproperty column columns columns_updated commit compress concat concat_ws concurrent connect connection connection_id consistent constraint constraints continue contributors conv convert convert_tz corresponding cos cot count count_big crc32 create cross cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime data database databases datalength date_add date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts datetimeoffsetfromparts day dayname dayofmonth dayofweek dayofyear deallocate declare decode default deferrable deferred degrees delayed delete des_decrypt des_encrypt des_key_file desc describe descriptor diagnostics difference disconnect distinct distinctrow div do domain double drop dumpfile each else elt enclosed encode encrypt end end-exec engine engines eomonth errors escape escaped event eventdata events except exception exec execute exists exp explain export_set extended external extract fast fetch field fields find_in_set first first_value floor flush for force foreign format found found_rows from from_base64 from_days from_unixtime full function get get_format get_lock getdate getutcdate global go goto grant grants greatest group group_concat grouping grouping_id gtid_subset gtid_subtract handler having help hex high_priority hosts hour ident_current ident_incr ident_seed identified identity if ifnull ignore iif ilike immediate in index indicator inet6_aton inet6_ntoa inet_aton inet_ntoa infile initially inner innodb input insert install instr intersect into is is_free_lock is_ipv4 is_ipv4_compat is_ipv4_mapped is_not is_not_null is_used_lock isdate isnull isolation join key kill language last last_day last_insert_id last_value lcase lead leading least leaves left len lenght level like limit lines ln load load_file local localtime localtimestamp locate lock log log10 log2 logfile logs low_priority lower lpad ltrim make_set makedate maketime master master_pos_wait match matched max md5 medium merge microsecond mid min minute mod mode module month monthname mutex name_const names national natural nchar next no no_write_to_binlog not now nullif nvarchar oct octet_length of old_password on only open optimize option optionally or ord order outer outfile output pad parse partial partition password patindex percent_rank percentile_cont percentile_disc period_add period_diff pi plugin position pow power pragma precision prepare preserve primary prior privileges procedure procedure_analyze processlist profile profiles public publishingservername purge quarter query quick quote quotename radians rand read references regexp relative relaylog release release_lock rename repair repeat replace replicate reset restore restrict return returns reverse revoke right rlike rollback rollup round row row_count rows rpad rtrim savepoint schema scroll sec_to_time second section select serializable server session session_user set sha sha1 sha2 share show sign sin size slave sleep smalldatetimefromparts snapshot some soname soundex sounds_like space sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sql_variant_property sqlstate sqrt square start starting status std stddev stddev_pop stddev_samp stdev stdevp stop str str_to_date straight_join strcmp string stuff subdate substr substring subtime subtring_index sum switchoffset sysdate sysdatetime sysdatetimeoffset system_user sysutcdatetime table tables tablespace tan temporary terminated tertiary_weights then time time_format time_to_sec timediff timefromparts timestamp timestampadd timestampdiff timezone_hour timezone_minute to to_base64 to_days to_seconds todatetimeoffset trailing transaction translation trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse ucase uncompress uncompressed_length unhex unicode uninstall union unique unix_timestamp unknown unlock update upgrade upped upper usage use user user_resources using utc_date utc_time utc_timestamp uuid uuid_short validate_password_strength value values var var_pop var_samp variables variance varp version view warnings week weekday weekofyear weight_string when whenever where with work write xml xor year yearweek zon\",literal:\"true false null\",built_in:\"array bigint binary bit blob boolean char character date dec decimal float int integer interval number numeric real serial smallint varchar varying int8 serial8 text\"},c:[{cN:\"string\",b:\"'\",e:\"'\",c:[e.BE,{b:\"''\"}]},{cN:\"string\",b:'\"',e:'\"',c:[e.BE,{b:'\"\"'}]},{cN:\"string\",b:\"`\",e:\"`\",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage(\"xml\",function(){var t=\"[A-Za-z0-9\\\\._:-]+\",e={b:/<\\?(php)?(?!\\w)/,e:/\\?>/,sL:\"php\",subLanguageMode:\"continuous\"},c={eW:!0,i:/</,r:0,c:[e,{cN:\"attribute\",b:t,r:0},{b:\"=\",r:0,c:[{cN:\"value\",c:[e],v:[{b:/\"/,e:/\"/},{b:/'/,e:/'/},{b:/[^\\s\\/>]+/}]}]}]};return{aliases:[\"html\",\"xhtml\",\"rss\",\"atom\",\"xsl\",\"plist\"],cI:!0,c:[{cN:\"doctype\",b:\"<!DOCTYPE\",e:\">\",r:10,c:[{b:\"\\\\[\",e:\"\\\\]\"}]},{cN:\"comment\",b:\"<!--\",e:\"-->\",r:10},{cN:\"cdata\",b:\"<\\\\!\\\\[CDATA\\\\[\",e:\"\\\\]\\\\]>\",r:10},{cN:\"tag\",b:\"<style(?=\\\\s|>|$)\",e:\">\",k:{title:\"style\"},c:[c],starts:{e:\"</style>\",rE:!0,sL:\"css\"}},{cN:\"tag\",b:\"<script(?=\\\\s|>|$)\",e:\">\",k:{title:\"script\"},c:[c],starts:{e:\"</script>\",rE:!0,sL:\"javascript\"}},e,{cN:\"pi\",b:/<\\?\\w+/,e:/\\?>/,r:10},{cN:\"tag\",b:\"</?\",e:\"/?>\",c:[{cN:\"title\",b:/[^ \\/><\\n\\t]+/,r:0},c]}]}});hljs.registerLanguage(\"php\",function(e){var c={cN:\"variable\",b:\"\\\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*\"},i={cN:\"preprocessor\",b:/<\\?(php)?|\\?>/},a={cN:\"string\",c:[e.BE,i],v:[{b:'b\"',e:'\"'},{b:\"b'\",e:\"'\"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},n={v:[e.BNM,e.CNM]};return{aliases:[\"php3\",\"php4\",\"php5\",\"php6\"],cI:!0,k:\"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally\",c:[e.CLCM,e.HCM,{cN:\"comment\",b:\"/\\\\*\",e:\"\\\\*/\",c:[{cN:\"phpdoc\",b:\"\\\\s@[A-Za-z]+\"},i]},{cN:\"comment\",b:\"__halt_compiler.+?;\",eW:!0,k:\"__halt_compiler\",l:e.UIR},{cN:\"string\",b:\"<<<['\\\"]?\\\\w+['\\\"]?$\",e:\"^\\\\w+;\",c:[e.BE]},i,c,{b:/->+[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*/},{cN:\"function\",bK:\"function\",e:/[;{]/,eE:!0,i:\"\\\\$|\\\\[|%\",c:[e.UTM,{cN:\"params\",b:\"\\\\(\",e:\"\\\\)\",c:[\"self\",c,e.CBCM,a,n]}]},{cN:\"class\",bK:\"class interface\",e:\"{\",eE:!0,i:/[:\\(\\$\"]/,c:[{bK:\"extends implements\"},e.UTM]},{bK:\"namespace\",e:\";\",i:/[\\.']/,c:[e.UTM]},{bK:\"use\",e:\";\",c:[e.UTM]},{b:\"=>\"},a,n]}});\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/identicon.js ---- */\n\n\n/**\n * Identicon.js v1.0\n * http://github.com/stewartlord/identicon.js\n *\n * Requires PNGLib\n * http://www.xarg.org/download/pnglib.js\n *\n * Copyright 2013, Stewart Lord\n * Released under the BSD license\n * http://www.opensource.org/licenses/bsd-license.php\n */\n\n(function() {\n    Identicon = function(hash, size, margin){\n        this.hash   = hash;\n        this.size   = size   || 64;\n        this.margin = margin || .08;\n    }\n\n    Identicon.prototype = {\n        hash:   null,\n        size:   null,\n        margin: null,\n\n        render: function(){\n            var hash    = this.hash,\n                size    = this.size,\n                margin  = Math.floor(size * this.margin),\n                cell    = Math.floor((size - (margin * 2)) / 5),\n                image   = new PNGlib(size, size, 256);\n\n            // light-grey background\n            var bg      = image.color(240, 240, 240);\n\n            // foreground is last 7 chars as hue at 50% saturation, 70% brightness\n            var rgb     = this.hsl2rgb(parseInt(hash.substr(-7), 16) / 0xfffffff, .5, .7),\n                fg      = image.color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);\n\n            // the first 15 characters of the hash control the pixels (even/odd)\n            // they are drawn down the middle first, then mirrored outwards\n            var i, color;\n            for (i = 0; i < 15; i++) {\n                color = parseInt(hash.charAt(i), 16) % 2 ? bg : fg;\n                if (i < 5) {\n                    this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);\n                } else if (i < 10) {\n                    this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);\n                    this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);\n                } else if (i < 15) {\n                    this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);\n                    this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);\n                }\n            }\n\n            return image;\n        },\n\n        rectangle: function(x, y, w, h, color, image) {\n            var i, j;\n            for (i = x; i < x + w; i++) {\n                for (j = y; j < y + h; j++) {\n                    image.buffer[image.index(i, j)] = color;\n                }\n            }\n        },\n\n        // adapted from: https://gist.github.com/aemkei/1325937\n        hsl2rgb: function(h, s, b){\n            h *= 6;\n            s = [\n                b += s *= b < .5 ? b : 1 - b,\n                b - h % 1 * s * 2,\n                b -= s *= 2,\n                b,\n                b + h % 1 * s,\n                b + s\n            ];\n\n            return[\n                s[ ~~h    % 6 ],  // red\n                s[ (h|16) % 6 ],  // green\n                s[ (h|8)  % 6 ]   // blue\n            ];\n        },\n\n        toString: function(){\n            return this.render().getBase64();\n        }\n    }\n\n    window.Identicon = Identicon;\n})();\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/jquery.cssanim.coffee ---- */\n\n\n(function() {\n  jQuery.fn.cssSlideDown = function() {\n    var elem;\n    elem = this;\n    elem.css({\n      \"opacity\": 0,\n      \"margin-bottom\": 0,\n      \"margin-top\": 0,\n      \"padding-bottom\": 0,\n      \"padding-top\": 0,\n      \"display\": \"none\",\n      \"transform\": \"scale(0.8)\"\n    });\n    setTimeout((function() {\n      var height;\n      elem.css(\"display\", \"\");\n      height = elem.outerHeight();\n      elem.css({\n        \"height\": 0,\n        \"display\": \"\"\n      }).cssLater(\"transition\", \"all 0.3s ease-out\", 20);\n      elem.cssLater({\n        \"height\": height,\n        \"opacity\": 1,\n        \"margin-bottom\": \"\",\n        \"margin-top\": \"\",\n        \"padding-bottom\": \"\",\n        \"padding-top\": \"\",\n        \"transform\": \"scale(1)\"\n      }, null, 40);\n      return elem.cssLater(\"transition\", \"\", 1000, \"noclear\");\n    }), 10);\n    return this;\n  };\n\n  jQuery.fn.fancySlideDown = function() {\n    var elem;\n    elem = this;\n    return elem.css({\n      \"opacity\": 0,\n      \"transform\": \"scale(0.9)\"\n    }).slideDown().animate({\n      \"opacity\": 1,\n      \"scale\": 1\n    }, {\n      \"duration\": 600,\n      \"queue\": false,\n      \"easing\": \"easeOutBack\"\n    });\n  };\n\n  jQuery.fn.fancySlideUp = function() {\n    var elem;\n    elem = this;\n    return elem.delay(600).slideUp(600).animate({\n      \"opacity\": 0,\n      \"scale\": 0.9\n    }, {\n      \"duration\": 600,\n      \"queue\": false,\n      \"easing\": \"easeOutQuad\"\n    });\n  };\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/jquery.csslater.coffee ---- */\n\n\n(function() {\n  var timers;\n\n  timers = {};\n\n  jQuery.fn.readdClass = function(class_name) {\n    var elem;\n    elem = this;\n    elem.removeClass(class_name);\n    setTimeout((function() {\n      return elem.addClass(class_name);\n    }), 1);\n    return this;\n  };\n\n  jQuery.fn.removeLater = function(time) {\n    var elem;\n    if (time == null) {\n      time = 500;\n    }\n    elem = this;\n    setTimeout((function() {\n      return elem.remove();\n    }), time);\n    return this;\n  };\n\n  jQuery.fn.hideLater = function(time) {\n    if (time == null) {\n      time = 500;\n    }\n    this.cssLater(\"display\", \"none\", time);\n    return this;\n  };\n\n  jQuery.fn.addClassLater = function(class_name, time, mode) {\n    var elem;\n    if (time == null) {\n      time = 5;\n    }\n    if (mode == null) {\n      mode = \"clear\";\n    }\n    elem = this;\n    if (timers[class_name] && mode === \"clear\") {\n      clearInterval(timers[class_name]);\n    }\n    timers[class_name] = setTimeout((function() {\n      return elem.addClass(class_name);\n    }), time);\n    return this;\n  };\n\n  jQuery.fn.removeClassLater = function(class_name, time, mode) {\n    var elem;\n    if (time == null) {\n      time = 500;\n    }\n    if (mode == null) {\n      mode = \"clear\";\n    }\n    elem = this;\n    if (timers[class_name] && mode === \"clear\") {\n      clearInterval(timers[class_name]);\n    }\n    timers[class_name] = setTimeout((function() {\n      return elem.removeClass(class_name);\n    }), time);\n    return this;\n  };\n\n  jQuery.fn.cssLater = function(name, val, time, mode) {\n    var elem;\n    if (time == null) {\n      time = 500;\n    }\n    if (mode == null) {\n      mode = \"clear\";\n    }\n    elem = this;\n    if (timers[name] && mode === \"clear\") {\n      clearInterval(timers[name]);\n    }\n    if (time === \"now\") {\n      elem.css(name, val);\n    } else {\n      timers[name] = setTimeout((function() {\n        return elem.css(name, val);\n      }), time);\n    }\n    return this;\n  };\n\n  jQuery.fn.toggleClassLater = function(name, val, time, mode) {\n    var elem;\n    if (time == null) {\n      time = 10;\n    }\n    if (mode == null) {\n      mode = \"clear\";\n    }\n    elem = this;\n    if (timers[name] && mode === \"clear\") {\n      clearInterval(timers[name]);\n    }\n    timers[name] = setTimeout((function() {\n      return elem.toggleClass(name, val);\n    }), time);\n    return this;\n  };\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/marked.min.js ---- */\n\n\n/**\n * marked - a markdown parser\n * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)\n * https://github.com/chjj/marked\n */\n(function(){var block={newline:/^\\n+/,code:/^( {4}[^\\n]+\\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\\n+|$)/,heading:/^ *(#{1,6}) *([^\\n]+?) *#* *(?:\\n+|$)/,nptable:noop,lheading:/^([^\\n]+)\\n *(=|-){2,} *(?:\\n+|$)/,blockquote:/^( *>[^\\n]+(\\n(?!def)[^\\n]+)*\\n*)+/,list:/^( *)(bull) [\\s\\S]+?(?:hr|def|\\n{2,}(?! )(?!\\1bull )\\n*|\\s*$)/,html:/^ *(?:comment|closed|closing) *(?:\\n{2,}|\\s*$)/,def:/^ *\\[([^\\]]+)\\]: *<?([^\\s>]+)>?(?: +[\"(]([^\\n]+)[\")])? *(?:\\n+|$)/,table:noop,paragraph:/^((?:[^\\n]+\\n?(?!hr|heading|lheading|blockquote|tag|def))+)\\n*/,text:/^[^\\n]+/};block.bullet=/(?:[*+-]|\\d+\\.)/;block.item=/^( *)(bull) [^\\n]*(?:\\n(?!\\1bull )[^\\n]*)*/;block.item=replace(block.item,\"gm\")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)(\"hr\",\"\\\\n+(?=\\\\1?(?:[-*_] *){3,}(?:\\\\n+|$))\")(\"def\",\"\\\\n+(?=\"+block.def.source+\")\")();block.blockquote=replace(block.blockquote)(\"def\",block.def)();block._tag=\"(?!(?:\"+\"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code\"+\"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo\"+\"|span|br|wbr|ins|del|img)\\\\b)\\\\w+(?!:/|[^\\\\w\\\\s@]*@)\\\\b\";block.html=replace(block.html)(\"comment\",/<!--[\\s\\S]*?-->/)(\"closed\",/<(tag)[\\s\\S]+?<\\/\\1>/)(\"closing\",/<tag(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)(\"hr\",block.hr)(\"heading\",block.heading)(\"lheading\",block.lheading)(\"blockquote\",block.blockquote)(\"tag\",\"<\"+block._tag)(\"def\",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,}) *(\\S+)? *\\n([\\s\\S]+?)\\s*\\1 *(?:\\n+|$)/,paragraph:/^/});block.gfm.paragraph=replace(block.paragraph)(\"(?!\",\"(?!\"+block.gfm.fences.source.replace(\"\\\\1\",\"\\\\2\")+\"|\"+block.list.source.replace(\"\\\\1\",\"\\\\3\")+\"|\")();block.tables=merge({},block.gfm,{nptable:/^ *(\\S.*\\|.*)\\n *([-:]+ *\\|[-| :]*)\\n((?:.*\\|.*(?:\\n|$))*)\\n*/,table:/^ *\\|(.+)\\n *\\|( *[-:]+[-| :]*)\\n((?: *\\|.*(?:\\n|$))*)\\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\\r\\n|\\r/g,\"\\n\").replace(/\\t/g,\"    \").replace(/\\u00a0/g,\" \").replace(/\\u2424/g,\"\\n\");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,\"\"),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:\"space\"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,\"\");this.tokens.push({type:\"code\",text:!this.options.pedantic?cap.replace(/\\n+$/,\"\"):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"code\",lang:cap[2],text:cap[3]});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"heading\",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:\"table\",header:cap[1].replace(/^ *| *\\| *$/g,\"\").split(/ *\\| */),align:cap[2].replace(/^ *|\\| *$/g,\"\").split(/ *\\| */),cells:cap[3].replace(/\\n$/,\"\").split(\"\\n\")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]=\"right\"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]=\"center\"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]=\"left\"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].split(/ *\\| */)}this.tokens.push(item);continue}if(cap=this.rules.lheading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"heading\",depth:cap[2]===\"=\"?1:2,text:cap[1]});continue}if(cap=this.rules.hr.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"hr\"});continue}if(cap=this.rules.blockquote.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"blockquote_start\"});cap=cap[0].replace(/^ *> ?/gm,\"\");this.token(cap,top,true);this.tokens.push({type:\"blockquote_end\"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:\"list_start\",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i<l;i++){item=cap[i];space=item.length;item=item.replace(/^ *([*+-]|\\d+\\.) +/,\"\");if(~item.indexOf(\"\\n \")){space-=item.length;item=!this.options.pedantic?item.replace(new RegExp(\"^ {1,\"+space+\"}\",\"gm\"),\"\"):item.replace(/^ {1,4}/gm,\"\")}if(this.options.smartLists&&i!==l-1){b=block.bullet.exec(cap[i+1])[0];if(bull!==b&&!(bull.length>1&&b.length>1)){src=cap.slice(i+1).join(\"\\n\")+src;i=l-1}}loose=next||/\\n\\n(?!\\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)===\"\\n\";if(!loose)loose=next}this.tokens.push({type:loose?\"loose_item_start\":\"list_item_start\"});this.token(item,false,bq);this.tokens.push({type:\"list_item_end\"})}this.tokens.push({type:\"list_end\"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?\"paragraph\":\"html\",pre:cap[1]===\"pre\"||cap[1]===\"script\"||cap[1]===\"style\",text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:\"table\",header:cap[1].replace(/^ *| *\\| *$/g,\"\").split(/ *\\| */),align:cap[2].replace(/^ *|\\| *$/g,\"\").split(/ *\\| */),cells:cap[3].replace(/(?: *\\| *)?\\n$/,\"\").split(\"\\n\")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]=\"right\"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]=\"center\"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]=\"left\"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].replace(/^ *\\| *| *\\| *$/g,\"\").split(/ *\\| */)}this.tokens.push(item);continue}if(top&&(cap=this.rules.paragraph.exec(src))){src=src.substring(cap[0].length);this.tokens.push({type:\"paragraph\",text:cap[1].charAt(cap[1].length-1)===\"\\n\"?cap[1].slice(0,-1):cap[1]});continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:\"text\",text:cap[0]});continue}if(src){throw new Error(\"Infinite loop on byte: \"+src.charCodeAt(0))}}return this.tokens};var inline={escape:/^\\\\([\\\\`*{}\\[\\]()#+\\-.!_>])/,autolink:/^<([^ >]+(@|:\\/)[^ >]+)>/,url:noop,tag:/^<!--[\\s\\S]*?-->|^<\\/?\\w+(?:\"[^\"]*\"|'[^']*'|[^'\">])*?>/,link:/^!?\\[(inside)\\]\\(href\\)/,reflink:/^!?\\[(inside)\\]\\s*\\[([^\\]]*)\\]/,nolink:/^!?\\[((?:\\[[^\\]]*\\]|[^\\[\\]])*)\\]/,strong:/^__([\\s\\S]+?)__(?!_)|^\\*\\*([\\s\\S]+?)\\*\\*(?!\\*)/,em:/^\\b_((?:__|[\\s\\S])+?)_\\b|^\\*((?:\\*\\*|[\\s\\S])+?)\\*(?!\\*)/,code:/^(`+)\\s*([\\s\\S]*?[^`])\\s*\\1(?!`)/,br:/^ {2,}\\n(?!\\s*$)/,del:noop,text:/^[\\s\\S]+?(?=[\\\\<!\\[_*`]| {2,}\\n|$)/};inline._inside=/(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*/;inline._href=/\\s*<?([\\s\\S]*?)>?(?:\\s+['\"]([\\s\\S]*?)['\"])?\\s*/;inline.link=replace(inline.link)(\"inside\",inline._inside)(\"href\",inline._href)();inline.reflink=replace(inline.reflink)(\"inside\",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\\S)([\\s\\S]*?\\S)__(?!_)|^\\*\\*(?=\\S)([\\s\\S]*?\\S)\\*\\*(?!\\*)/,em:/^_(?=\\S)([\\s\\S]*?\\S)_(?!_)|^\\*(?=\\S)([\\s\\S]*?\\S)\\*(?!\\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)(\"])\",\"~|])\")(),url:/^(https?:\\/\\/[^\\s<]+[^<.,:;\"')\\]\\s])/,del:/^~~(?=\\S)([\\s\\S]*?\\S)~~/,text:replace(inline.text)(\"]|\",\"~]|\")(\"|\",\"|https?://|\")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)(\"{2,}\",\"*\")(),text:replace(inline.gfm.text)(\"{2,}\",\"*\")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error(\"Tokens array requires a `links` property.\")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out=\"\",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]===\"@\"){text=cap[1].charAt(6)===\":\"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle(\"mailto:\")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^<a /i.test(cap[0])){this.inLink=true}else if(this.inLink&&/^<\\/a>/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\\s+/g,\" \");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=escape(this.smartypants(cap[0]));continue}if(src){throw new Error(\"Infinite loop on byte: \"+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!==\"!\"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/--/g,\"—\").replace(/(^|[-\\u2014/(\\[{\"\\s])'/g,\"$1‘\").replace(/'/g,\"’\").replace(/(^|[-\\u2014/(\\[{\\u2018\\s])\"/g,\"$1“\").replace(/\"/g,\"”\").replace(/\\.{3}/g,\"…\")};InlineLexer.prototype.mangle=function(text){var out=\"\",l=text.length,i=0,ch;for(;i<l;i++){ch=text.charCodeAt(i);if(Math.random()>.5){ch=\"x\"+ch.toString(16)}out+=\"&#\"+ch+\";\"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return\"<pre><code>\"+(escaped?code:escape(code,true))+\"\\n</code></pre>\"}return'<pre><code class=\"'+this.options.langPrefix+escape(lang,true)+'\">'+(escaped?code:escape(code,true))+\"\\n</code></pre>\\n\"};Renderer.prototype.blockquote=function(quote){return\"<blockquote>\\n\"+quote+\"</blockquote>\\n\"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return\"<h\"+level+' id=\"'+this.options.headerPrefix+raw.toLowerCase().replace(/[^\\w]+/g,\"-\")+'\">'+text+\"</h\"+level+\">\\n\"};Renderer.prototype.hr=function(){return this.options.xhtml?\"<hr/>\\n\":\"<hr>\\n\"};Renderer.prototype.list=function(body,ordered){var type=ordered?\"ol\":\"ul\";return\"<\"+type+\">\\n\"+body+\"</\"+type+\">\\n\"};Renderer.prototype.listitem=function(text){return\"<li>\"+text+\"</li>\\n\"};Renderer.prototype.paragraph=function(text){return\"<p>\"+text+\"</p>\\n\"};Renderer.prototype.table=function(header,body){return\"<table>\\n\"+\"<thead>\\n\"+header+\"</thead>\\n\"+\"<tbody>\\n\"+body+\"</tbody>\\n\"+\"</table>\\n\"};Renderer.prototype.tablerow=function(content){return\"<tr>\\n\"+content+\"</tr>\\n\"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?\"th\":\"td\";var tag=flags.align?\"<\"+type+' style=\"text-align:'+flags.align+'\">':\"<\"+type+\">\";return tag+content+\"</\"+type+\">\\n\"};Renderer.prototype.strong=function(text){return\"<strong>\"+text+\"</strong>\"};Renderer.prototype.em=function(text){return\"<em>\"+text+\"</em>\"};Renderer.prototype.codespan=function(text){return\"<code>\"+text+\"</code>\"};Renderer.prototype.br=function(){return this.options.xhtml?\"<br/>\":\"<br>\"};Renderer.prototype.del=function(text){return\"<del>\"+text+\"</del>\"};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\\w:]/g,\"\").toLowerCase()}catch(e){return\"\"}if(prot.indexOf(\"javascript:\")===0){return\"\"}}var out='<a href=\"'+href+'\"';if(title){out+=' title=\"'+title+'\"'}out+=\">\"+text+\"</a>\";return out};Renderer.prototype.image=function(href,title,text){var out='<img src=\"'+href+'\" alt=\"'+text+'\"';if(title){out+=' title=\"'+title+'\"'}out+=this.options.xhtml?\"/>\":\">\";return out};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out=\"\";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type===\"text\"){body+=\"\\n\"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case\"space\":{return\"\"}case\"hr\":{return this.renderer.hr()}case\"heading\":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case\"code\":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case\"table\":{var header=\"\",body=\"\",i,row,cell,flags,j;cell=\"\";for(i=0;i<this.token.header.length;i++){flags={header:true,align:this.token.align[i]};cell+=this.renderer.tablecell(this.inline.output(this.token.header[i]),{header:true,align:this.token.align[i]})}header+=this.renderer.tablerow(cell);for(i=0;i<this.token.cells.length;i++){row=this.token.cells[i];cell=\"\";for(j=0;j<row.length;j++){cell+=this.renderer.tablecell(this.inline.output(row[j]),{header:false,align:this.token.align[j]})}body+=this.renderer.tablerow(cell)}return this.renderer.table(header,body)}case\"blockquote_start\":{var body=\"\";while(this.next().type!==\"blockquote_end\"){body+=this.tok()}return this.renderer.blockquote(body)}case\"list_start\":{var body=\"\",ordered=this.token.ordered;while(this.next().type!==\"list_end\"){body+=this.tok()}return this.renderer.list(body,ordered)}case\"list_item_start\":{var body=\"\";while(this.next().type!==\"list_item_end\"){body+=this.token.type===\"text\"?this.parseText():this.tok()}return this.renderer.listitem(body)}case\"loose_item_start\":{var body=\"\";while(this.next().type!==\"list_item_end\"){body+=this.tok()}return this.renderer.listitem(body)}case\"html\":{var html=!this.token.pre&&!this.options.pedantic?this.inline.output(this.token.text):this.token.text;return this.renderer.html(html)}case\"paragraph\":{return this.renderer.paragraph(this.inline.output(this.token.text))}case\"text\":{return this.renderer.paragraph(this.parseText())}}};function escape(html,encode){return html.replace(!encode?/&(?!#?\\w+;)/g:/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&#39;\")}function unescape(html){return html.replace(/&([#\\w]+);/g,function(_,n){n=n.toLowerCase();if(n===\"colon\")return\":\";if(n.charAt(0)===\"#\"){return n.charAt(1)===\"x\"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return\"\"})}function replace(regex,opt){regex=regex.source;opt=opt||\"\";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\\[])\\^/g,\"$1\");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;i<arguments.length;i++){target=arguments[i];for(key in target){if(Object.prototype.hasOwnProperty.call(target,key)){obj[key]=target[key]}}}return obj}function marked(src,opt,callback){if(callback||typeof opt===\"function\"){if(!callback){callback=opt;opt=null}opt=merge({},marked.defaults,opt||{});var highlight=opt.highlight,tokens,pending,i=0;try{tokens=Lexer.lex(src,opt)}catch(e){return callback(e)}pending=tokens.length;var done=function(err){if(err){opt.highlight=highlight;return callback(err)}var out;try{out=Parser.parse(tokens,opt)}catch(e){err=e}opt.highlight=highlight;return err?callback(err):callback(null,out)};if(!highlight||highlight.length<3){return done()}delete opt.highlight;if(!pending)return done();for(;i<tokens.length;i++){(function(token){if(token.type!==\"code\"){return--pending||done()}return highlight(token.text,token.lang,function(err,code){if(err)return done(err);if(code==null||code===token.text){return--pending||done()}token.text=code;token.escaped=true;--pending||done()})})(tokens[i])}return}try{if(opt)opt=merge({},marked.defaults,opt);return Parser.parse(Lexer.lex(src,opt),opt)}catch(e){e.message+=\"\\nPlease report this to https://github.com/chjj/marked.\";if((opt||marked.defaults).silent){return\"<p>An error occured:</p><pre>\"+escape(e.message+\"\",true)+\"</pre>\"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,smartLists:false,silent:false,highlight:null,langPrefix:\"lang-\",smartypants:false,headerPrefix:\"\",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!==\"undefined\"&&typeof exports===\"object\"){module.exports=marked}else if(typeof define===\"function\"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!==\"undefined\"?window:global)}());\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/lib/pnglib.js ---- */\n\n\n/**\n* A handy class to calculate color values.\n*\n* @version 1.0\n* @author Robert Eisele <robert@xarg.org>\n* @copyright Copyright (c) 2010, Robert Eisele\n* @link http://www.xarg.org/2010/03/generate-client-side-png-files-using-javascript/\n* @license http://www.opensource.org/licenses/bsd-license.php BSD License\n*\n*/\n\n(function() {\n\n\t// helper functions for that ctx\n\tfunction write(buffer, offs) {\n\t\tfor (var i = 2; i < arguments.length; i++) {\n\t\t\tfor (var j = 0; j < arguments[i].length; j++) {\n\t\t\t\tbuffer[offs++] = arguments[i].charAt(j);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction byte2(w) {\n\t\treturn String.fromCharCode((w >> 8) & 255, w & 255);\n\t}\n\n\tfunction byte4(w) {\n\t\treturn String.fromCharCode((w >> 24) & 255, (w >> 16) & 255, (w >> 8) & 255, w & 255);\n\t}\n\n\tfunction byte2lsb(w) {\n\t\treturn String.fromCharCode(w & 255, (w >> 8) & 255);\n\t}\n\n\twindow.PNGlib = function(width,height,depth) {\n\n\t\tthis.width   = width;\n\t\tthis.height  = height;\n\t\tthis.depth   = depth;\n\n\t\t// pixel data and row filter identifier size\n\t\tthis.pix_size = height * (width + 1);\n\n\t\t// deflate header, pix_size, block headers, adler32 checksum\n\t\tthis.data_size = 2 + this.pix_size + 5 * Math.floor((0xfffe + this.pix_size) / 0xffff) + 4;\n\n\t\t// offsets and sizes of Png chunks\n\t\tthis.ihdr_offs = 0;\t\t\t\t\t\t\t\t\t// IHDR offset and size\n\t\tthis.ihdr_size = 4 + 4 + 13 + 4;\n\t\tthis.plte_offs = this.ihdr_offs + this.ihdr_size;\t// PLTE offset and size\n\t\tthis.plte_size = 4 + 4 + 3 * depth + 4;\n\t\tthis.trns_offs = this.plte_offs + this.plte_size;\t// tRNS offset and size\n\t\tthis.trns_size = 4 + 4 + depth + 4;\n\t\tthis.idat_offs = this.trns_offs + this.trns_size;\t// IDAT offset and size\n\t\tthis.idat_size = 4 + 4 + this.data_size + 4;\n\t\tthis.iend_offs = this.idat_offs + this.idat_size;\t// IEND offset and size\n\t\tthis.iend_size = 4 + 4 + 4;\n\t\tthis.buffer_size  = this.iend_offs + this.iend_size;\t// total PNG size\n\n\t\tthis.buffer  = new Array();\n\t\tthis.palette = new Object();\n\t\tthis.pindex  = 0;\n\n\t\tvar _crc32 = new Array();\n\n\t\t// initialize buffer with zero bytes\n\t\tfor (var i = 0; i < this.buffer_size; i++) {\n\t\t\tthis.buffer[i] = \"\\x00\";\n\t\t}\n\n\t\t// initialize non-zero elements\n\t\twrite(this.buffer, this.ihdr_offs, byte4(this.ihdr_size - 12), 'IHDR', byte4(width), byte4(height), \"\\x08\\x03\");\n\t\twrite(this.buffer, this.plte_offs, byte4(this.plte_size - 12), 'PLTE');\n\t\twrite(this.buffer, this.trns_offs, byte4(this.trns_size - 12), 'tRNS');\n\t\twrite(this.buffer, this.idat_offs, byte4(this.idat_size - 12), 'IDAT');\n\t\twrite(this.buffer, this.iend_offs, byte4(this.iend_size - 12), 'IEND');\n\n\t\t// initialize deflate header\n\t\tvar header = ((8 + (7 << 4)) << 8) | (3 << 6);\n\t\theader+= 31 - (header % 31);\n\n\t\twrite(this.buffer, this.idat_offs + 8, byte2(header));\n\n\t\t// initialize deflate block headers\n\t\tfor (var i = 0; (i << 16) - 1 < this.pix_size; i++) {\n\t\t\tvar size, bits;\n\t\t\tif (i + 0xffff < this.pix_size) {\n\t\t\t\tsize = 0xffff;\n\t\t\t\tbits = \"\\x00\";\n\t\t\t} else {\n\t\t\t\tsize = this.pix_size - (i << 16) - i;\n\t\t\t\tbits = \"\\x01\";\n\t\t\t}\n\t\t\twrite(this.buffer, this.idat_offs + 8 + 2 + (i << 16) + (i << 2), bits, byte2lsb(size), byte2lsb(~size));\n\t\t}\n\n\t\t/* Create crc32 lookup table */\n\t\tfor (var i = 0; i < 256; i++) {\n\t\t\tvar c = i;\n\t\t\tfor (var j = 0; j < 8; j++) {\n\t\t\t\tif (c & 1) {\n\t\t\t\t\tc = -306674912 ^ ((c >> 1) & 0x7fffffff);\n\t\t\t\t} else {\n\t\t\t\t\tc = (c >> 1) & 0x7fffffff;\n\t\t\t\t}\n\t\t\t}\n\t\t\t_crc32[i] = c;\n\t\t}\n\n\t\t// compute the index into a png for a given pixel\n\t\tthis.index = function(x,y) {\n\t\t\tvar i = y * (this.width + 1) + x + 1;\n\t\t\tvar j = this.idat_offs + 8 + 2 + 5 * Math.floor((i / 0xffff) + 1) + i;\n\t\t\treturn j;\n\t\t}\n\n\t\t// convert a color and build up the palette\n\t\tthis.color = function(red, green, blue, alpha) {\n\n\t\t\talpha = alpha >= 0 ? alpha : 255;\n\t\t\tvar color = (((((alpha << 8) | red) << 8) | green) << 8) | blue;\n\n\t\t\tif (typeof this.palette[color] == \"undefined\") {\n\t\t\t\tif (this.pindex == this.depth) return \"\\x00\";\n\n\t\t\t\tvar ndx = this.plte_offs + 8 + 3 * this.pindex;\n\n\t\t\t\tthis.buffer[ndx + 0] = String.fromCharCode(red);\n\t\t\t\tthis.buffer[ndx + 1] = String.fromCharCode(green);\n\t\t\t\tthis.buffer[ndx + 2] = String.fromCharCode(blue);\n\t\t\t\tthis.buffer[this.trns_offs+8+this.pindex] = String.fromCharCode(alpha);\n\n\t\t\t\tthis.palette[color] = String.fromCharCode(this.pindex++);\n\t\t\t}\n\t\t\treturn this.palette[color];\n\t\t}\n\n\t\t// output a PNG string, Base64 encoded\n\t\tthis.getBase64 = function() {\n\n\t\t\tvar s = this.getDump();\n\n\t\t\tvar ch = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n\t\t\tvar c1, c2, c3, e1, e2, e3, e4;\n\t\t\tvar l = s.length;\n\t\t\tvar i = 0;\n\t\t\tvar r = \"\";\n\n\t\t\tdo {\n\t\t\t\tc1 = s.charCodeAt(i);\n\t\t\t\te1 = c1 >> 2;\n\t\t\t\tc2 = s.charCodeAt(i+1);\n\t\t\t\te2 = ((c1 & 3) << 4) | (c2 >> 4);\n\t\t\t\tc3 = s.charCodeAt(i+2);\n\t\t\t\tif (l < i+2) { e3 = 64; } else { e3 = ((c2 & 0xf) << 2) | (c3 >> 6); }\n\t\t\t\tif (l < i+3) { e4 = 64; } else { e4 = c3 & 0x3f; }\n\t\t\t\tr+= ch.charAt(e1) + ch.charAt(e2) + ch.charAt(e3) + ch.charAt(e4);\n\t\t\t} while ((i+= 3) < l);\n\t\t\treturn r;\n\t\t}\n\n\t\t// output a PNG string\n\t\tthis.getDump = function() {\n\n\t\t\t// compute adler32 of output pixels + row filter bytes\n\t\t\tvar BASE = 65521; /* largest prime smaller than 65536 */\n\t\t\tvar NMAX = 5552;  /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */\n\t\t\tvar s1 = 1;\n\t\t\tvar s2 = 0;\n\t\t\tvar n = NMAX;\n\n\t\t\tfor (var y = 0; y < this.height; y++) {\n\t\t\t\tfor (var x = -1; x < this.width; x++) {\n\t\t\t\t\ts1+= this.buffer[this.index(x, y)].charCodeAt(0);\n\t\t\t\t\ts2+= s1;\n\t\t\t\t\tif ((n-= 1) == 0) {\n\t\t\t\t\t\ts1%= BASE;\n\t\t\t\t\t\ts2%= BASE;\n\t\t\t\t\t\tn = NMAX;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\ts1%= BASE;\n\t\t\ts2%= BASE;\n\t\t\twrite(this.buffer, this.idat_offs + this.idat_size - 8, byte4((s2 << 16) | s1));\n\n\t\t\t// compute crc32 of the PNG chunks\n\t\t\tfunction crc32(png, offs, size) {\n\t\t\t\tvar crc = -1;\n\t\t\t\tfor (var i = 4; i < size-4; i += 1) {\n\t\t\t\t\tcrc = _crc32[(crc ^ png[offs+i].charCodeAt(0)) & 0xff] ^ ((crc >> 8) & 0x00ffffff);\n\t\t\t\t}\n\t\t\t\twrite(png, offs+size-4, byte4(crc ^ -1));\n\t\t\t}\n\n\t\t\tcrc32(this.buffer, this.ihdr_offs, this.ihdr_size);\n\t\t\tcrc32(this.buffer, this.plte_offs, this.plte_size);\n\t\t\tcrc32(this.buffer, this.trns_offs, this.trns_size);\n\t\t\tcrc32(this.buffer, this.idat_offs, this.idat_size);\n\t\t\tcrc32(this.buffer, this.iend_offs, this.iend_size);\n\n\t\t\t// convert PNG to string\n\t\t\treturn \"\\211PNG\\r\\n\\032\\n\"+this.buffer.join('');\n\t\t}\n\t}\n\n})();\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/utils/Class.coffee ---- */\n\n\n(function() {\n  var Class,\n    __slice = [].slice;\n\n  Class = (function() {\n    function Class() {}\n\n    Class.prototype.trace = true;\n\n    Class.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n      if (!this.trace) {\n        return;\n      }\n      if (typeof console === 'undefined') {\n        return;\n      }\n      args.unshift(\"[\" + this.constructor.name + \"]\");\n      console.log.apply(console, args);\n      return this;\n    };\n\n    Class.prototype.logStart = function() {\n      var args, name;\n      name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n      if (!this.trace) {\n        return;\n      }\n      this.logtimers || (this.logtimers = {});\n      this.logtimers[name] = +(new Date);\n      if (args.length > 0) {\n        this.log.apply(this, [\"\" + name].concat(__slice.call(args), [\"(started)\"]));\n      }\n      return this;\n    };\n\n    Class.prototype.logEnd = function() {\n      var args, ms, name;\n      name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];\n      ms = +(new Date) - this.logtimers[name];\n      this.log.apply(this, [\"\" + name].concat(__slice.call(args), [\"(Done in \" + ms + \"ms)\"]));\n      return this;\n    };\n\n    return Class;\n\n  })();\n\n  window.Class = Class;\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/utils/InlineEditor.coffee ---- */\n\n\n(function() {\n  var InlineEditor,\n    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };\n\n  InlineEditor = (function() {\n    function InlineEditor(_at_elem, _at_getContent, _at_saveContent, _at_getObject) {\n      this.elem = _at_elem;\n      this.getContent = _at_getContent;\n      this.saveContent = _at_saveContent;\n      this.getObject = _at_getObject;\n      this.cancelEdit = __bind(this.cancelEdit, this);\n      this.deleteObject = __bind(this.deleteObject, this);\n      this.saveEdit = __bind(this.saveEdit, this);\n      this.stopEdit = __bind(this.stopEdit, this);\n      this.startEdit = __bind(this.startEdit, this);\n      this.edit_button = $(\"<a href='#Edit' class='editable-edit icon-edit'></a>\");\n      this.edit_button.on(\"click\", this.startEdit);\n      this.elem.addClass(\"editable\").before(this.edit_button);\n      this.editor = null;\n      this.elem.on(\"mouseenter\", (function(_this) {\n        return function(e) {\n          var scrolltop, top;\n          _this.edit_button.css(\"opacity\", \"0.4\");\n          scrolltop = $(window).scrollTop();\n          top = _this.edit_button.offset().top - parseInt(_this.edit_button.css(\"margin-top\"));\n          if (scrolltop > top) {\n            return _this.edit_button.css(\"margin-top\", scrolltop - top + e.clientY - 20);\n          } else {\n            return _this.edit_button.css(\"margin-top\", \"\");\n          }\n        };\n      })(this));\n      this.elem.on(\"mouseleave\", (function(_this) {\n        return function() {\n          return _this.edit_button.css(\"opacity\", \"\");\n        };\n      })(this));\n      if (this.elem.is(\":hover\")) {\n        this.elem.trigger(\"mouseenter\");\n      }\n    }\n\n    InlineEditor.prototype.startEdit = function() {\n      var _i, _results;\n      this.content_before = this.elem.html();\n      this.editor = $(\"<textarea class='editor'></textarea>\");\n      this.editor.css(\"outline\", \"10000px solid rgba(255,255,255,0)\").cssLater(\"transition\", \"outline 0.3s\", 5).cssLater(\"outline\", \"10000px solid rgba(255,255,255,0.9)\", 10);\n      this.editor.val(this.getContent(this.elem, \"raw\"));\n      this.elem.after(this.editor);\n      this.elem.html((function() {\n        _results = [];\n        for (_i = 1; _i <= 50; _i++){ _results.push(_i); }\n        return _results;\n      }).apply(this).join(\"fill the width\"));\n      this.copyStyle(this.elem, this.editor);\n      this.elem.html(this.content_before);\n      this.autoExpand(this.editor);\n      this.elem.css(\"display\", \"none\");\n      if ($(window).scrollTop() === 0) {\n        this.editor[0].selectionEnd = 0;\n        this.editor.focus();\n      }\n      $(\".editable-edit\").css(\"display\", \"none\");\n      $(\".editbar\").css(\"display\", \"inline-block\").addClassLater(\"visible\", 10);\n      $(\".publishbar\").css(\"opacity\", 0);\n      $(\".editbar .object\").text(this.getObject(this.elem).data(\"object\") + \".\" + this.elem.data(\"editable\"));\n      $(\".editbar .button\").removeClass(\"loading\");\n      $(\".editbar .save\").off(\"click\").on(\"click\", this.saveEdit);\n      $(\".editbar .delete\").off(\"click\").on(\"click\", this.deleteObject);\n      $(\".editbar .cancel\").off(\"click\").on(\"click\", this.cancelEdit);\n      if (this.getObject(this.elem).data(\"deletable\")) {\n        $(\".editbar .delete\").css(\"display\", \"\").html(\"Delete \" + this.getObject(this.elem).data(\"object\").split(\":\")[0]);\n      } else {\n        $(\".editbar .delete\").css(\"display\", \"none\");\n      }\n      window.onbeforeunload = function() {\n        return 'Your unsaved blog changes will be lost!';\n      };\n      return false;\n    };\n\n    InlineEditor.prototype.stopEdit = function() {\n      this.editor.remove();\n      this.editor = null;\n      this.elem.css(\"display\", \"\");\n      $(\".editable-edit\").css(\"display\", \"\");\n      $(\".editbar\").cssLater(\"display\", \"none\", 1000).removeClass(\"visible\");\n      $(\".publishbar\").css(\"opacity\", 1);\n      return window.onbeforeunload = null;\n    };\n\n    InlineEditor.prototype.saveEdit = function() {\n      var content;\n      content = this.editor.val();\n      $(\".editbar .save\").addClass(\"loading\");\n      this.saveContent(this.elem, content, (function(_this) {\n        return function(content_html) {\n          if (content_html) {\n            $(\".editbar .save\").removeClass(\"loading\");\n            _this.stopEdit();\n            if (typeof content_html === \"string\") {\n              _this.elem.html(content_html);\n            }\n            return $('pre code').each(function(i, block) {\n              return hljs.highlightBlock(block);\n            });\n          } else {\n            return $(\".editbar .save\").removeClass(\"loading\");\n          }\n        };\n      })(this));\n      return false;\n    };\n\n    InlineEditor.prototype.deleteObject = function() {\n      var object_type;\n      object_type = this.getObject(this.elem).data(\"object\").split(\":\")[0];\n      Page.cmd(\"wrapperConfirm\", [\"Are you sure you sure to delete this \" + object_type + \"?\", \"Delete\"], (function(_this) {\n        return function(confirmed) {\n          $(\".editbar .delete\").addClass(\"loading\");\n          return Page.saveContent(_this.getObject(_this.elem), null, function() {\n            return _this.stopEdit();\n          });\n        };\n      })(this));\n      return false;\n    };\n\n    InlineEditor.prototype.cancelEdit = function() {\n      this.stopEdit();\n      this.elem.html(this.content_before);\n      $('pre code').each(function(i, block) {\n        return hljs.highlightBlock(block);\n      });\n      return false;\n    };\n\n    InlineEditor.prototype.copyStyle = function(elem_from, elem_to) {\n      var from_style;\n      elem_to.addClass(elem_from[0].className);\n      from_style = getComputedStyle(elem_from[0]);\n      elem_to.css({\n        fontFamily: from_style.fontFamily,\n        fontSize: from_style.fontSize,\n        fontWeight: from_style.fontWeight,\n        marginTop: from_style.marginTop,\n        marginRight: from_style.marginRight,\n        marginBottom: from_style.marginBottom,\n        marginLeft: from_style.marginLeft,\n        paddingTop: from_style.paddingTop,\n        paddingRight: from_style.paddingRight,\n        paddingBottom: from_style.paddingBottom,\n        paddingLeft: from_style.paddingLeft,\n        lineHeight: from_style.lineHeight,\n        textAlign: from_style.textAlign,\n        color: from_style.color,\n        letterSpacing: from_style.letterSpacing\n      });\n      if (elem_from.innerWidth() < 1000) {\n        return elem_to.css(\"minWidth\", elem_from.innerWidth());\n      }\n    };\n\n    InlineEditor.prototype.autoExpand = function(elem) {\n      var editor;\n      editor = elem[0];\n      elem.height(1);\n      elem.on(\"input\", function() {\n        if (editor.scrollHeight > elem.height()) {\n          return elem.height(1).height(editor.scrollHeight + parseFloat(elem.css(\"borderTopWidth\")) + parseFloat(elem.css(\"borderBottomWidth\")));\n        }\n      });\n      elem.trigger(\"input\");\n      return elem.on('keydown', function(e) {\n        var s, val;\n        if (e.which === 9) {\n          e.preventDefault();\n          s = this.selectionStart;\n          val = elem.val();\n          elem.val(val.substring(0, this.selectionStart) + \"\\t\" + val.substring(this.selectionEnd));\n          return this.selectionEnd = s + 1;\n        }\n      });\n    };\n\n    return InlineEditor;\n\n  })();\n\n  window.InlineEditor = InlineEditor;\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/utils/RateLimit.coffee ---- */\n\n\n(function() {\n  var call_after_interval, limits;\n\n  limits = {};\n\n  call_after_interval = {};\n\n  window.RateLimit = function(interval, fn) {\n    if (!limits[fn]) {\n      call_after_interval[fn] = false;\n      fn();\n      return limits[fn] = setTimeout((function() {\n        if (call_after_interval[fn]) {\n          fn();\n        }\n        delete limits[fn];\n        return delete call_after_interval[fn];\n      }), interval);\n    } else {\n      return call_after_interval[fn] = true;\n    }\n  };\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/utils/Text.coffee ---- */\n\n\n(function() {\n  var Renderer, Text,\n    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    __hasProp = {}.hasOwnProperty,\n    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };\n\n  Renderer = (function(_super) {\n    __extends(Renderer, _super);\n\n    function Renderer() {\n      return Renderer.__super__.constructor.apply(this, arguments);\n    }\n\n    Renderer.prototype.image = function(href, title, text) {\n      return \"<code>![\" + text + \"](\" + href + \")</code>\";\n    };\n\n    return Renderer;\n\n  })(marked.Renderer);\n\n  Text = (function() {\n    function Text() {\n      this.toUrl = __bind(this.toUrl, this);\n    }\n\n    Text.prototype.toColor = function(text) {\n      var color, hash, i, value, _i, _j, _ref;\n      hash = 0;\n      for (i = _i = 0, _ref = text.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {\n        hash = text.charCodeAt(i) + ((hash << 5) - hash);\n      }\n      color = '#';\n      return \"hsl(\" + (hash % 360) + \",30%,50%)\";\n      for (i = _j = 0; _j <= 2; i = ++_j) {\n        value = (hash >> (i * 8)) & 0xFF;\n        color += ('00' + value.toString(16)).substr(-2);\n      }\n      return color;\n    };\n\n    Text.prototype.toMarked = function(text, options) {\n      if (options == null) {\n        options = {};\n      }\n      options[\"gfm\"] = true;\n      options[\"breaks\"] = true;\n      if (options.sanitize) {\n        options[\"renderer\"] = renderer;\n      }\n      text = marked(text, options);\n      return this.fixHtmlLinks(text);\n    };\n\n    Text.prototype.fixHtmlLinks = function(text) {\n      if (window.is_proxy) {\n        return text.replace(/href=\"http:\\/\\/(127.0.0.1|localhost):43110/g, 'href=\"http://zero');\n      } else {\n        return text.replace(/href=\"http:\\/\\/(127.0.0.1|localhost):43110/g, 'href=\"');\n      }\n    };\n\n    Text.prototype.fixLink = function(link) {\n      if (window.is_proxy) {\n        return link.replace(/http:\\/\\/(127.0.0.1|localhost):43110/, 'http://zero');\n      } else {\n        return link.replace(/http:\\/\\/(127.0.0.1|localhost):43110/, '');\n      }\n    };\n\n    Text.prototype.toUrl = function(text) {\n      return text.replace(/[^A-Za-z0-9]/g, \"+\").replace(/[+]+/g, \"+\").replace(/[+]+$/, \"\");\n    };\n\n    return Text;\n\n  })();\n\n  window.is_proxy = window.location.pathname === \"/\";\n\n  window.renderer = new Renderer();\n\n  window.Text = new Text();\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/utils/Time.coffee ---- */\n\n\n(function() {\n  var Time;\n\n  Time = (function() {\n    function Time() {}\n\n    Time.prototype.since = function(time) {\n      var back, now, secs;\n      now = +(new Date) / 1000;\n      secs = now - time;\n      if (secs < 60) {\n        back = \"Just now\";\n      } else if (secs < 60 * 60) {\n        back = (Math.round(secs / 60)) + \" minutes ago\";\n      } else if (secs < 60 * 60 * 24) {\n        back = (Math.round(secs / 60 / 60)) + \" hours ago\";\n      } else if (secs < 60 * 60 * 24 * 3) {\n        back = (Math.round(secs / 60 / 60 / 24)) + \" days ago\";\n      } else {\n        back = \"on \" + this.date(time);\n      }\n      back = back.replace(/1 ([a-z]+)s/, \"1 $1\");\n      return back;\n    };\n\n    Time.prototype.date = function(timestamp, format) {\n      var display, parts;\n      if (format == null) {\n        format = \"short\";\n      }\n      parts = (new Date(timestamp * 1000)).toString().split(\" \");\n      if (format === \"short\") {\n        display = parts.slice(1, 4);\n      } else {\n        display = parts.slice(1, 5);\n      }\n      return display.join(\" \").replace(/( [0-9]{4})/, \",$1\");\n    };\n\n    Time.prototype.timestamp = function(date) {\n      if (date == null) {\n        date = \"\";\n      }\n      if (date === \"now\" || date === \"\") {\n        return parseInt(+(new Date) / 1000);\n      } else {\n        return parseInt(Date.parse(date) / 1000);\n      }\n    };\n\n    Time.prototype.readtime = function(text) {\n      var chars;\n      chars = text.length;\n      if (chars > 1500) {\n        return parseInt(chars / 1500) + \" min read\";\n      } else {\n        return \"less than 1 min read\";\n      }\n    };\n\n    return Time;\n\n  })();\n\n  window.Time = new Time;\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/utils/ZeroFrame.coffee ---- */\n\n\n(function() {\n  var ZeroFrame,\n    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    __hasProp = {}.hasOwnProperty;\n\n  ZeroFrame = (function(_super) {\n    __extends(ZeroFrame, _super);\n\n    function ZeroFrame(url) {\n      this.onCloseWebsocket = __bind(this.onCloseWebsocket, this);\n      this.onOpenWebsocket = __bind(this.onOpenWebsocket, this);\n      this.route = __bind(this.route, this);\n      this.onMessage = __bind(this.onMessage, this);\n      this.url = url;\n      this.waiting_cb = {};\n      this.connect();\n      this.next_message_id = 1;\n      this.init();\n    }\n\n    ZeroFrame.prototype.init = function() {\n      return this;\n    };\n\n    ZeroFrame.prototype.connect = function() {\n      this.target = window.parent;\n      window.addEventListener(\"message\", this.onMessage, false);\n      return this.cmd(\"innerReady\");\n    };\n\n    ZeroFrame.prototype.onMessage = function(e) {\n      var cmd, message;\n      message = e.data;\n      cmd = message.cmd;\n      if (cmd === \"response\") {\n        if (this.waiting_cb[message.to] != null) {\n          return this.waiting_cb[message.to](message.result);\n        } else {\n          return this.log(\"Websocket callback not found:\", message);\n        }\n      } else if (cmd === \"wrapperReady\") {\n        return this.cmd(\"innerReady\");\n      } else if (cmd === \"ping\") {\n        return this.response(message.id, \"pong\");\n      } else if (cmd === \"wrapperOpenedWebsocket\") {\n        return this.onOpenWebsocket();\n      } else if (cmd === \"wrapperClosedWebsocket\") {\n        return this.onCloseWebsocket();\n      } else {\n        return this.onRequest(cmd, message);\n      }\n    };\n\n    ZeroFrame.prototype.route = function(cmd, message) {\n      return this.log(\"Unknown command\", message);\n    };\n\n    ZeroFrame.prototype.response = function(to, result) {\n      return this.send({\n        \"cmd\": \"response\",\n        \"to\": to,\n        \"result\": result\n      });\n    };\n\n    ZeroFrame.prototype.cmd = function(cmd, params, cb) {\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      return this.send({\n        \"cmd\": cmd,\n        \"params\": params\n      }, cb);\n    };\n\n    ZeroFrame.prototype.send = function(message, cb) {\n      if (cb == null) {\n        cb = null;\n      }\n      message.id = this.next_message_id;\n      this.next_message_id += 1;\n      this.target.postMessage(message, \"*\");\n      if (cb) {\n        return this.waiting_cb[message.id] = cb;\n      }\n    };\n\n    ZeroFrame.prototype.onOpenWebsocket = function() {\n      return this.log(\"Websocket open\");\n    };\n\n    ZeroFrame.prototype.onCloseWebsocket = function() {\n      return this.log(\"Websocket close\");\n    };\n\n    return ZeroFrame;\n\n  })(Class);\n\n  window.ZeroFrame = ZeroFrame;\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/Comments.coffee ---- */\n\n\n(function() {\n  var Comments,\n    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    __hasProp = {}.hasOwnProperty;\n\n  Comments = (function(_super) {\n    __extends(Comments, _super);\n\n    function Comments() {\n      return Comments.__super__.constructor.apply(this, arguments);\n    }\n\n    Comments.prototype.pagePost = function(post_id, cb) {\n      if (cb == null) {\n        cb = false;\n      }\n      this.post_id = post_id;\n      this.rules = {};\n      $(\".button-submit-comment\").on(\"click\", (function(_this) {\n        return function() {\n          _this.submitComment();\n          return false;\n        };\n      })(this));\n      this.loadComments(\"noanim\", cb);\n      this.autoExpand($(\".comment-textarea\"));\n      return $(\".certselect\").on(\"click\", (function(_this) {\n        return function() {\n          if (Page.server_info.rev < 160) {\n            Page.cmd(\"wrapperNotification\", [\"error\", \"Comments requires at least ZeroNet 0.3.0 Please upgade!\"]);\n          } else {\n            Page.cmd(\"certSelect\", [[\"zeroid.bit\"]]);\n          }\n          return false;\n        };\n      })(this));\n    };\n\n    Comments.prototype.loadComments = function(type, cb) {\n      var query;\n      if (type == null) {\n        type = \"show\";\n      }\n      if (cb == null) {\n        cb = false;\n      }\n      query = \"SELECT comment.*, json_content.json_id AS content_json_id, keyvalue.value AS cert_user_id, json.directory, (SELECT COUNT(*) FROM comment_vote WHERE comment_vote.comment_uri = comment.comment_id || '@' || json.directory)+1 AS votes FROM comment LEFT JOIN json USING (json_id) LEFT JOIN json AS json_content ON (json_content.directory = json.directory AND json_content.file_name='content.json') LEFT JOIN keyvalue ON (keyvalue.json_id = json_content.json_id AND key = 'cert_user_id') WHERE post_id = \" + this.post_id + \" ORDER BY date_added DESC\";\n      return Page.cmd(\"dbQuery\", query, (function(_this) {\n        return function(comments) {\n          var comment, comment_address, elem, user_address, _i, _len;\n          $(\".comments-num\").text(comments.length);\n          for (_i = 0, _len = comments.length; _i < _len; _i++) {\n            comment = comments[_i];\n            user_address = comment.directory.replace(\"users/\", \"\");\n            comment_address = comment.comment_id + \"_\" + user_address;\n            elem = $(\"#comment_\" + comment_address);\n            if (elem.length === 0) {\n              elem = $(\".comment.template\").clone().removeClass(\"template\").attr(\"id\", \"comment_\" + comment_address).data(\"post_id\", _this.post_id);\n              if (type !== \"noanim\") {\n                elem.cssSlideDown();\n              }\n              $(\".reply\", elem).on(\"click\", function(e) {\n                return _this.buttonReply($(e.target).parents(\".comment\"));\n              });\n            }\n            _this.applyCommentData(elem, comment);\n            elem.appendTo(\".comments\");\n          }\n          return setTimeout((function() {\n            return Page.addInlineEditors();\n          }), 1000);\n        };\n      })(this));\n    };\n\n    Comments.prototype.applyCommentData = function(elem, comment) {\n      var cert_domain, user_address, user_name, _ref;\n      _ref = comment.cert_user_id.split(\"@\"), user_name = _ref[0], cert_domain = _ref[1];\n      user_address = comment.directory.replace(\"users/\", \"\");\n      $(\".comment-body\", elem).html(Text.toMarked(comment.body, {\n        \"sanitize\": true\n      }));\n      $(\".user_name\", elem).text(user_name).css({\n        \"color\": Text.toColor(comment.cert_user_id)\n      }).attr(\"title\", user_name + \"@\" + cert_domain + \": \" + user_address);\n      $(\".added\", elem).text(Time.since(comment.date_added)).attr(\"title\", Time.date(comment.date_added, \"long\"));\n      if (user_address === Page.site_info.auth_address) {\n        $(elem).attr(\"data-object\", \"Comment:\" + comment.comment_id).attr(\"data-deletable\", \"yes\");\n        return $(\".comment-body\", elem).attr(\"data-editable\", \"body\").data(\"content\", comment.body);\n      }\n    };\n\n    Comments.prototype.buttonReply = function(elem) {\n      var body_add, elem_quote, post_id, user_name;\n      this.log(\"Reply to\", elem);\n      user_name = $(\".user_name\", elem).text();\n      post_id = elem.attr(\"id\");\n      body_add = \"> [\" + user_name + \"](\\#\" + post_id + \"): \";\n      elem_quote = $(\".comment-body\", elem).clone();\n      $(\"blockquote\", elem_quote).remove();\n      body_add += elem_quote.text().trim(\"\\n\").replace(/\\n/g, \"\\n> \");\n      body_add += \"\\n\\n\";\n      $(\".comment-new .comment-textarea\").val($(\".comment-new .comment-textarea\").val() + body_add);\n      $(\".comment-new .comment-textarea\").trigger(\"input\").focus();\n      return false;\n    };\n\n    Comments.prototype.submitComment = function() {\n      var body, inner_path;\n      if (!Page.site_info.cert_user_id) {\n        Page.cmd(\"wrapperNotification\", [\"info\", \"Please, select your account.\"]);\n        return false;\n      }\n      body = $(\".comment-new .comment-textarea\").val();\n      if (!body) {\n        $(\".comment-new .comment-textarea\").focus();\n        return false;\n      }\n      $(\".comment-new .button-submit\").addClass(\"loading\");\n      inner_path = \"data/users/\" + Page.site_info.auth_address + \"/data.json\";\n      return Page.cmd(\"fileGet\", {\n        \"inner_path\": inner_path,\n        \"required\": false\n      }, (function(_this) {\n        return function(data) {\n          var json_raw;\n          if (data) {\n            data = JSON.parse(data);\n          } else {\n            data = {\n              \"next_comment_id\": 1,\n              \"comment\": [],\n              \"comment_vote\": {}\n            };\n          }\n          data.comment.push({\n            \"comment_id\": data.next_comment_id,\n            \"body\": body,\n            \"post_id\": _this.post_id,\n            \"date_added\": Time.timestamp()\n          });\n          data.next_comment_id += 1;\n          json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\\t')));\n          return Page.writePublish(inner_path, btoa(json_raw), function(res) {\n            $(\".comment-new .button-submit\").removeClass(\"loading\");\n            _this.loadComments();\n            _this.checkCert(\"updaterules\");\n            _this.log(\"Writepublish result\", res);\n            if (res !== false) {\n              return $(\".comment-new .comment-textarea\").val(\"\");\n            }\n          });\n        };\n      })(this));\n    };\n\n    Comments.prototype.checkCert = function(type) {\n      var last_cert_user_id;\n      last_cert_user_id = $(\".comment-new .user_name\").text();\n      if (Page.site_info.cert_user_id) {\n        $(\".comment-new\").removeClass(\"comment-nocert\");\n        $(\".comment-new .user_name\").text(Page.site_info.cert_user_id);\n      } else {\n        $(\".comment-new\").addClass(\"comment-nocert\");\n        $(\".comment-new .user_name\").text(\"Please sign in\");\n      }\n      if ($(\".comment-new .user_name\").text() !== last_cert_user_id || type === \"updaterules\") {\n        if (Page.site_info.cert_user_id) {\n          return Page.cmd(\"fileRules\", \"data/users/\" + Page.site_info.auth_address + \"/content.json\", (function(_this) {\n            return function(rules) {\n              _this.rules = rules;\n              if (rules.max_size) {\n                return _this.setCurrentSize(rules.current_size);\n              } else {\n                return _this.setCurrentSize(0);\n              }\n            };\n          })(this));\n        } else {\n          return this.setCurrentSize(0);\n        }\n      }\n    };\n\n    Comments.prototype.setCurrentSize = function(current_size) {\n      var current_size_kb;\n      if (current_size) {\n        current_size_kb = current_size / 1000;\n        $(\".user-size\").text(\"used: \" + (current_size_kb.toFixed(1)) + \"k/\" + (Math.round(this.rules.max_size / 1000)) + \"k\");\n        return $(\".user-size-used\").css(\"width\", Math.round(70 * current_size / this.rules.max_size));\n      } else {\n        return $(\".user-size\").text(\"\");\n      }\n    };\n\n    Comments.prototype.autoExpand = function(elem) {\n      var editor;\n      editor = elem[0];\n      if (elem.height() > 0) {\n        elem.height(1);\n      }\n      elem.on(\"input\", (function(_this) {\n        return function() {\n          var current_size, min_height, new_height, old_height;\n          if (editor.scrollHeight > elem.height()) {\n            old_height = elem.height();\n            elem.height(1);\n            new_height = editor.scrollHeight;\n            new_height += parseFloat(elem.css(\"borderTopWidth\"));\n            new_height += parseFloat(elem.css(\"borderBottomWidth\"));\n            new_height -= parseFloat(elem.css(\"paddingTop\"));\n            new_height -= parseFloat(elem.css(\"paddingBottom\"));\n            min_height = parseFloat(elem.css(\"lineHeight\")) * 2;\n            if (new_height < min_height) {\n              new_height = min_height + 4;\n            }\n            elem.height(new_height - 4);\n          }\n          if (_this.rules.max_size) {\n            if (elem.val().length > 0) {\n              current_size = _this.rules.current_size + elem.val().length + 90;\n            } else {\n              current_size = _this.rules.current_size;\n            }\n            return _this.setCurrentSize(current_size);\n          }\n        };\n      })(this));\n      if (elem.height() > 0) {\n        return elem.trigger(\"input\");\n      } else {\n        return elem.height(\"48px\");\n      }\n    };\n\n    return Comments;\n\n  })(Class);\n\n  window.Comments = new Comments();\n\n}).call(this);\n\n\n\n/* ---- data/1BLogC9LN4oPDcruNz3qo1ysa133E9AGg8/js/ZeroBlog.coffee ---- */\n\n\n(function() {\n  var ZeroBlog,\n    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },\n    __hasProp = {}.hasOwnProperty;\n\n  ZeroBlog = (function(_super) {\n    __extends(ZeroBlog, _super);\n\n    function ZeroBlog() {\n      this.setSiteinfo = __bind(this.setSiteinfo, this);\n      this.actionSetSiteInfo = __bind(this.actionSetSiteInfo, this);\n      this.saveContent = __bind(this.saveContent, this);\n      this.getContent = __bind(this.getContent, this);\n      this.getObject = __bind(this.getObject, this);\n      this.onOpenWebsocket = __bind(this.onOpenWebsocket, this);\n      this.publish = __bind(this.publish, this);\n      this.pageLoaded = __bind(this.pageLoaded, this);\n      return ZeroBlog.__super__.constructor.apply(this, arguments);\n    }\n\n    ZeroBlog.prototype.init = function() {\n      this.data = null;\n      this.site_info = null;\n      this.server_info = null;\n      this.event_page_load = $.Deferred();\n      this.event_site_info = $.Deferred();\n      $.when(this.event_page_load, this.event_site_info).done((function(_this) {\n        return function() {\n          if (_this.site_info.settings.own || _this.data.demo) {\n            _this.addInlineEditors();\n            _this.checkPublishbar();\n            $(\".publishbar\").on(\"click\", _this.publish);\n            $(\".posts .button.new\").css(\"display\", \"inline-block\");\n            return $(\".editbar .icon-help\").on(\"click\", function() {\n              $(\".editbar .markdown-help\").css(\"display\", \"block\");\n              $(\".editbar .markdown-help\").toggleClassLater(\"visible\", 10);\n              $(\".editbar .icon-help\").toggleClass(\"active\");\n              return false;\n            });\n          }\n        };\n      })(this));\n      $.when(this.event_site_info).done((function(_this) {\n        return function() {\n          var imagedata;\n          _this.log(\"event site info\");\n          imagedata = new Identicon(_this.site_info.address, 70).toString();\n          return $(\"body\").append(\"<style>.avatar { background-image: url(data:image/png;base64,\" + imagedata + \") }</style>\");\n        };\n      })(this));\n      return this.log(\"inited!\");\n    };\n\n    ZeroBlog.prototype.loadData = function(query) {\n      if (query == null) {\n        query = \"new\";\n      }\n      if (query === \"old\") {\n        query = \"SELECT key, value FROM json LEFT JOIN keyvalue USING (json_id) WHERE path = 'data.json'\";\n      } else {\n        query = \"SELECT key, value FROM json LEFT JOIN keyvalue USING (json_id) WHERE directory = '' AND file_name = 'data.json'\";\n      }\n      return this.cmd(\"dbQuery\", [query], (function(_this) {\n        return function(res) {\n          var row, _i, _len;\n          _this.data = {};\n          if (res) {\n            for (_i = 0, _len = res.length; _i < _len; _i++) {\n              row = res[_i];\n              _this.data[row.key] = row.value;\n            }\n            $(\".left h1 a:not(.editable-edit)\").html(_this.data.title).data(\"content\", _this.data.title);\n            $(\".left h2\").html(Text.toMarked(_this.data.description)).data(\"content\", _this.data.description);\n            return $(\".left .links\").html(Text.toMarked(_this.data.links)).data(\"content\", _this.data.links);\n          }\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.routeUrl = function(url) {\n      var match;\n      this.log(\"Routing url:\", url);\n      if (match = url.match(/Post:([0-9]+)/)) {\n        $(\"body\").addClass(\"page-post\");\n        this.post_id = parseInt(match[1]);\n        return this.pagePost();\n      } else {\n        $(\"body\").addClass(\"page-main\");\n        return this.pageMain();\n      }\n    };\n\n    ZeroBlog.prototype.pagePost = function() {\n      var s;\n      s = +(new Date);\n      return this.cmd(\"dbQuery\", [\"SELECT * FROM post WHERE post_id = \" + this.post_id + \" LIMIT 1\"], (function(_this) {\n        return function(res) {\n          if (res.length) {\n            _this.applyPostdata($(\".post-full\"), res[0], true);\n            Comments.pagePost(_this.post_id);\n          } else {\n            $(\".post-full\").html(\"<h1>Not found</h1>\");\n          }\n          return _this.pageLoaded();\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.pageMain = function() {\n      return this.cmd(\"dbQuery\", [\"SELECT post.*, COUNT(comment_id) AS comments FROM post LEFT JOIN comment USING (post_id) GROUP BY post_id ORDER BY date_published\"], (function(_this) {\n        return function(res) {\n          var elem, post, s, _i, _len;\n          s = +(new Date);\n          for (_i = 0, _len = res.length; _i < _len; _i++) {\n            post = res[_i];\n            elem = $(\"#post_\" + post.post_id);\n            if (elem.length === 0) {\n              elem = $(\".post.template\").clone().removeClass(\"template\").attr(\"id\", \"post_\" + post.post_id);\n              elem.prependTo(\".posts\");\n            }\n            _this.applyPostdata(elem, post);\n          }\n          _this.pageLoaded();\n          _this.log(\"Posts loaded in\", (+(new Date)) - s, \"ms\");\n          return $(\".posts .new\").on(\"click\", function() {\n            _this.cmd(\"fileGet\", [\"data/data.json\"], function(res) {\n              var data;\n              data = JSON.parse(res);\n              data.post.unshift({\n                post_id: data.next_post_id,\n                title: \"New blog post\",\n                date_published: (+(new Date)) / 1000,\n                body: \"Blog post body\"\n              });\n              data.next_post_id += 1;\n              elem = $(\".post.template\").clone().removeClass(\"template\");\n              _this.applyPostdata(elem, data.post[0]);\n              elem.hide();\n              elem.prependTo(\".posts\").slideDown();\n              _this.addInlineEditors(elem);\n              return _this.writeData(data);\n            });\n            return false;\n          });\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.pageLoaded = function() {\n      $(\"body\").addClass(\"loaded\");\n      $('pre code').each(function(i, block) {\n        return hljs.highlightBlock(block);\n      });\n      this.event_page_load.resolve();\n      return this.cmd(\"innerLoaded\", true);\n    };\n\n    ZeroBlog.prototype.addInlineEditors = function(parent) {\n      var editor, elem, elems, _i, _len;\n      this.logStart(\"Adding inline editors\");\n      elems = $(\"[data-editable]:visible\", parent);\n      for (_i = 0, _len = elems.length; _i < _len; _i++) {\n        elem = elems[_i];\n        elem = $(elem);\n        if (!elem.data(\"editor\") && !elem.hasClass(\"editor\")) {\n          editor = new InlineEditor(elem, this.getContent, this.saveContent, this.getObject);\n          elem.data(\"editor\", editor);\n        }\n      }\n      return this.logEnd(\"Adding inline editors\");\n    };\n\n    ZeroBlog.prototype.checkPublishbar = function() {\n      if (!this.site_modified || this.site_modified > this.site_info.content.modified) {\n        return $(\".publishbar\").addClass(\"visible\");\n      } else {\n        return $(\".publishbar\").removeClass(\"visible\");\n      }\n    };\n\n    ZeroBlog.prototype.publish = function() {\n      this.cmd(\"wrapperPrompt\", [\"Enter your private key:\", \"password\"], (function(_this) {\n        return function(privatekey) {\n          $(\".publishbar .button\").addClass(\"loading\");\n          return _this.cmd(\"sitePublish\", [privatekey], function(res) {\n            $(\".publishbar .button\").removeClass(\"loading\");\n            return _this.log(\"Publish result:\", res);\n          });\n        };\n      })(this));\n      return false;\n    };\n\n    ZeroBlog.prototype.applyPostdata = function(elem, post, full) {\n      var body, date_published, title_hash;\n      if (full == null) {\n        full = false;\n      }\n      title_hash = post.title.replace(/[#?& ]/g, \"+\").replace(/[+]+/g, \"+\");\n      elem.data(\"object\", \"Post:\" + post.post_id);\n      $(\".title .editable\", elem).html(post.title).attr(\"href\", \"?Post:\" + post.post_id + \":\" + title_hash).data(\"content\", post.title);\n      date_published = Time.since(post.date_published);\n      if (post.body.match(/^---/m)) {\n        date_published += \" &middot; \" + (Time.readtime(post.body));\n        $(\".more\", elem).css(\"display\", \"inline-block\").attr(\"href\", \"?Post:\" + post.post_id + \":\" + title_hash);\n      }\n      $(\".details .published\", elem).html(date_published).data(\"content\", post.date_published);\n      if (post.comments > 0) {\n        $(\".details .comments-num\", elem).css(\"display\", \"inline\").attr(\"href\", \"?Post:\" + post.post_id + \":\" + title_hash + \"#Comments\");\n        $(\".details .comments-num .num\", elem).text(post.comments + \" comments\");\n      } else {\n        $(\".details .comments-num\", elem).css(\"display\", \"none\");\n      }\n      if (full) {\n        body = post.body;\n      } else {\n        body = post.body.replace(/^([\\s\\S]*?)\\n---\\n[\\s\\S]*$/, \"$1\");\n      }\n      return $(\".body\", elem).html(Text.toMarked(body)).data(\"content\", post.body);\n    };\n\n    ZeroBlog.prototype.onOpenWebsocket = function(e) {\n      this.loadData();\n      this.routeUrl(window.location.search.substring(1));\n      this.cmd(\"siteInfo\", {}, this.setSiteinfo);\n      return this.cmd(\"serverInfo\", {}, (function(_this) {\n        return function(ret) {\n          _this.server_info = ret;\n          if (_this.server_info.rev < 160) {\n            return _this.loadData(\"old\");\n          }\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.getObject = function(elem) {\n      return elem.parents(\"[data-object]:first\");\n    };\n\n    ZeroBlog.prototype.getContent = function(elem, raw) {\n      var content, id, type, _ref;\n      if (raw == null) {\n        raw = false;\n      }\n      _ref = this.getObject(elem).data(\"object\").split(\":\"), type = _ref[0], id = _ref[1];\n      id = parseInt(id);\n      content = elem.data(\"content\");\n      if (elem.data(\"editable-mode\") === \"timestamp\") {\n        content = Time.date(content, \"full\");\n      }\n      if (elem.data(\"editable-mode\") === \"simple\" || raw) {\n        return content;\n      } else {\n        return Text.toMarked(content);\n      }\n    };\n\n    ZeroBlog.prototype.saveContent = function(elem, content, cb) {\n      var id, type, _ref;\n      if (cb == null) {\n        cb = false;\n      }\n      if (elem.data(\"deletable\") && content === null) {\n        return this.deleteObject(elem, cb);\n      }\n      elem.data(\"content\", content);\n      _ref = this.getObject(elem).data(\"object\").split(\":\"), type = _ref[0], id = _ref[1];\n      id = parseInt(id);\n      if (type === \"Post\" || type === \"Site\") {\n        return this.saveSite(elem, type, id, content, cb);\n      } else if (type === \"Comment\") {\n        return this.saveComment(elem, type, id, content, cb);\n      }\n    };\n\n    ZeroBlog.prototype.saveSite = function(elem, type, id, content, cb) {\n      return this.cmd(\"fileGet\", [\"data/data.json\"], (function(_this) {\n        return function(res) {\n          var data, post;\n          data = JSON.parse(res);\n          if (type === \"Post\") {\n            post = ((function() {\n              var _i, _len, _ref, _results;\n              _ref = data.post;\n              _results = [];\n              for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n                post = _ref[_i];\n                if (post.post_id === id) {\n                  _results.push(post);\n                }\n              }\n              return _results;\n            })())[0];\n            if (elem.data(\"editable-mode\") === \"timestamp\") {\n              content = Time.timestamp(content);\n            }\n            post[elem.data(\"editable\")] = content;\n          } else if (type === \"Site\") {\n            data[elem.data(\"editable\")] = content;\n          }\n          return _this.writeData(data, function(res) {\n            if (cb) {\n              if (res === true) {\n                if (elem.data(\"editable-mode\") === \"simple\") {\n                  return cb(content);\n                } else if (elem.data(\"editable-mode\") === \"timestamp\") {\n                  return cb(Time.since(content));\n                } else {\n                  return cb(Text.toMarked(content));\n                }\n              } else {\n                return cb(false);\n              }\n            }\n          });\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.saveComment = function(elem, type, id, content, cb) {\n      var inner_path;\n      this.log(\"Saving comment...\", id);\n      this.getObject(elem).css(\"height\", \"auto\");\n      inner_path = \"data/users/\" + Page.site_info.auth_address + \"/data.json\";\n      return Page.cmd(\"fileGet\", {\n        \"inner_path\": inner_path,\n        \"required\": false\n      }, (function(_this) {\n        return function(data) {\n          var comment, json_raw;\n          data = JSON.parse(data);\n          comment = ((function() {\n            var _i, _len, _ref, _results;\n            _ref = data.comment;\n            _results = [];\n            for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n              comment = _ref[_i];\n              if (comment.comment_id === id) {\n                _results.push(comment);\n              }\n            }\n            return _results;\n          })())[0];\n          comment[elem.data(\"editable\")] = content;\n          _this.log(data);\n          json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\\t')));\n          return _this.writePublish(inner_path, btoa(json_raw), function(res) {\n            if (res === true) {\n              Comments.checkCert(\"updaterules\");\n              if (cb) {\n                return cb(Text.toMarked(content, {\n                  \"sanitize\": true\n                }));\n              }\n            } else {\n              _this.cmd(\"wrapperNotification\", [\"error\", \"File write error: \" + res]);\n              if (cb) {\n                return cb(false);\n              }\n            }\n          });\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.deleteObject = function(elem, cb) {\n      var id, inner_path, type, _ref;\n      if (cb == null) {\n        cb = False;\n      }\n      _ref = elem.data(\"object\").split(\":\"), type = _ref[0], id = _ref[1];\n      id = parseInt(id);\n      if (type === \"Post\") {\n        return this.cmd(\"fileGet\", [\"data/data.json\"], (function(_this) {\n          return function(res) {\n            var data, post;\n            data = JSON.parse(res);\n            if (type === \"Post\") {\n              post = ((function() {\n                var _i, _len, _ref1, _results;\n                _ref1 = data.post;\n                _results = [];\n                for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n                  post = _ref1[_i];\n                  if (post.post_id === id) {\n                    _results.push(post);\n                  }\n                }\n                return _results;\n              })())[0];\n              if (!post) {\n                return false;\n              }\n              data.post.splice(data.post.indexOf(post), 1);\n              return _this.writeData(data, function(res) {\n                if (cb) {\n                  cb();\n                }\n                if (res === true) {\n                  return elem.slideUp();\n                }\n              });\n            }\n          };\n        })(this));\n      } else if (type === \"Comment\") {\n        inner_path = \"data/users/\" + Page.site_info.auth_address + \"/data.json\";\n        return this.cmd(\"fileGet\", {\n          \"inner_path\": inner_path,\n          \"required\": false\n        }, (function(_this) {\n          return function(data) {\n            var comment, json_raw;\n            data = JSON.parse(data);\n            comment = ((function() {\n              var _i, _len, _ref1, _results;\n              _ref1 = data.comment;\n              _results = [];\n              for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n                comment = _ref1[_i];\n                if (comment.comment_id === id) {\n                  _results.push(comment);\n                }\n              }\n              return _results;\n            })())[0];\n            data.comment.splice(data.comment.indexOf(comment), 1);\n            json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\\t')));\n            return _this.writePublish(inner_path, btoa(json_raw), function(res) {\n              if (res === true) {\n                elem.slideUp();\n              }\n              if (cb) {\n                return cb();\n              }\n            });\n          };\n        })(this));\n      }\n    };\n\n    ZeroBlog.prototype.writeData = function(data, cb) {\n      var json_raw;\n      if (cb == null) {\n        cb = null;\n      }\n      if (!data) {\n        return this.log(\"Data missing\");\n      }\n      this.data[\"modified\"] = data.modified = Time.timestamp();\n      json_raw = unescape(encodeURIComponent(JSON.stringify(data, void 0, '\\t')));\n      this.cmd(\"fileWrite\", [\"data/data.json\", btoa(json_raw)], (function(_this) {\n        return function(res) {\n          if (res === \"ok\") {\n            if (cb) {\n              cb(true);\n            }\n          } else {\n            _this.cmd(\"wrapperNotification\", [\"error\", \"File write error: \" + res]);\n            if (cb) {\n              cb(false);\n            }\n          }\n          return _this.checkPublishbar();\n        };\n      })(this));\n      return this.cmd(\"fileGet\", [\"content.json\"], (function(_this) {\n        return function(content) {\n          content = content.replace(/\"title\": \".*?\"/, \"\\\"title\\\": \\\"\" + data.title + \"\\\"\");\n          return _this.cmd(\"fileWrite\", [\"content.json\", btoa(content)], function(res) {\n            if (res !== \"ok\") {\n              return _this.cmd(\"wrapperNotification\", [\"error\", \"Content.json write error: \" + res]);\n            }\n          });\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.writePublish = function(inner_path, data, cb) {\n      return this.cmd(\"fileWrite\", [inner_path, data], (function(_this) {\n        return function(res) {\n          if (res !== \"ok\") {\n            _this.cmd(\"wrapperNotification\", [\"error\", \"File write error: \" + res]);\n            cb(false);\n            return false;\n          }\n          return _this.cmd(\"sitePublish\", {\n            \"inner_path\": inner_path\n          }, function(res) {\n            if (res === \"ok\") {\n              return cb(true);\n            } else {\n              return cb(res);\n            }\n          });\n        };\n      })(this));\n    };\n\n    ZeroBlog.prototype.onRequest = function(cmd, message) {\n      if (cmd === \"setSiteInfo\") {\n        return this.actionSetSiteInfo(message);\n      } else {\n        return this.log(\"Unknown command\", message);\n      }\n    };\n\n    ZeroBlog.prototype.actionSetSiteInfo = function(message) {\n      this.setSiteinfo(message.params);\n      return this.checkPublishbar();\n    };\n\n    ZeroBlog.prototype.setSiteinfo = function(site_info) {\n      var _ref, _ref1;\n      this.site_info = site_info;\n      this.event_site_info.resolve(site_info);\n      if ($(\"body\").hasClass(\"page-post\")) {\n        Comments.checkCert();\n      }\n      if (((_ref = site_info.event) != null ? _ref[0] : void 0) === \"file_done\" && site_info.event[1].match(/.*users.*data.json$/)) {\n        if ($(\"body\").hasClass(\"page-post\")) {\n          Comments.loadComments();\n        }\n        if ($(\"body\").hasClass(\"page-main\")) {\n          return RateLimit(500, (function(_this) {\n            return function() {\n              return _this.pageMain();\n            };\n          })(this));\n        }\n      } else if (((_ref1 = site_info.event) != null ? _ref1[0] : void 0) === \"file_done\" && site_info.event[1] === \"data/data.json\") {\n        this.loadData();\n        if ($(\"body\").hasClass(\"page-main\")) {\n          this.pageMain();\n        }\n        if ($(\"body\").hasClass(\"page-post\")) {\n          return this.pagePost();\n        }\n      } else {\n\n      }\n    };\n\n    return ZeroBlog;\n\n  })(ZeroFrame);\n\n  window.Page = new ZeroBlog();\n\n}).call(this);\n"
  },
  {
    "path": "src/Tor/TorManager.py",
    "content": "import logging\nimport re\nimport socket\nimport binascii\nimport sys\nimport os\nimport time\nimport random\nimport subprocess\nimport atexit\n\nimport gevent\n\nfrom Config import config\nfrom Crypt import CryptRsa\nfrom Site import SiteManager\nimport socks\nfrom gevent.lock import RLock\nfrom Debug import Debug\nfrom Plugin import PluginManager\n\n\n@PluginManager.acceptPlugins\nclass TorManager(object):\n    def __init__(self, fileserver_ip=None, fileserver_port=None):\n        self.privatekeys = {}  # Onion: Privatekey\n        self.site_onions = {}  # Site address: Onion\n        self.tor_exe = \"tools/tor/tor.exe\"\n        self.has_meek_bridges = os.path.isfile(\"tools/tor/PluggableTransports/meek-client.exe\")\n        self.tor_process = None\n        self.log = logging.getLogger(\"TorManager\")\n        self.start_onions = None\n        self.conn = None\n        self.lock = RLock()\n        self.starting = True\n        self.connecting = True\n        self.status = None\n        self.event_started = gevent.event.AsyncResult()\n\n        if config.tor == \"disable\":\n            self.enabled = False\n            self.start_onions = False\n            self.setStatus(\"Disabled\")\n        else:\n            self.enabled = True\n            self.setStatus(\"Waiting\")\n\n        if fileserver_port:\n            self.fileserver_port = fileserver_port\n        else:\n            self.fileserver_port = config.fileserver_port\n\n        self.ip, self.port = config.tor_controller.rsplit(\":\", 1)\n        self.port = int(self.port)\n\n        self.proxy_ip, self.proxy_port = config.tor_proxy.rsplit(\":\", 1)\n        self.proxy_port = int(self.proxy_port)\n\n    def start(self):\n        self.log.debug(\"Starting (Tor: %s)\" % config.tor)\n        self.starting = True\n        try:\n            if not self.connect():\n                raise Exception(self.status)\n            self.log.debug(\"Tor proxy port %s check ok\" % config.tor_proxy)\n        except Exception as err:\n            if sys.platform.startswith(\"win\") and os.path.isfile(self.tor_exe):\n                self.log.info(\"Starting self-bundled Tor, due to Tor proxy port %s check error: %s\" % (config.tor_proxy, err))\n                # Change to self-bundled Tor ports\n                self.port = 49051\n                self.proxy_port = 49050\n                if config.tor == \"always\":\n                    socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, \"127.0.0.1\", self.proxy_port)\n                self.enabled = True\n                if not self.connect():\n                    self.startTor()\n            else:\n                self.log.info(\"Disabling Tor, because error while accessing Tor proxy at port %s: %s\" % (config.tor_proxy, err))\n                self.enabled = False\n\n    def setStatus(self, status):\n        self.status = status\n        if \"main\" in sys.modules: # import main has side-effects, breaks tests\n            import main\n            if \"ui_server\" in dir(main):\n                main.ui_server.updateWebsocket()\n\n    def startTor(self):\n        if sys.platform.startswith(\"win\"):\n            try:\n                self.log.info(\"Starting Tor client %s...\" % self.tor_exe)\n                tor_dir = os.path.dirname(self.tor_exe)\n                startupinfo = subprocess.STARTUPINFO()\n                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW\n                cmd = r\"%s -f torrc --defaults-torrc torrc-defaults --ignore-missing-torrc\" % self.tor_exe\n                if config.tor_use_bridges:\n                    cmd += \" --UseBridges 1\"\n\n                self.tor_process = subprocess.Popen(cmd, cwd=tor_dir, close_fds=True, startupinfo=startupinfo)\n                for wait in range(1, 3):  # Wait for startup\n                    time.sleep(wait * 0.5)\n                    self.enabled = True\n                    if self.connect():\n                        if self.isSubprocessRunning():\n                            self.request(\"TAKEOWNERSHIP\")  # Shut down Tor client when controll connection closed\n                        break\n                # Terminate on exit\n                atexit.register(self.stopTor)\n            except Exception as err:\n                self.log.error(\"Error starting Tor client: %s\" % Debug.formatException(str(err)))\n                self.enabled = False\n        self.starting = False\n        self.event_started.set(False)\n        return False\n\n    def isSubprocessRunning(self):\n        return self.tor_process and self.tor_process.pid and self.tor_process.poll() is None\n\n    def stopTor(self):\n        self.log.debug(\"Stopping...\")\n        try:\n            if self.isSubprocessRunning():\n                self.request(\"SIGNAL SHUTDOWN\")\n        except Exception as err:\n            self.log.error(\"Error stopping Tor: %s\" % err)\n\n    def connect(self):\n        if not self.enabled:\n            return False\n        self.site_onions = {}\n        self.privatekeys = {}\n\n        return self.connectController()\n\n    def connectController(self):\n        if \"socket_noproxy\" in dir(socket):  # Socket proxy-patched, use non-proxy one\n            conn = socket.socket_noproxy(socket.AF_INET, socket.SOCK_STREAM)\n        else:\n            conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n\n        self.log.debug(\"Connecting to Tor Controller %s:%s\" % (self.ip, self.port))\n        self.connecting = True\n        try:\n            with self.lock:\n                conn.connect((self.ip, self.port))\n\n                # Auth cookie file\n                res_protocol = self.send(\"PROTOCOLINFO\", conn)\n                cookie_match = re.search('COOKIEFILE=\"(.*?)\"', res_protocol)\n\n                if config.tor_password:\n                    res_auth = self.send('AUTHENTICATE \"%s\"' % config.tor_password, conn)\n                elif cookie_match:\n                    cookie_file = cookie_match.group(1).encode(\"ascii\").decode(\"unicode_escape\")\n                    if not os.path.isfile(cookie_file) and self.tor_process:\n                        # Workaround for tor client cookie auth file utf8 encoding bug (https://github.com/torproject/stem/issues/57)\n                        cookie_file = os.path.dirname(self.tor_exe) + \"\\\\data\\\\control_auth_cookie\"\n                    auth_hex = binascii.b2a_hex(open(cookie_file, \"rb\").read())\n                    res_auth = self.send(\"AUTHENTICATE %s\" % auth_hex.decode(\"utf8\"), conn)\n                else:\n                    res_auth = self.send(\"AUTHENTICATE\", conn)\n\n                if \"250 OK\" not in res_auth:\n                    raise Exception(\"Authenticate error %s\" % res_auth)\n\n                # Version 0.2.7.5 required because ADD_ONION support\n                res_version = self.send(\"GETINFO version\", conn)\n                version = re.search(r'version=([0-9\\.]+)', res_version).group(1)\n                if float(version.replace(\".\", \"0\", 2)) < 207.5:\n                    raise Exception(\"Tor version >=0.2.7.5 required, found: %s\" % version)\n\n                self.setStatus(\"Connected (%s)\" % res_auth)\n                self.event_started.set(True)\n                self.starting = False\n                self.connecting = False\n                self.conn = conn\n        except Exception as err:\n            self.conn = None\n            self.setStatus(\"Error (%s)\" % str(err))\n            self.log.warning(\"Tor controller connect error: %s\" % Debug.formatException(str(err)))\n            self.enabled = False\n        return self.conn\n\n    def disconnect(self):\n        if self.conn:\n            self.conn.close()\n        self.conn = None\n\n    def startOnions(self):\n        if self.enabled:\n            self.log.debug(\"Start onions\")\n            self.start_onions = True\n            self.getOnion(\"global\")\n\n    # Get new exit node ip\n    def resetCircuits(self):\n        res = self.request(\"SIGNAL NEWNYM\")\n        if \"250 OK\" not in res:\n            self.setStatus(\"Reset circuits error (%s)\" % res)\n            self.log.error(\"Tor reset circuits error: %s\" % res)\n\n    def addOnion(self):\n        if len(self.privatekeys) >= config.tor_hs_limit:\n            return random.choice([key for key in list(self.privatekeys.keys()) if key != self.site_onions.get(\"global\")])\n\n        result = self.makeOnionAndKey()\n        if result:\n            onion_address, onion_privatekey = result\n            self.privatekeys[onion_address] = onion_privatekey\n            self.setStatus(\"OK (%s onions running)\" % len(self.privatekeys))\n            SiteManager.peer_blacklist.append((onion_address + \".onion\", self.fileserver_port))\n            return onion_address\n        else:\n            return False\n\n    def makeOnionAndKey(self):\n        res = self.request(\"ADD_ONION NEW:RSA1024 port=%s\" % self.fileserver_port)\n        match = re.search(\"ServiceID=([A-Za-z0-9]+).*PrivateKey=RSA1024:(.*?)[\\r\\n]\", res, re.DOTALL)\n        if match:\n            onion_address, onion_privatekey = match.groups()\n            return (onion_address, onion_privatekey)\n        else:\n            self.setStatus(\"AddOnion error (%s)\" % res)\n            self.log.error(\"Tor addOnion error: %s\" % res)\n            return False\n\n    def delOnion(self, address):\n        res = self.request(\"DEL_ONION %s\" % address)\n        if \"250 OK\" in res:\n            del self.privatekeys[address]\n            self.setStatus(\"OK (%s onion running)\" % len(self.privatekeys))\n            return True\n        else:\n            self.setStatus(\"DelOnion error (%s)\" % res)\n            self.log.error(\"Tor delOnion error: %s\" % res)\n            self.disconnect()\n            return False\n\n    def request(self, cmd):\n        with self.lock:\n            if not self.enabled:\n                return False\n            if not self.conn:\n                if not self.connect():\n                    return \"\"\n            return self.send(cmd)\n\n    def send(self, cmd, conn=None):\n        if not conn:\n            conn = self.conn\n        self.log.debug(\"> %s\" % cmd)\n        back = \"\"\n        for retry in range(2):\n            try:\n                conn.sendall(b\"%s\\r\\n\" % cmd.encode(\"utf8\"))\n                while not back.endswith(\"250 OK\\r\\n\"):\n                    back += conn.recv(1024 * 64).decode(\"utf8\")\n                break\n            except Exception as err:\n                self.log.error(\"Tor send error: %s, reconnecting...\" % err)\n                if not self.connecting:\n                    self.disconnect()\n                    time.sleep(1)\n                    self.connect()\n                back = None\n        if back:\n            self.log.debug(\"< %s\" % back.strip())\n        return back\n\n    def getPrivatekey(self, address):\n        return self.privatekeys[address]\n\n    def getPublickey(self, address):\n        return CryptRsa.privatekeyToPublickey(self.privatekeys[address])\n\n    def getOnion(self, site_address):\n        if not self.enabled:\n            return None\n\n        if config.tor == \"always\":  # Different onion for every site\n            onion = self.site_onions.get(site_address)\n        else:  # Same onion for every site\n            onion = self.site_onions.get(\"global\")\n            site_address = \"global\"\n\n        if not onion:\n            with self.lock:\n                self.site_onions[site_address] = self.addOnion()\n                onion = self.site_onions[site_address]\n                self.log.debug(\"Created new hidden service for %s: %s\" % (site_address, onion))\n\n        return onion\n\n    # Creates and returns a\n    # socket that has connected to the Tor Network\n    def createSocket(self, onion, port):\n        if not self.enabled:\n            return False\n        self.log.debug(\"Creating new Tor socket to %s:%s\" % (onion, port))\n        if self.starting:\n            self.log.debug(\"Waiting for startup...\")\n            self.event_started.get()\n        if config.tor == \"always\":  # Every socket is proxied by default, in this mode\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        else:\n            sock = socks.socksocket()\n            sock.set_proxy(socks.SOCKS5, self.proxy_ip, self.proxy_port)\n        return sock\n"
  },
  {
    "path": "src/Tor/__init__.py",
    "content": "from .TorManager import TorManager"
  },
  {
    "path": "src/Translate/Translate.py",
    "content": "import os\nimport json\nimport logging\nimport inspect\nimport re\nimport html\nimport string\n\nfrom Config import config\n\ntranslates = []\n\n\nclass EscapeProxy(dict):\n    # Automatically escape the accessed string values\n    def __getitem__(self, key):\n        val = dict.__getitem__(self, key)\n        if type(val) in (str, str):\n            return html.escape(val)\n        elif type(val) is dict:\n            return EscapeProxy(val)\n        elif type(val) is list:\n            return EscapeProxy(enumerate(val))  # Convert lists to dict\n        else:\n            return val\n\n\nclass Translate(dict):\n    def __init__(self, lang_dir=None, lang=None):\n        if not lang_dir:\n            lang_dir = os.path.dirname(__file__) + \"/languages/\"\n        if not lang:\n            lang = config.language\n        self.lang = lang\n        self.lang_dir = lang_dir\n        self.setLanguage(lang)\n        self.formatter = string.Formatter()\n\n        if config.debug:\n            # Auto reload FileRequest on change\n            from Debug import DebugReloader\n            DebugReloader.watcher.addCallback(self.load)\n\n        translates.append(self)\n\n    def setLanguage(self, lang):\n        self.lang = re.sub(\"[^a-z-]\", \"\", lang)\n        self.lang_file = self.lang_dir + \"%s.json\" % lang\n        self.load()\n\n    def __repr__(self):\n        return \"<translate %s>\" % self.lang\n\n    def load(self):\n        if self.lang == \"en\":\n            data = {}\n            dict.__init__(self, data)\n            self.clear()\n        elif os.path.isfile(self.lang_file):\n            try:\n                data = json.load(open(self.lang_file, encoding=\"utf8\"))\n                logging.debug(\"Loaded translate file: %s (%s entries)\" % (self.lang_file, len(data)))\n            except Exception as err:\n                logging.error(\"Error loading translate file %s: %s\" % (self.lang_file, err))\n                data = {}\n            dict.__init__(self, data)\n        else:\n            data = {}\n            dict.__init__(self, data)\n            self.clear()\n            logging.debug(\"Translate file not exists: %s\" % self.lang_file)\n\n    def format(self, s, kwargs, nested=False):\n        kwargs[\"_\"] = self\n        if nested:\n            back = self.formatter.vformat(s, [], kwargs)  # PY3 TODO: Change to format_map\n            return self.formatter.vformat(back, [], kwargs)\n        else:\n            return self.formatter.vformat(s, [], kwargs)\n\n    def formatLocals(self, s, nested=False):\n        kwargs = inspect.currentframe().f_back.f_locals\n        return self.format(s, kwargs, nested=nested)\n\n    def __call__(self, s, kwargs=None, nested=False, escape=True):\n        if not kwargs:\n            kwargs = inspect.currentframe().f_back.f_locals\n        if escape:\n            kwargs = EscapeProxy(kwargs)\n        return self.format(s, kwargs, nested=nested)\n\n    def __missing__(self, key):\n        return key\n\n    def pluralize(self, value, single, multi):\n        if value > 1:\n            return self[multi].format(value)\n        else:\n            return self[single].format(value)\n\n    def translateData(self, data, translate_table=None, mode=\"js\"):\n        if not translate_table:\n            translate_table = self\n\n        patterns = []\n        for key, val in list(translate_table.items()):\n            if key.startswith(\"_(\"):  # Problematic string: only match if called between _(\" \") function\n                key = key.replace(\"_(\", \"\").replace(\")\", \"\").replace(\", \", '\", \"')\n                translate_table[key] = \"|\" + val\n            patterns.append(re.escape(key))\n\n        def replacer(match):\n            target = translate_table[match.group(1)]\n            if mode == \"js\":\n                if target and target[0] == \"|\":  # Strict string match\n                    if match.string[match.start() - 2] == \"_\":  # Only if the match if called between _(\" \") function\n                        return '\"' + target[1:] + '\"'\n                    else:\n                        return '\"' + match.group(1) + '\"'\n                return '\"' + target + '\"'\n            else:\n                return match.group(0)[0] + target + match.group(0)[-1]\n\n        if mode == \"html\":\n            pattern = '[\">](' + \"|\".join(patterns) + ')[\"<]'\n        else:\n            pattern = '\"(' + \"|\".join(patterns) + ')\"'\n        data = re.sub(pattern, replacer, data)\n\n        if mode == \"html\":\n            data = data.replace(\"lang={lang}\", \"lang=%s\" % self.lang)  # lang get parameter to .js file to avoid cache\n\n        return data\n\ntranslate = Translate()\n"
  },
  {
    "path": "src/Translate/__init__.py",
    "content": "from .Translate import *"
  },
  {
    "path": "src/Translate/languages/da.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Tillykke, din port (<b>{0}</b>) er åben.<br>Du er nu fuld klient på ZeroNet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"TOR er aktiv, alle forbindelser anvender Onions.\",\n\t\"Successfully started Tor onion hidden services.\": \"OK. Startede TOR skjult onion service.\",\n\t\"Unable to start hidden services, please check your config.\": \"Fejl. Kunne ikke starte TOR skjult onion service. Tjek din opsætning!\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Åben port <b>{0}</b> på din router for hurtigere forbindelse.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Begrænset forbindelse. Åben venligst port <b>{0}</b> på din router\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"eller opsæt TOR for fuld adgang til ZeroNet!\",\n\n\t\"Select account you want to use in this site:\": \"Vælg bruger til brug på denne side:\",\n\t\"currently selected\": \"nuværende bruger\",\n\t\"Unique to site\": \"Unik på siden\",\n\n\t\"Content signing failed\": \"Signering af indhold fejlede\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Indhold i kø for offentliggørelse i {0:.0f} sekunder.\",\n\t\"Content published to {0} peers.\": \"Indhold offentliggjort til {0} klienter.\",\n\t\"No peers found, but your content is ready to access.\": \"Ingen klienter fundet, men dit indhold er klar til hentning.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Din forbindelse er begrænset. Åben venligst port <b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"på din router for at dele din side med alle.\",\n\t\"Content publish failed.\": \"Offentliggørelse af indhold fejlede.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Denne fil er endnu ikke delt færdig. Tidligere indhold kan gå tabt hvis du skriver til filen nu.\",\n\t\"Write content anyway\": \"Del indhold alligevel\",\n\t\"New certificate added:\": \"Nyt certifikat oprettet:\",\n\t\"You current certificate:\": \"Dit nuværende certifikat: \",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Skift certificat til {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certifikat ændret til <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Side klonet\",\n\n\t\"You have successfully changed the web interface's language!\": \"OK. Du har nu skiftet sprog på web brugergrænsefladen!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Pga. browser cache kan skift af sprog tage nogle minutter.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Forbindelse til <b>UiServer Websocket</b> blev tabt. Genopretter forbindelse...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Forbindelse til <b>UiServer Websocket</b> genoprettet.\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocket fejl. Genindlæs venligst siden (F5)!\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Opretter forbindelse...\",\n\t\"Site size: <b>\": \"Side størrelse: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> er større end den tilladte default \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Åben side og sæt max side størrelse til \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" filer skal downloades\",\n\t\" downloaded\": \" downloadet\",\n\t\" download failed\": \" download fejlede\",\n\t\"Peers found: \": \"Klienter fundet: \",\n\t\"No peers found\": \"Ingen klienter fundet\",\n\t\"Running out of size limit (\": \"Siden fylder snart for meget (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Ret max side størrelse til \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Site size limit changed to {0}MB\": \"Max side størrelse ændret til {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Ny version af denne side er blevet offentliggjort.<br>Genindlæs venligst siden (F5) for at se nyt indhold!\",\n\t\"This site requests permission:\": \"Denne side betyder om tilladdelse:\",\n\t\"_(Accept)\": \"Tillad\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/de.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Gratulation, dein Port <b>{0}</b> ist offen.<br>Du bist ein volles Mitglied des ZeroNet Netzwerks!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor Modus aktiv, jede Verbindung nutzt die Onion Route.\",\n\t\"Successfully started Tor onion hidden services.\": \"Tor versteckte Dienste erfolgreich gestartet.\",\n\t\"Unable to start hidden services, please check your config.\": \"Nicht möglich versteckte Dienste zu starten.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Für schnellere Verbindungen, öffne Port <b>{0}</b> auf deinem Router.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Deine Verbindung ist eingeschränkt. Bitte öffne Port <b>{0}</b> auf deinem Router\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"oder konfiguriere Tor um ein volles Mitglied des ZeroNet Netzwerks zu werden.\",\n\n\t\"Select account you want to use in this site:\": \"Wähle das Konto, das du auf dieser Seite benutzen willst:\",\n\t\"currently selected\": \"aktuell ausgewählt\",\n\t\"Unique to site\": \"Eindeutig zur Seite\",\n\n\t\"Content signing failed\": \"Signierung des Inhalts fehlgeschlagen\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Veröffentlichung des Inhalts um {0:.0f} Sekunden verzögert.\",\n\t\"Content published to {0} peers.\": \"Inhalt zu {0} Peers veröffentlicht.\",\n\t\"No peers found, but your content is ready to access.\": \"Keine Peers gefunden, aber dein Inhalt ist bereit zum Zugriff.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Deine Netzwerkverbindung ist beschränkt. Bitte öffne Port <b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"auf deinem Router um deine Seite für Jeden zugänglich zu machen.\",\n\t\"Content publish failed.\": \"Inhalt konnte nicht veröffentlicht werden.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Diese Datei wird noch synchronisiert. Wenn jetzt geschrieben wird geht der vorherige Inhalt verloren.\",\n\t\"Write content anyway\": \"Inhalt trotzdem schreiben\",\n\t\"New certificate added:\": \"Neues Zertifikat hinzugefügt:\",\n\t\"You current certificate:\": \"Dein aktuelles Zertifikat:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Ändere es zu {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Zertifikat geändert zu: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Seite geklont\",\n\n\t\"You have successfully changed the web interface's language!\": \"Du hast die Sprache des Webinterface erfolgreich geändert!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Aufgrund des Browsercaches kann die volle Transformation Minuten dauern.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Die Verbindung mit <b>UiServer Websocket</b>ist abgebrochen. Neu verbinden...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Die Verbindung mit <b>UiServer Websocket</b> wurde wiederhergestellt.\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocket Fehler, bitte Seite neu laden.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Verbinden...\",\n\t\"Site size: <b>\": \"Seitengröße: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> ist größer als der erlaubte Standart\",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Öffne Seite und setze das Limit auf \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" Dateien müssen noch heruntergeladen werden\",\n\t\" downloaded\": \" heruntergeladen\",\n\t\" download failed\": \" Herunterladen fehlgeschlagen\",\n\t\"Peers found: \": \"Peers gefunden: \",\n\t\"No peers found\": \"Keine Peers gefunden\",\n\t\"Running out of size limit (\": \"Das Speicherlimit ist bald ausgeschöpft (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Limit auf \\\" + site_info.next_size_limit + \\\"MB ändern\",\n\t\"Site size limit changed to {0}MB\": \"Speicherlimit für diese Seite auf {0}MB geändert\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Neue version dieser Seite wurde gerade veröffentlicht.<br>Lade die Seite neu um den geänderten Inhalt zu sehen.\",\n\t\"This site requests permission:\": \"Diese Seite fordert rechte:\",\n\t\"_(Accept)\": \"Genehmigen\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/es.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"¡Felicidades! tu puerto <b>{0}</b> está abierto.<br>¡Eres un miembro completo de la red Zeronet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Modo Tor activado, cada conexión usa una ruta Onion.\",\n\t\"Successfully started Tor onion hidden services.\": \"Tor ha iniciado satisfactoriamente la ocultación de los servicios onion.\",\n\t\"Unable to start hidden services, please check your config.\": \"No se puedo iniciar los servicios ocultos, por favor comprueba tu configuración.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Para conexiones más rápidas abre el puerto <b>{0}</b> en tu router.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Tu conexión está limitada. Por favor, abre el puerto <b>{0}</b> en tu router\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"o configura Tor para convertirte en un miembro completo de la red ZeroNet.\",\n\n\t\"Select account you want to use in this site:\": \"Selecciona la cuenta que quieres utilizar en este sitio:\",\n\t\"currently selected\": \"actualmente seleccionada\",\n\t\"Unique to site\": \"Única para el sitio\",\n\n\t\"Content signing failed\": \"Firma del contenido fallida\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Publicación de contenido en cola durante {0:.0f} segundos.\",\n\t\"Content published to {0} peers.\": \"Contenido publicado para {0} pares.\",\n\t\"No peers found, but your content is ready to access.\": \"No se ha encontrado pares, pero tu contenido está listo para ser accedido.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Tu conexión de red está restringida. Por favor, abre el puerto<b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"en tu router para hacer tu sitio accesible a todo el mundo.\",\n\t\"Content publish failed.\": \"Publicación de contenido fallida.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Este archivo está aún sincronizado, si le escribes ahora el contenido previo podría perderse.\",\n\t\"Write content anyway\": \"Escribir el contenido de todas formas\",\n\t\"New certificate added:\": \"Nuevo certificado añadido:\",\n\t\"You current certificate:\": \"Tu certificado actual:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Cambia esto a {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certificado cambiado a: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Sitio clonado\",\n\n\t\"You have successfully changed the web interface's language!\": \"¡Has cambiado con éxito el idioma de la interfaz web!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Debido a la caché del navegador, la transformación completa podría llevar unos minutos.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Se perdió la conexión con <b>UiServer Websocket</b>. Reconectando...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Conexión con <b>UiServer Websocket</b> recuperada.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Error de UiServer Websocket, por favor recarga la página.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Conectando...\",\n\t\"Site size: <b>\": \"Tamaño del sitio: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> es más grande de lo permitido por defecto\",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Abre tu sitio and establece el límite de tamaño a \\\" + site_info.next_size_limit + \\\"MBs\",\n\t\" files needs to be downloaded\": \" Los archivos necesitan ser descargados\",\n\t\" downloaded\": \" descargados\",\n\t\" download failed\": \" descarga fallida\",\n\t\"Peers found: \": \"Pares encontrados: \",\n\t\"No peers found\": \"No se han encontrado pares\",\n\t\"Running out of size limit (\": \"Superando el tamaño límite (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Establece ellímite a \\\" + site_info.next_size_limit + \\\"MB ändern\",\n\t\"Site size limit changed to {0}MB\": \"Límite de tamaño del sitio cambiado a {0}MBs\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Se ha publicado una nueva versión de esta página .<br>Recarga para ver el contenido modificado.\",\n\t\"This site requests permission:\": \"Este sitio solicita permiso:\",\n\t\"_(Accept)\": \"Conceder\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/fa.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"تبریک، درگاه <b>{0}</b> شما باز شده است.<br>شما یک عضو تمام شبکه ZeroNet هستید!\",\n\t\"Tor mode active, every connection using Onion route.\": \"حالت Tor فعال است، هر ارتباط از مسیریابی پیاز (Onion) استفاده می‌کند.\",\n\t\"Successfully started Tor onion hidden services.\": \"خدمات پنهان پیاز (Onion) Tor با موفقیت راه‌اندازی شد.\",\n\t\"Unable to start hidden services, please check your config.\": \"قادر به راه‌اندازی خدمات پنهان نیستیم، لطفا تنظیمات خود را بررسی نمایید.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"برای ارتباطات سریعتر درگاه <b>{0}</b> را بر روی مسیریاب (روتر) خود باز نمایید.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"ارتباط شما محدود‌شده است. لطفا درگاه <b>{0}</b> را در مسیریاب (روتر) خود باز نمایید\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"یا پیکربندی Tor را انجام دهید تا به یک عضو تمام شبکه ZeroNet تبدیل شوید.\",\n\n\t\"Select account you want to use in this site:\": \"حسابی را که می‌خواهید در این سایت استفاده کنید، انتخاب کنید:\",\n\t\"currently selected\": \"در حال حاضر انتخاب‌شده\",\n\t\"Unique to site\": \"مختص به سایت\",\n\n\t\"Content signing failed\": \"امضای محتوا با شکست مواجه شد\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"محتوا در صف انتشار با {0:.0f} ثانیه تاخیر قرار گرفت.\",\n\t\"Content published to {0} peers.\": \"محتوا برای {0} تعداد همتا انتشار یافت.\",\n\t\"No peers found, but your content is ready to access.\": \"همتایی یافت نشد، اما محتوای شما آماده دسترسی است.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"ارتباط شبکه شما محدود‌شده است. لطفا درگاه <b>{0}</b> را\",\n\t\"on your router to make your site accessible for everyone.\": \"در مسیریاب (روتر) خود باز کنید تا سایت خود را برای همه در دسترس قرار دهید.\",\n\t\"Content publish failed.\": \"انتشار محتوا موفق نبود.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"این فایل همچنان همگام است، اگز شما آن را بنویسید، ممکن است محتوای قبلی از‌بین رود.\",\n\t\"Write content anyway\": \"در هر صورت محتوا را بنویس\",\n\t\"New certificate added:\": \"گواهی جدیدی افزوده شد:\",\n\t\"You current certificate:\": \"گواهی فعلی شما:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"تغییرش بده به {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"گواهینامه به: <b>{auth_type}/{auth_user_name}@{domain}</b> تغییر پیدا کرد.\",\n\t\"Site cloned\": \"سایت همسان‌سازی شد\",\n\n\t\"You have successfully changed the web interface's language!\": \"شما با موفقیت زبان رابط وب را تغییر دادید!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"به دلیل ذخیره‌سازی در مرور‌گر، امکان دارد تغییر شکل کامل چند دقیقه طول بکشد.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"اتصال با <b>UiServer Websocket</b> قطع شد. اتصال دوباره...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"ارتباط با <b>UiServer Websocket</b> دوباره بر‌قرار شد.\",\n\t\"UiServer Websocket error, please reload the page.\": \"خطای UiServer Websocket, لطفا صفحه را دوباره بارگیری کنید.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;برقراری ارتباط...\",\n\t\"Site size: <b>\": \"حجم سایت: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> بیشتر از پیش‌فرض مجاز است \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"سایت را باز کرده و محدوده حجم را به \\\" + site_info.next_size_limit + \\\"MB تنظیم کن\",\n\t\" files needs to be downloaded\": \" فایل‌هایی که نیاز است، دانلود شوند\",\n\t\" downloaded\": \" دانلود شد\",\n\t\" download failed\": \" دانلود موفق نبود\",\n\t\"Peers found: \": \"چند همتا یافت شد: \",\n\t\"No peers found\": \"همتایی یافت نشد\",\n\t\"Running out of size limit (\": \"عبور کرده از محدوده حجم (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"محدوده را به \\\" + site_info.next_size_limit + \\\"MB تنظیم کن\",\n\t\"Site size limit changed to {0}MB\": \"محدوده حجم سایت به {0}MB تغییر کرد\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" نسخه جدیدی از این صفحه منتشر شده است.<br> برای مشاهده محتوای تغییر‌یافته دوباره بارگیری نمایید.\",\n\t\"This site requests permission:\": \"این سایت درخواست مجوز می‌کند:\",\n\t\"_(Accept)\": \"_(پذیرفتن)\"\n}\n"
  },
  {
    "path": "src/Translate/languages/fr.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Félicitations, le port (<b>{0}</b>) est ouvert.<br>Vous êtes maintenant membre de ZeroNet!!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor actif, toutes les connexions utilisent un routage Onion.\",\n\t\"Successfully started Tor onion hidden services.\": \"Tor activé avec succès.\",\n\t\"Unable to start hidden services, please check your config.\": \"Impossible d'activer Tor, veuillez vérifier votre configuration.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Pour une meilleure connectivité, ouvrez le port <b>{0}</b> sur votre routeur.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Connectivité limitée. Veuillez ouvrir le port <b>{0}</b> sur votre routeur\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"ou configurez Tor afin d'avoir accès aux pairs ZeroNet Onion.\",\n\n\t\"Select account you want to use in this site:\": \"Sélectionnez le compte que vous voulez utiliser pour ce site:\",\n\t\"currently selected\": \"présentement sélectionné\",\n\t\"Unique to site\": \"Unique au site\",\n\n\t\"Content signing failed\": \"Échec à la signature du contenu\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Publication du contenu différée {0:.0f} secondes.\",\n\t\"Content published to {0} peers.\": \"Contenu publié à {0} pairs.\",\n\t\"No peers found, but your content is ready to access.\": \"Aucun pair trouvé, mais votre contenu est accessible.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Connectivité limitée. Veuillez ouvrir le port <b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"sur votre routeur pour que votre site soit accessible à tous.\",\n\t\"Content publish failed.\": \"Échec de la publication du contenu.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Ce fichier n'est pas à jour, si vous le modifiez maintenant une version antérieure pourrait être perdue.\",\n\t\"Write content anyway\": \"Enregistrer quand même\",\n\t\"New certificate added:\": \"Nouveau cetificat ajouté :\",\n\t\"You current certificate:\": \"Votre certificat actuel :\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Changer pour {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certificat changé pour : <b>{auth_type}/{auth_user_name}@{domain}</b>-ra.\",\n\t\"Site cloned\": \"Site cloné\",\n\n\t\"You have successfully changed the web interface's language!\": \"Vous avez modifié la langue d'affichage avec succès!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"En fonction du cache du navigateur, la modification pourrait prendre quelques minutes.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Connexion avec <b>UiServer Websocket</b> rompue. Reconnexion...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Connexion avec <b>UiServer Websocket</b> rétablie.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Erreur du UiServer Websocket, veuillez recharger la page.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Connexion...\",\n\t\"Site size: <b>\": \"Taille du site : <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> est plus large que la taille permise par défaut \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Ouvrez le site et augmentez la taille maximale à \\\" + site_info.next_size_limit + \\\"MB-ra\",\n\t\" files needs to be downloaded\": \" fichiers doivent être téléchargés\",\n\t\" downloaded\": \" téléchargés\",\n\t\" download failed\": \" échec de téléchargement\",\n\t\"Peers found: \": \"Pairs trouvés: \",\n\t\"No peers found\": \"Aucun pair trouvé\",\n\t\"Running out of size limit (\": \"Vous approchez la taille maximale (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Augmentez la taille maximale à \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Site size limit changed to {0}MB\": \"Taille maximale du site changée à {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Une nouvelle version de cette page vient d'être publiée.<br> Rechargez pour voir les modifications.\",\n\t\"This site requests permission:\": \"Ce site requiert une permission :\",\n\t\"_(Accept)\": \"Autoriser\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/hu.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Gratulálunk, a portod (<b>{0}</b>) nyitva van.<br>Teljes értékű tagja vagy a hálózatnak!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor mód aktív, minden kapcsolat az Onion hálózaton keresztül történik.\",\n\t\"Successfully started Tor onion hidden services.\": \"Sikeresen elindultak a Tor onion titkos szolgáltatások.\",\n\t\"Unable to start hidden services, please check your config.\": \"Nem sikerült elindítani a Tor onion szolgáltatásokat. Kérjük, ellenőrizd a beállításokat!\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"A gyorsabb kapcsolatok érdekében nyisd ki a <b>{0}</b> portot a routereden.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"A kapcsolatod korlátozott. Kérjük, nyisd ki a <b>{0}</b> portot a routereden\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"vagy állítsd be a Tor kliensed, hogy teljes értékű tagja legyél a hálózatnak!\",\n\n\t\"Select account you want to use in this site:\": \"Válaszd ki az oldalhoz használt felhasználónevet:\",\n\t\"currently selected\": \"jelenleg kijelölt\",\n\t\"Unique to site\": \"Egyedi az oldalon\",\n\n\t\"Content signing failed\": \"Tartalom aláírása sikeretelen\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Tartalom publikálása elhalasztva {0:.0f} másodperccel.\",\n\t\"Content published to {0} peers.\": \"Tartalom publikálva {0} fél részére.\",\n\t\"No peers found, but your content is ready to access.\": \"Aktív csatlakozási pont nem található, de a tartalmad készen áll a kiszolgálásra.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"A kapcsolatod korlátozott. Kérjük, nyisd ki a <b>{0}</b> portot\",\n\t\"on your router to make your site accessible for everyone.\": \"a routereden, hogy az oldalad mindenki számára elérhető legyen.\",\n\t\"Content publish failed.\": \"Sikertelen tartalom publikálás.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Ez a fájl még letöltés alatt van, ha most felülírod a korábbi tartalma elveszhet.\",\n\t\"Write content anyway\": \"Felülírás\",\n\t\"New certificate added:\": \"Új tanúsítvány hozzáadva:\",\n\t\"You current certificate:\": \"A jelenlegi tanúsítványod: \",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Változtatás {auth_type}/{auth_user_name}@{domain}-ra\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"A tanúsítvány megváltozott <b>{auth_type}/{auth_user_name}@{domain}</b>-ra.\",\n\t\"Site cloned\": \"Az oldal klónozva\",\n\n\t\"You have successfully changed the web interface's language!\": \"Sikeresen átállítottad a web felület nyelvét!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"A böngésző cache-elése miatt egy pár percig eltarthat a teljes átállás.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Az <b>UiServer Websocket</b> kapcsolat megszakadt. Újracsatlakozás...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Az <b>UiServer Websocket</b> kapcsolat visszaállt.\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocket hiba, töltsd újra az oldalt!\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Csatlakozás...\",\n\t\"Site size: <b>\": \"Oldal mérete: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> nagyobb, mint az engedélyezett \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Az oldal megnyitása és a korlát módosítása \\\" + site_info.next_size_limit + \\\"MB-ra\",\n\t\" files needs to be downloaded\": \" fájlt kell letölteni\",\n\t\" downloaded\": \" letöltve\",\n\t\" download failed\": \" letöltés sikertelen\",\n\t\"Peers found: \": \"Talált csatlakozási pontok: \",\n\t\"No peers found\": \"Nincs csatlakozási pont\",\n\t\"Running out of size limit (\": \"Az oldal hamarosan eléri a méretkorlátot (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"A korlát módosítása \\\" + site_info.next_size_limit + \\\"MB-ra\",\n\t\"Site size limit changed to {0}MB\": \"A méretkorlát módosítva {0}MB-ra\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \"Az oldal épp most módosult<br>A megváltozott tartalomért töltsd újra!\",\n\t\"This site requests permission:\": \"Az oldal megtekintéséhez szükséges jog:\",\n\t\"_(Accept)\": \"Engedélyezés\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/it.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Congratulazioni, la tua porta <b>{0}</b> è aperta.<br>Ora sei un membro effettivo della rete ZeroNet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Modalità Tor attiva, ogni connessione sta usando la rete Onion.\",\n\t\"Successfully started Tor onion hidden services.\": \"Servizi Tor onion nascosti avviati con successo.\",\n\t\"Unable to start hidden services, please check your config.\": \"Impossibile avviare i servizi nascosti. Si prega di controllare la propria configurazione!\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Per avere connessioni più veloci aprire la porta <b>{0}</b> sul router.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"La tua connessione è limitata. Aprire la porta <b>{0}</b> sul router\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"o configurare Tor per diventare membro effettivo della rete ZeroNet!\",\n\n\t\"Select account you want to use in this site:\": \"Seleziona l'account che vuoi utilizzare per questo sito:\",\n\t\"currently selected\": \"attualmente selezionato\",\n\t\"Unique to site\": \"Unico sul sito\",\n\n\t\"Content signing failed\": \"Firma contenuti fallita\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Pubblicazione contenuti in coda per {0:.0f} secondi.\",\n\t\"Content published to {0} peers.\": \"Contenuti pubblicati su {0} peer.\",\n\t\"No peers found, but your content is ready to access.\": \"Nessun peer trovato, ma i tuoi contenuti sono pronti per l'accesso.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"La tua connessione di rete è limitata. Aprire la porta <b>{0}</b> \",\n\t\"on your router to make your site accessible for everyone.\": \"sul router, per rendere il sito accessibile a chiunque.\",\n\t\"Content publish failed.\": \"Pubblicazione contenuti fallita.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Questo file è ancora in sincronizzazione, se viene modificato i contenuti precedenti andranno persi.\",\n\t\"Write content anyway\": \"Scrivere comunque i contenuti\",\n\t\"New certificate added:\": \"Aggiunto nuovo certificato:\",\n\t\"You current certificate:\": \"Il tuo attuale certificato:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Cambiarlo in {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certificato cambianto in: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Sito clonato\",\n\n\t\"You have successfully changed the web interface's language!\": \"Hai cambiato con successo la lingua dell'interfaccia web!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"La trasformazione completa potrebbe richiedre alcuni minuti a causa della cache del browser.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"La connessione con <b>UiServer Websocket</b> è andata persa. Riconnessione...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Connessione con <b>UiServer Websocket</b> recuperata.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Errore UiServer Websocket, ricaricare la pagina!\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Connessione...\",\n\t\"Site size: <b>\": \"Dimensione del sito: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> è più grande del valore predefinito consentito \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Aprire il sito e impostare la dimensione limite a \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" i file devono essere scaricati\",\n\t\" downloaded\": \" scaricati\",\n\t\" download failed\": \" scaricamento fallito\",\n\t\"Peers found: \": \"Peer trovati: \",\n\t\"No peers found\": \"Nessun peer trovato\",\n\t\"Running out of size limit (\": \"Superato il limite di spazio (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Imposta il limite a \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Site size limit changed to {0}MB\": \"Limite di spazio cambiato a {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \"E' stata rilasciata una nuova versione di questa pagina<br>Ricaricare per vedere il contenuto modificato!\",\n\t\"This site requests permission:\": \"Questo sito richiede permessi:\",\n\t\"_(Accept)\": \"Concedere\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/jp.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"おめでとうございます。ポート <b>{0}</b> が開きました。これでZeroNetネットワークのメンバーです。\",\n\t\"Tor mode active, every connection using Onion route.\": \"Torモードがアクティブです、全ての接続はOnionルートを使用します。\",\n\t\"Successfully started Tor onion hidden services.\": \"Tor onionサービスを正常に開始しました。\",\n\t\"Unable to start hidden services, please check your config.\": \"非表示のサービスを開始できません。設定を確認してください。\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"接続を高速化するにはルーターのポート <b>{0}</b> を開けてください。\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"接続が制限されています。ルーターのポート <b>{0}</b> を開けてください。\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"または、TorをZeroNetネットワークのメンバーになるように設定してください。\",\n\n\t\"Select account you want to use in this site:\": \"このサイトで使用するアカウントを選択:\",\n\t\"No certificate\": \"証明書がありません\",\n\t\"currently selected\": \"現在選択中\",\n\t\"Unique to site\": \"サイト固有\",\n\n\t\"Content signing failed\": \"コンテンツの署名に失敗\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"コンテンツの公開は{0:.0f}秒のキューに入れられました。\",\n\t\"Content published to {0}/{1} peers.\": \"サイトの更新を通知済 {0}/{1} ピア\",\n\t\"Content published to {0} peers.\": \"{0}ピアに公開されたコンテンツ。\",\n\t\"No peers found, but your content is ready to access.\": \"ピアは見つかりませんでしたが、コンテンツにアクセスする準備ができました。\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"ネットワーク接続が制限されています。ポート <b>{0}</b> を開いて、\",\n\t\"on your router to make your site accessible for everyone.\": \"誰でもサイトにアクセスできるようにしてください。\",\n\t\"Content publish failed.\": \"コンテンツの公開に失敗しました。\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"このファイルはまだ同期しています。今すぐ書き込むと、前のコンテンツが失われる可能性があります。\",\n\t\"Write content anyway\": \"とにかくコンテンツを書く\",\n\t\"New certificate added:\": \"新しい証明書が追加されました:\",\n\t\"You current certificate:\": \"現在の証明書:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"{auth_type}/{auth_user_name}@{domain} に変更\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"変更後の証明書: <b>{auth_type}/{auth_user_name}@{domain}</b>\",\n\t\"Site cloned\": \"複製されたサイト\",\n\n\t\"You have successfully changed the web interface's language!\": \"Webインターフェースの言語が正常に変更されました！\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"ブラウザのキャッシュにより、完全な変換には数分かかる場合があります。\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"<b>UiServer Websocket</b>との接続が失われました。再接続しています...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"<b>UiServer Websocket</b>との接続が回復しました。\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocketエラー、ページをリロードしてください。\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;接続しています...\",\n\t\"Site size: <b>\": \"サイトサイズ: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b>はデフォルトの許容値よりも大きいです。 \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"サイトを開き、サイズ制限を \\\" + site_info.next_size_limit + \\\"MB に設定\",\n\t\" files needs to be downloaded\": \" ファイルをダウンロードする必要があります\",\n\t\" downloaded\": \" ダウンロード\",\n\t\" download failed\": \" ダウンロード失敗\",\n\t\"Peers found: \": \"ピアが見つかりました: \",\n\t\"No peers found\": \"ピアが見つかりません\",\n\t\"Running out of size limit (\": \"サイズ制限を使い果たしました (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"制限を \\\" + site_info.next_size_limit + \\\"MB に設定\",\n\t\"Cloning site...\": \"サイトを複製中…\",  \n\t\"Site size limit changed to {0}MB\": \"サイトのサイズ制限が {0}MB に変更されました\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" このページの新しいバージョンが公開されました。<br>変更されたコンテンツを見るには再読み込みしてください。\",\n\t\"This site requests permission:\": \"このサイトは権限を要求しています:\",\n\t\"_(Accept)\": \"_(許可)\",\n\t\n\t\"Save\": \"保存\",\n\t\"Trackers announcing\": \"トラッカーをお知らせ\",\n\t\"Error\": \"エラー\",\n\t\"Done\": \"完了\",\n\t\"Tracker connection error detected.\": \"トラッカー接続エラーが検出されました。\",\n\n\t\"Update <b>ZeroNet client</b> to latest version?\": \"<b>ZeroNetクライアント</b>を最新版に更新しますか？\",\n\t\"Update\": \"更新\",\n\t\"Restart <b>ZeroNet client</b>?\": \"ZeroNetクライアントを再起動しますか？\",\n\t\"Restart\": \"再起動\",\n\t\"Shut down <b>ZeroNet client</b>?\": \"<b>ZeroNetクライアント</b>を終了しますか？\",\n\t\"Shut down\": \"終了\"\n}\n"
  },
  {
    "path": "src/Translate/languages/nl.json",
    "content": "{\n    \"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Gefeliciteerd, je poort <b>{0}</b> is geopend.<br>Je bent een volledig lid van het ZeroNet netwerk!\",\n    \"Tor mode active, every connection using Onion route.\": \"Tor modus actief, elke verbinding gebruikt een Onion route.\",\n    \"Successfully started Tor onion hidden services.\": \"Tor onion verborgen diensten zijn met succes gestart.\",\n    \"Unable to start hidden services, please check your config.\": \"Het was niet mogelijk om verborgen diensten te starten, controleer je configuratie.\",\n    \"For faster connections open <b>{0}</b> port on your router.\": \"Voor snellere verbindingen open je de poort <b>{0}</b> op je router.\",\n    \"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Je verbinding is beperkt. Open altjeblieft poort <b>{0}</b> op je router\",\n    \"or configure Tor to become a full member of the ZeroNet network.\": \"of configureer Tor om een volledig lid van het ZeroNet netwerk te worden.\",\n\n    \"Select account you want to use in this site:\": \"Selecteer het account die je wilt gebruiken binnen deze site:\",\n    \"currently selected\": \"huidige selectie\",\n    \"Unique to site\": \"Uniek voor deze site\",\n\n    \"Content signing failed\": \"Inhoud ondertekenen mislukt\",\n    \"Content publish queued for {0:.0f} seconds.\": \"Publiceren van inhoud staat in de wachtrij voor {0:.0f} seconden.\",\n    \"Content published to {0} peers.\": \"Inhoud is gepubliceerd naar {0} peers\",\n    \"No peers found, but your content is ready to access.\": \"Geen peers gevonden, maar je inhoud is klaar voor toegang.\",\n    \"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Je netwerkverbinding is beperkt. Open alsjeblieft poort <b>{0}</b>\",\n    \"on your router to make your site accessible for everyone.\": \"op je router om je site toegankelijk te maken voor iedereen.\",\n    \"Content publish failed.\": \"Inhoud publicatie mislukt.\",\n    \"This file still in sync, if you write it now, then the previous content may be lost.\": \"Dit bestand is nog in sync, als je het nu overschrijft, dan is mogelijk de vorige inhoud verloren.\",\n    \"Write content anyway\": \"Inhoud toch schrijven\",\n    \"New certificate added:\": \"Nieuw certificaat toegevoegd:\",\n    \"You current certificate:\": \"Je huidige certificaat:\",\n    \"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Verander het naar {auth_type}/{auth_user_name}@{domain}\",\n    \"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certificaat veranderd naar: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n    \"Site cloned\": \"Site gecloned\",\n\n    \"You have successfully changed the web interface's language!\": \"Je hebt met succes de taal van de web interface aangepast!\",\n    \"Due to the browser's caching, the full transformation could take some minute.\": \"Door caching van je browser kan de volledige transformatie enkele minuten duren.\",\n\n    \"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Verbinding met <b>UiServer Websocket</b> verbroken. Opnieuw verbinden...\",\n    \"Connection with <b>UiServer Websocket</b> recovered.\": \"Verbinding met <b>UiServer Websocket</b> hersteld.\",\n    \"UiServer Websocket error, please reload the page.\": \"UiServer Websocket fout, herlaad alsjeblieft de pagina.\",\n    \"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Verbinden...\",\n    \"Site size: <b>\": \"Site grootte <b>\",\n    \"MB</b> is larger than default allowed \": \"MB</b> is groter dan de standaard toegestaan \",\n    \"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Open de site en stel de limeit op de grootte in op \\\" + site_info.next_size_limit + \\\"MB\",\n    \" files needs to be downloaded\": \" bestanden moeten worden gedownload\",\n    \" downloaded\": \" gedownload\",\n    \" download failed\": \" download mislukt\",\n    \"Peers found: \": \"Peers gevonden: \",\n    \"No peers found\": \"Geen peers gevonden\",\n    \"Running out of size limit (\": \"Limeit op grootte bereikt (\",\n    \"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Stel limiet in op \\\" + site_info.next_size_limit + \\\"MB\",\n    \"Site size limit changed to {0}MB\": \"Site limiet op grootte is veranderd naar {0}MB\",\n    \" New version of this page has just released.<br>Reload to see the modified content.\": \" Een nieuwe versie van deze pagina is zojuist uitgekomen.<br>Herlaad de pagina om de bijgewerkte inhoud te zien.\",\n    \"This site requests permission:\": \"Deze site vraagt om permissie:\",\n    \"_(Accept)\": \"Toekennen\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/pl.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Gratulacje, twój port <b>{0}</b> jest otwarty.<br>Jesteś pełnoprawnym użytkownikiem sieci ZeroNet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tryb Tor aktywny, każde połączenie przy użyciu trasy Cebulowej.\",\n\t\"Successfully started Tor onion hidden services.\": \"Pomyślnie zainicjowano ukryte usługi cebulowe Tor.\",\n\t\"Unable to start hidden services, please check your config.\": \"Niezdolny do uruchomienia ukrytych usług, proszę sprawdź swoją konfigurację.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Dla szybszego połączenia otwórz <b>{0}</b> port w swoim routerze.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Połączenie jest ograniczone. Proszę, otwórz port <b>{0}</b> w swoim routerze\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"bądź skonfiguruj Tora by stać się pełnoprawnym użytkownikiem sieci ZeroNet.\",\n\n\t\"Select account you want to use in this site:\": \"Wybierz konto którego chcesz użyć na tej stronie:\",\n\t\"currently selected\": \"aktualnie wybrany\",\n\t\"Unique to site\": \"Unikatowy dla strony\",\n\n\t\"Content signing failed\": \"Podpisanie treści zawiodło\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Publikacja treści wstrzymana na {0:.0f} sekund(y).\",\n\t\"Content published to {0} peers.\": \"Treść opublikowana do {0} uzytkowników.\",\n\t\"No peers found, but your content is ready to access.\": \"Nie odnaleziono użytkowników, ale twoja treść jest dostępna.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Twoje połączenie sieciowe jest ograniczone. Proszę, otwórz port <b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"w swoim routerze, by twoja strona mogłabyć dostępna dla wszystkich.\",\n\t\"Content publish failed.\": \"Publikacja treści zawiodła.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Ten plik wciąż się synchronizuje, jeśli zapiszesz go teraz, poprzednia treść może zostać utracona.\",\n\t\"Write content anyway\": \"Zapisz treść mimo wszystko\",\n\t\"New certificate added:\": \"Nowy certyfikat dodany:\",\n\t\"You current certificate:\": \"Twój aktualny certyfikat: \",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Zmień na {auth_type}/{auth_user_name}@{domain}-ra\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certyfikat zmieniony na <b>{auth_type}/{auth_user_name}@{domain}</b>-ra.\",\n\t\"Site cloned\": \"Strona sklonowana\",\n\n\t\"You have successfully changed the web interface's language!\": \"Pomyślnie zmieniono język interfejsu stron!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Ze względu na buforowanie przeglądarki, pełna zmiana może zająć parę minutę.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Połączenie z <b>UiServer Websocket</b> zostało przerwane. Ponowne łączenie...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Połączenie z <b>UiServer Websocket</b> przywrócone.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Błąd UiServer Websocket, prosze odświeżyć stronę.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Łączenie...\",\n\t\"Site size: <b>\": \"Rozmiar strony: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> jest większy niż domyślnie dozwolony \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Otwórz stronę i ustaw limit na \\\" + site_info.next_size_limit + \\\"MBów\",\n\t\" files needs to be downloaded\": \" pliki muszą zostać ściągnięte\",\n\t\" downloaded\": \" ściągnięte\",\n\t\" download failed\": \" ściąganie nie powiodło się\",\n\t\"Peers found: \": \"Odnaleziono użytkowników: \",\n\t\"No peers found\": \"Nie odnaleziono użytkowników\",\n\t\"Running out of size limit (\": \"Limit rozmiaru na wyczerpaniu (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Ustaw limit na \\\" + site_info.next_size_limit + \\\"MBów\",\n\t\"Site size limit changed to {0}MB\": \"Rozmiar limitu strony zmieniony na {0}MBów\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \"Nowa wersja tej strony właśnie została wydana.<br>Odśwież by zobaczyć nową, zmodyfikowaną treść strony.\",\n\t\"This site requests permission:\": \"Ta strona wymaga uprawnień:\",\n\t\"_(Accept)\": \"Przyznaj uprawnienia\",\n\n\t\"Sign and publish\": \"Podpisz i opublikuj\",\n\t\"Restart <b>ZeroNet client</b>?\": \"Uruchomić ponownie klienta ZeroNet?\",\n\t\"Restart\": \"Uruchom ponownie\"\n}\n"
  },
  {
    "path": "src/Translate/languages/pt-br.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Parabéns, a porta<b>{0}</b> está aberta.<br>Você é um membro completo da rede ZeroNet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Modo Tor ativado, todas as conexões usam a rota Onion.\",\n\t\"Successfully started Tor onion hidden services.\": \"Os serviços ocultos Tor onion foram inciados com sucesso.\",\n\t\"Unable to start hidden services, please check your config.\": \"Não foi possível iniciar os serviços ocultos, por favor verifique suas configurações.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Para conexões mais rápidas, abra a porta <b>{0}</b> em seu roteador.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Sua conexão está restrita. Por favor, abra a porta <b>{0}</b> em seu roteador\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"ou configure o Tor para se tornar um membro completo da rede ZeroNet.\",\n\n\t\"Select account you want to use in this site:\": \"Selecione a conta que deseja usar nesse site:\",\n\t\"currently selected\": \"atualmente selecionada\",\n\t\"Unique to site\": \"Única para o site\",\n\n\t\"Content signing failed\": \"Assinatura de conteúdo falhou\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Publicação de conteúdo na fila por {0:.0f} segundos.\",\n\t\"Content published to {0} peers.\": \"Conteúdo publicado para {0} peers.\",\n\t\"No peers found, but your content is ready to access.\": \"Nenhum peer encontrado, mas seu conteúdo está pronto para ser acessado.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Sua conexão de rede está restrita. Por favor, abra a porta <b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"em seu roteador para tornar seu site acessível para todos.\",\n\t\"Content publish failed.\": \"Publicação de conteúdo falhou.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Esse arquivo ainda está sincronizado, se escreve-lo agora o conteúdo anterior poderá ser perdido.\",\n\t\"Write content anyway\": \"Escrever o conteúdo mesmo assim\",\n\t\"New certificate added:\": \"Novo certificado adicionado:\",\n\t\"You current certificate:\": \"Seu certificado atual:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Alterar para {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certificado alterado para: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Site clonado\",\n\n\t\"You have successfully changed the web interface's language!\": \"Você alterou o idioma da interface web com sucesso!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Devido ao cache do navegador, a transformação completa pode levar alguns minutos.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"A conexão com <b>UiServer Websocket</b> foi perdida. Reconectando...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Conexão com <b>UiServer Websocket</b> recuperada.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Erro de UiServer Websocket, por favor atualize a página.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Conectando...\",\n\t\"Site size: <b>\": \"Tamanho do site: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> é maior do que o tamanho permitido por padrão\",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Abrir site e definir limite de tamanho para \\\" + site_info.next_size_limit + \\\"MBs\",\n\t\" files needs to be downloaded\": \" os arquivos precisam ser baixados\",\n\t\" downloaded\": \" baixados\",\n\t\" download failed\": \" falha no download\",\n\t\"Peers found: \": \"Peers encontrados: \",\n\t\"No peers found\": \"Nenhum peer encontrado\",\n\t\"Running out of size limit (\": \"Passando do tamanho limite (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Definir limite para \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Site size limit changed to {0}MB\": \"Limite de tamanho do site alterado para {0}MBs\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Uma nova versão desse site acaba de ser publicada.<br>Atualize para ver o conteúdo modificado.\",\n\t\"This site requests permission:\": \"Esse site solicita permissão:\",\n\t\"_(Accept)\": \"Conceder\",\n\t\n\t\"Save\": \"Salvar\",\n\t\"Trackers announcing\": \"Trackers anunciando\",\n\t\"Error\": \"Erro\",\n\t\"Done\": \"Concluído\",\n\t\"Tracker connection error detected.\": \"Erro de conexão com tracker foi detectado.\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/ru.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Поздравляем, ваш порт <b>{0}</b> открыт.<br>Вы полноценный участник сети ZeroNet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Режим Tor включен, все соединения осуществляются через Tor.\",\n\t\"Successfully started Tor onion hidden services.\": \"Скрытый сервис Tor запущено успешно.\",\n\t\"Unable to start hidden services, please check your config.\": \"Ошибка при запуске скрытого сервиса, пожалуйста проверьте настройки\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Для более быстрой работы сети откройте <b>{0}</b> порт на вашем роутере.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Подключение ограничено. Пожалуйста откройте <b>{0}</b> порт на вашем роутере\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"или настройте Tor что бы стать полноценным участником сети ZeroNet.\",\n\n\t\"Select account you want to use in this site:\": \"Выберите аккаунт для использования на этом сайте:\",\n\t\"currently selected\": \"сейчас выбран\",\n\t\"Unique to site\": \"Уникальный для этого сайта\",\n\n\t\"Content signing failed\": \"Подпись контента не удалась\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Публикация контента поставлена в очередь {0:.0f} секунд.\",\n\t\"Content published to {0} peers.\": \"Контент опубликован на {0} пирах.\",\n\t\"No peers found, but your content is ready to access.\": \"Пиры не найдены, но ваш контент доступен.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Ваше подключение ограничено. Пожалуйста откройте <b>{0}</b> порт. \",\n\t\"on your router to make your site accessible for everyone.\": \"на вашем роутере, что бы ваш сайт стал доступнг посетителям.\",\n\t\"Content publish failed.\": \"Ошибка при публикации контента.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Этот файл всё еще синхронизируется, если продолжить его изменение, предыдущий контент может быть потерян.\",\n\t\"Write content anyway\": \"Записать контент в любом случае\",\n\t\"New certificate added:\": \"Добавлен новый сертификат:\",\n\t\"You current certificate:\": \"Ваш текущий сертификат: \",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Изменить его на {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Сертификат изменен на: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Сайт склонирован\",\n\n\t\"You have successfully changed the web interface's language!\": \"Язык интерфейса успешно изменен!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"В зависимости от работы вашего браузера полное преобразование может занять пару минут.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Подключение к <b>UiServer Websocket</b> прервано. Переподключаюсь...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Подключение к <b>UiServer Websocket</b> восстановлено.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Ошибка <b>UiServer Websocket</b>, перезагрузите страницу!\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Подключение...\",\n\t\"Site size: <b>\": \"Размер сайта: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> больше чем разрешено по умолчанию \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Открыть сайт и установить лимит занимаемого места на \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" файлы должны быть загружены\",\n\t\" downloaded\": \" загружено\",\n\t\" download failed\": \" ошибка загрузки\",\n\t\"Peers found: \": \"Пиров найдено: \",\n\t\"No peers found\": \"Пиры не найдены\",\n\t\"Running out of size limit (\": \"Доступное место закончилось (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Установить лимит на \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Site size limit changed to {0}MB\": \"Лимит памяти на диске изменен на {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \"Доступна новая версия данной страницы<br>Обновите страницу, что бы увидеть изменения!\",\n\t\"This site requests permission:\": \"Данный сайт запрашивает разрешения:\",\n\t\"_(Accept)\": \"Предоставить\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/sk.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Blahoželáme, váš port <b>{0}</b> je otvorený.<br>Ste úplným členom siete ZeroNet!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor mód aktívny, všetky spojenia teraz používajú Onion sieť.\",\n\t\"Successfully started Tor onion hidden services.\": \"Tor úspešne spustený.\",\n\t\"Unable to start hidden services, please check your config.\": \"Nebolo možné spustiť Tor, prosím skontrolujte nastavenia.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Pre rýchlejšie spojenie otvorte na vašom routery port <b>{0}</b>\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Vaše pripojenie je obmedzené. Prosím otvorte port <b>{0}</b> na vašom routery.\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"alebo nastavte Tor aby ste sa tali plným členom siete ZeroNet.\",\n\n\t\"Select account you want to use in this site:\": \"Zvoľte účet ktorý chcete používať na tejto stránke:\",\n\t\"currently selected\": \"aktuálne zvolené\",\n\t\"Unique to site\": \"Unikátny pre stránku\",\n\n\t\"Content signing failed\": \"Podpísanie obsahu zlyhalo\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Podpísanie obsahu bude na rade za {0:.0f} sekúnd\",\n\t\"Content published to {0} peers.\": \"Obsah publikovaný {0} peer-erom\",\n\t\"No peers found, but your content is ready to access.\": \"Neboli nájdený žiadny peer-ery, ale váš obsah je pripravený pre prístup.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Vaše pripojenie k sieti je obmedzené. Prosím otvorte port <b>{0}</b> na vašom routery.\",\n\t\"on your router to make your site accessible for everyone.\": \"na vašom routery aby bola vaša stránka prístupná pre všetkých.\",\n\t\"Content publish failed.\": \"Publikovanie obsahu zlyhalo.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Tento súbor sa stále synchronizuje, ak v ňom spravíte zmeny, predchádzajúci obsah sa môže stratiť.\",\n\t\"Write content anyway\": \"Aj tak spraviť zmeny\",\n\t\"New certificate added:\": \"Pridaný nový certifikát:\",\n\t\"You current certificate:\": \"Váš aktuálny certifikát:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Zvoľte to na {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Certifikát zmenený na: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Stránka naklonovaná\",\n\n\t\"You have successfully changed the web interface's language!\": \"Úspešne ste zmenili jazyk webového rozhrania!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Kôli cachu webového prehliadavača, ceľková transformácia môže chvíĺu trvať.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Spojenie s <b>UiServer Websocket</b> bolo stratené. Znovu pripájame...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Spojenie s <b>UiServer Websocket</b> obnovené.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Chyba UiServer Websocket-u, prosím znovu načítajte stránku.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Pripájanie...\",\n\t\"Site size: <b>\": \"Veľkosť stránky: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> je viac ako povolená hodnota\",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Otvoriť stránku a nastaviť limit veľkosti na \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" súbory je potrebné stiahnuť\",\n\t\" downloaded\": \" stiahnuté\",\n\t\" download failed\": \" sťahovanie zlyhalo\",\n\t\"Peers found: \": \"Peer-erov nájdených: \",\n\t\"No peers found\": \"Neboli nájdený žiadny peer-ery\",\n\t\"Running out of size limit (\": \"Presahuje povolený limit veľkosti pamäte (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Nastaviť limit na \\\" + site_info.next_size_limit + \\\"MB ändern\",\n\t\"Site size limit changed to {0}MB\": \"Limit veľkosti pamäte nastavený na {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Bola vydaná nová verzia tejto stránky.<br>Znovu načítajte túto stránku aby bolo vidieť zmeny.\",\n\t\"This site requests permission:\": \"Táto stránka vyžaduje povolenie:\",\n\t\"_(Accept)\": \"Udeliť\",\n\n\t\"on\": \"\",\n\t\"Oct\": \"Okt\",\n\t\"May\": \"Máj\",\n\t\"Jun\": \"Jún\",\n\t\"Jul\": \"Júl\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/sl.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Čestitke, vaša vrata <b>{0}</b> so odprta.<br>Postali ste polnopravni član ZeroNet omrežja!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Način Tor aktiven.\",\n\t\"Successfully started Tor onion hidden services.\": \"Storitve Tor uspešno zagnane.\",\n\t\"Unable to start hidden services, please check your config.\": \"Ni bilo mogoče zagnati Tor storitev. Preverite nastavitve.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Za hitrejše povezave na svojem usmerjevalniku odprite vrata <b>{0}</b>.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Vaša povezava je omejena. Na svojem usmerjevalniku odprite vrata <b>{0}</b>\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"ali nastavite Tor, da postanete polnopravni član ZeroNet omrežja.\",\n\n\t\"Select account you want to use in this site:\": \"Izberite račun, ki ga želite uporabiti na tem spletnem mestu:\",\n\t\"currently selected\": \"trenutno izbrano\",\n\t\"Unique to site\": \"Edinstven za spletno mesto\",\n\n\t\"Content signing failed\": \"Podpisovanje vsebine ni uspelo\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"Objava vsebine na čakanju za {0:.0f} sekund.\",\n\t\"Content published to {0} peers.\": \"Vsebina objavljena na {0} povezavah.\",\n\t\"No peers found, but your content is ready to access.\": \"Ni nobenih povezav, vendar je vaša vsebina pripravljena za dostop.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Vaša povezava je omejena. Prosimo, odprite vrata <b>{0}</b>\",\n\t\"on your router to make your site accessible for everyone.\": \"na vašem usmerjevalniku, da bo vaše spletno mesto dostopno za vse.\",\n\t\"Content publish failed.\": \"Objavljanje vsebine ni uspelo.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Ta datoteka se še vedno sinhronizira. Če jo uredite zdaj, se lahko zgodi, da bo prejšnja vsebina izgubljena.\",\n\t\"Write content anyway\": \"Vseeno uredi vsebino\",\n\t\"New certificate added:\": \"Dodano novo potrdilo:\",\n\t\"You current certificate:\": \"Trenutno potrdilo:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"Spremenite ga na {auth_type}/{auth_user_name}@{domain}\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"Potrdilo spremenjeno na: <b>{auth_type}/{auth_user_name}@{domain}</b>.\",\n\t\"Site cloned\": \"Stran klonirana\",\n\n\t\"You have successfully changed the web interface's language!\": \"Uspešno ste spremenili jezik spletnega vmesnika!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Zaradi predpomnjenja brskalnika lahko popolna preobrazba traja nekaj minut.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"Povezava z <b>UiServer Websocket</b> je bila izgubljena. Ponovno povezovanje ...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"Povezava z <b>UiServer Websocket</b> je vzpostavljena.\",\n\t\"UiServer Websocket error, please reload the page.\": \"Napaka UiServer Websocket. Prosimo osvežite stran.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Povezovanje ...\",\n\t\"Site size: <b>\": \"Velikost strani: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> je večja od dovoljenih\",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Odpri to stran in nastavi omejitev na \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" datotek mora biti prenešenih\",\n\t\" downloaded\": \" preneseno\",\n\t\" download failed\": \" prenos ni uspel\",\n\t\"Peers found: \": \"Najdene povezave: \",\n\t\"No peers found\": \"Ni najdenih povezav\",\n\t\"Running out of size limit (\": \"Zmanjkuje dovoljenega prostora (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Nastavi omejitev na \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Site size limit changed to {0}MB\": \"Omejitev strani nastavljena na{0} MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Ravnokar je bila objavljena nova različica te strani.<br>Osvežite jo, da boste videli novo vsebino.\",\n\t\"This site requests permission:\": \"Ta stran zahteva dovoljenja:\",\n\t\"_(Accept)\": \"Dovoli\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/tr.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"Tebrikler, portunuz (<b>{0}</b>) açık.<br>Artık ZeroNet ağına katıldınız!\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor aktif, tüm bağlantılar Onion yönlendircisini kullanıyor.\",\n\t\"Successfully started Tor onion hidden services.\": \"Gizli Tor hizmetleri başlatıldı.\",\n\t\"Unable to start hidden services, please check your config.\": \"Gizli hizmetler başlatılamadı, lütfen ayarlarınızı kontrol ediniz.\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"Daha hızlı bağlantı için <b>{0}</b> nolu portu bilgisayarınıza yönlendirin.\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"Sınırlı bağlantı. Lütfen, <b>{0}</b> nolu portu bilgisayarınıza yönlendirin\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"ya da ZeroNet ağına tam olarak katılabilmek için Tor'u kullanın.\",\n\n\t\"Select account you want to use in this site:\": \"Bu sitede kullanmak için bir hesap seçiniz:\",\n\t\"currently selected\": \"kullanılan\",\n\t\"Unique to site\": \"Bu site için benzersiz\",\n\n\t\"Content signing failed\": \"İçerik imzalama başarısız oldu\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"İçerik yayımlanmak üzere {0:.0f} saniyedir kuyrukta.\",\n\t\"Content published to {0} peers.\": \"İçerik {0} eşe dağıtıldı.\",\n\t\"No peers found, but your content is ready to access.\": \"Eş bulunamadı, ama içeriğiniz erişime hazır.\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"Sınırlı bağlantı. Lütfen, <b>{0}</b> nolu portu bilgisayarınıza yönlendirin\",\n\t\"on your router to make your site accessible for everyone.\": \"böylece sitenizi herkes için erişilebilir yapabilirsiniz\",\n\t\"Content publish failed.\": \"İçerik yayımlama başarısız oldu.\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"Bu dosya hala güncelleniyor, eğer şimdi kaydederseniz, önceki içerik kaybolabilir.\",\n\t\"Write content anyway\": \"Yine de kaydet\",\n\t\"New certificate added:\": \"Yeni sertifika eklendi:\",\n\t\"You current certificate:\": \"Kullanılan sertifikanız:\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"{auth_type}/{auth_user_name}@{domain} olarak değiştir.\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"<b>{auth_type}/{auth_user_name}@{domain}</b> olarak değiştirildi\",\n\t\"Site cloned\": \"Site klonlandı\",\n\n\t\"You have successfully changed the web interface's language!\": \"WEB ara yüzü için dil başarıyla değiştirildi!\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"Tam dönüşümün sağlanması, tarayıcı önbelleklemesi yüzünden zaman alabilir.\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"<b>UiServer Websocket</b> ile bağlantı kesildi. Yeniden bağlanılıyor...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"<b>UiServer Websocket</b> ile bağlantı yeniden kuruldu.\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocket hatası, lütfen sayfayı yenileyin.\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;Bağlanıyor...\",\n\t\"Site size: <b>\": \"Site boyutu: <b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> izin verilenden fazla \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Siteyi açın ve boyut sınırını \\\" + site_info.next_size_limit + \\\"MB'ye yükseltin\",\n\t\" files needs to be downloaded\": \" indirilmesi gereken dosyalar\",\n\t\" downloaded\": \" indirildi\",\n\t\" download failed\": \" indirme başarısız\",\n\t\"Peers found: \": \"Bulunan eşler: \",\n\t\"No peers found\": \"Eş bulunamadı\",\n\t\"Running out of size limit (\": \"Boyut sınırlamasını aştı (\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"Sınırlamayı \\\" + site_info.next_size_limit + \\\"MB'ye yükselt\",\n\t\"Site size limit changed to {0}MB\": \"Site boyut sınırlaması {0}MB olarak ayarlandı\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" Bu sayfanın yeni versiyonu yayımlandı.<br>Değişen içeriği görmek için yeniden yükleyiniz.\",\n\t\"This site requests permission:\": \"Bu site bir izin istiyor:\",\n\t\"_(Accept)\": \"İzin ver\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/zh-tw.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"祝賀，你的埠 (<b>{0}</b>) 已經打開。<br>你已經是 ZeroNet 網路的正式成員了！\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor 模式啟用，每個連接正在使用洋蔥路由。\",\n\t\"Successfully started Tor onion hidden services.\": \"成功啟動 Tor 洋蔥隱藏服務。\",\n\t\"Unable to start hidden services, please check your config.\": \"無法打開隱藏服務，請檢查你的配置。\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"為了更快的連接請在路由器上打開 <b>{0}</b> 埠。\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"你的連接受限制。請在你的路由器上打開 <b>{0}</b> 埠\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"或者配置你的 Tor 來成為 ZeroNet 的正式成員。\",\n\n\t\"Select account you want to use in this site:\": \"選擇你要在這個網站使用的帳戶：\",\n\t\"currently selected\": \"當前選擇\",\n\t\"Unique to site\": \"網站獨有身份\",\n\n\t\"Content signing failed\": \"內容簽署失敗\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"內容已加入 {0:.0f} 秒後的發佈隊列。\",\n\t\"Content published to {0}/{1} peers.\": \"內容已發佈到 {0}/{1} 個節點。\",\n\t\"Content published to {0} peers.\": \"內容已發佈到 {0} 個節點。\",\n\t\"No peers found, but your content is ready to access.\": \"找不到節點，但是你的內容已經準備好被訪問。\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"你的網路連接受限制。請在你的路由器上打開 <b>{0}</b> 埠\",\n\t\"on your router to make your site accessible for everyone.\": \"確保你的網站能被每一個人訪問。\",\n\t\"Content publish failed.\": \"內容發佈失敗。\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"這個檔仍然在同步中，如果你現在寫入它，之前的內容可能會被丟失。\",\n\t\"Write content anyway\": \"強制寫入內容\",\n\t\"New certificate added:\": \"新證書：\",\n\t\"You current certificate:\": \"你當前的證書：\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"改變至 {auth_type}/{auth_user_name}@{domain}-ra\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"證書更改至：<b>{auth_type}/{auth_user_name}@{domain}</b>。\",\n\t\"Site cloned\": \"網站已克隆\",\n\n\t\"You have successfully changed the web interface's language!\": \"你已經成功改變了 Web 界面的語言！\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"由於你的瀏覽器緩存，完整的翻譯可能需要花幾分鐘。\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"<b>UiServer Websocket</b> 的連線已丟失。重新連線中...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"<b>UiServer Websocket</b> 的連線已恢復。\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocket 錯誤，請重新載入頁面。\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;連線中...\",\n\t\"Site size: <b>\": \"網站大小：<b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> 比預設允許的值更大 \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"打開網站並設定大小限制到 \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" 個檔需要下載\",\n\t\" downloaded\": \" 已下載\",\n\t\" download failed\": \" 下載失敗\",\n\t\"Peers found: \": \"已找到節點：\",\n\t\"No peers found\": \"找不到節點\",\n\t\"Running out of size limit (\": \"超出大小限制\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"設定限制到 \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Cloning site...\": \"複製網站中...\",\n\t\"Site cloned\": \"網站已複製\",\n\t\"Site size limit changed to {0}MB\": \"網站大小限制已改變到 {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" 本頁面的新版本已經發佈。<br>重新載入來查看更改後的內容。\",\n\t\"This site requests permission:\": \"這個網站的請求許可權：\",\n\t\"_(Accept)\": \"授權\"\n\n}\n"
  },
  {
    "path": "src/Translate/languages/zh.json",
    "content": "{\n\t\"Congratulations, your port <b>{0}</b> is opened.<br>You are a full member of the ZeroNet network!\": \"祝贺，您的端口 (<b>{0}</b>) 已经打开。<br>您已经是 ZeroNet 网络的正式成员了！\",\n\t\"Tor mode active, every connection using Onion route.\": \"Tor 模式启用，每个连接正在使用洋葱路由。\",\n\t\"Successfully started Tor onion hidden services.\": \"成功启动 Tor 洋葱隐藏服务。\",\n\t\"Unable to start hidden services, please check your config.\": \"无法打开隐藏服务，请检查您的配置。\",\n\t\"For faster connections open <b>{0}</b> port on your router.\": \"为了更快的连接请在路由器上打开 <b>{0}</b> 端口。\",\n\t\"Your connection is restricted. Please, open <b>{0}</b> port on your router\": \"您的连接受限制。请在您的路由器上打开 <b>{0}</b> 端口\",\n\t\"or configure Tor to become a full member of the ZeroNet network.\": \"或者配置您的 Tor 来成为 ZeroNet 的正式成员。\",\n\n\t\"Select account you want to use in this site:\": \"选择您要在这个网站使用的帐户:\",\n\t\"No certificate\": \"没有证书\",\n\t\"currently selected\": \"当前选择\",\n\t\"Unique to site\": \"网站独有身份\",\n\n\t\"Content signing failed\": \"内容签名失败\",\n\t\"Content publish queued for {0:.0f} seconds.\": \"内容已加入 {0:.0f} 秒后的发布队列。\",\n\t\"Content published to {0}/{1} peers.\": \"内容已发布到 {0}/{1} 个节点。\",\n\t\"Content published to {0} peers.\": \"内容已发布到 {0} 个节点。\",\n\t\"No peers found, but your content is ready to access.\": \"找不到节点，但是您的内容已经准备好被访问。\",\n\t\"Your network connection is restricted. Please, open <b>{0}</b> port\": \"您的网络连接受限制。请在您的路由器上打开 <b>{0}</b> 端口\",\n\t\"on your router to make your site accessible for everyone.\": \"确保您的站点能被每一个人访问。\",\n\t\"Content publish failed.\": \"内容发布失败。\",\n\t\"This file still in sync, if you write it now, then the previous content may be lost.\": \"这个文件仍然在同步中，如果您现在写入它，之前的内容可能会被丢失。\",\n\t\"Write content anyway\": \"强制写入内容\",\n\t\"New certificate added:\": \"新证书：\",\n\t\"You current certificate:\": \"您当前的证书：\",\n\t\"Change it to {auth_type}/{auth_user_name}@{domain}\": \"更改至 {auth_type}/{auth_user_name}@{domain}-ra\",\n\t\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\": \"证书更改至：<b>{auth_type}/{auth_user_name}@{domain}</b>。\",\n\t\"Site cloned\": \"站点已克隆\",\n\n\t\"You have successfully changed the web interface's language!\": \"您已经成功更改了 web 界面的语言！\",\n\t\"Due to the browser's caching, the full transformation could take some minute.\": \"由于您的浏览器缓存，完整的翻译可能需要花几分钟。\",\n\n\t\"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\": \"<b>UiServer Websocket</b> 的连接已丢失。重新连接中...\",\n\t\"Connection with <b>UiServer Websocket</b> recovered.\": \"<b>UiServer Websocket</b> 的连接已恢复。\",\n\t\"UiServer Websocket error, please reload the page.\": \"UiServer Websocket 错误，请重新加载页面。\",\n\t\"&nbsp;&nbsp;&nbsp;Connecting...\": \"&nbsp;&nbsp;&nbsp;连接中...\",\n\t\"Site size: <b>\": \"站点大小：<b>\",\n\t\"MB</b> is larger than default allowed \": \"MB</b> 比默认允许的值更大 \",\n\t\"Open site and set size limit to \\\" + site_info.next_size_limit + \\\"MB\": \"打开站点并设置大小限制到 \\\" + site_info.next_size_limit + \\\"MB\",\n\t\" files needs to be downloaded\": \" 个文件需要下载\",\n\t\" downloaded\": \" 已下载\",\n\t\" download failed\": \" 下载失败\",\n\t\"Peers found: \": \"已找到节点：\",\n\t\"No peers found\": \"找不到节点\",\n\t\"Running out of size limit (\": \"超出大小限制\",\n\t\"Set limit to \\\" + site_info.next_size_limit + \\\"MB\": \"设置限制到 \\\" + site_info.next_size_limit + \\\"MB\",\n\t\"Cloning site...\": \"克隆站点中...\",\n\t\"Site cloned\": \"站点已克隆\",\n\t\"Site size limit changed to {0}MB\": \"站点大小限制已更改到 {0}MB\",\n\t\" New version of this page has just released.<br>Reload to see the modified content.\": \" 本页面的新版本已经发布。<br>重新加载来查看更改后的内容。\",\n\t\"This site requests permission:\": \"这个站点的请求权限：\",\n\t\"_(Accept)\": \"授权\"\n\n}\n"
  },
  {
    "path": "src/Ui/UiRequest.py",
    "content": "import time\nimport re\nimport os\nimport mimetypes\nimport json\nimport html\nimport urllib\nimport socket\n\nimport gevent\n\nfrom Config import config\nfrom Site import SiteManager\nfrom User import UserManager\nfrom Plugin import PluginManager\nfrom Ui.UiWebsocket import UiWebsocket\nfrom Crypt import CryptHash\nfrom util import helper\n\nstatus_texts = {\n    200: \"200 OK\",\n    206: \"206 Partial Content\",\n    400: \"400 Bad Request\",\n    403: \"403 Forbidden\",\n    404: \"404 Not Found\",\n    500: \"500 Internal Server Error\",\n}\n\ncontent_types = {\n    \"asc\": \"application/pgp-keys\",\n    \"css\": \"text/css\",\n    \"gpg\": \"application/pgp-encrypted\",\n    \"html\": \"text/html\",\n    \"js\": \"application/javascript\",\n    \"json\": \"application/json\",\n    \"oga\": \"audio/ogg\",\n    \"ogg\": \"application/ogg\",\n    \"ogv\": \"video/ogg\",\n    \"sig\": \"application/pgp-signature\",\n    \"txt\": \"text/plain\",\n    \"webmanifest\": \"application/manifest+json\",\n    \"wasm\": \"application/wasm\",\n    \"webp\": \"image/webp\"\n}\n\n\nclass SecurityError(Exception):\n    pass\n\n\n@PluginManager.acceptPlugins\nclass UiRequest(object):\n\n    def __init__(self, server, get, env, start_response):\n        if server:\n            self.server = server\n            self.log = server.log\n        self.get = get  # Get parameters\n        self.env = env  # Enviroment settings\n        # ['CONTENT_LENGTH', 'CONTENT_TYPE', 'GATEWAY_INTERFACE', 'HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE',\n        #  'HTTP_COOKIE', 'HTTP_CACHE_CONTROL', 'HTTP_HOST', 'HTTP_HTTPS', 'HTTP_ORIGIN', 'HTTP_PROXY_CONNECTION', 'HTTP_REFERER',\n        #  'HTTP_USER_AGENT', 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_PORT', 'REQUEST_METHOD', 'SCRIPT_NAME',\n        #  'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL', 'SERVER_SOFTWARE', 'werkzeug.request', 'wsgi.errors',\n        #  'wsgi.input', 'wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once', 'wsgi.url_scheme', 'wsgi.version']\n\n        self.start_response = start_response  # Start response function\n        self.user = None\n        self.script_nonce = None  # Nonce for script tags in wrapper html\n\n    def learnHost(self, host):\n        self.server.allowed_hosts.add(host)\n        self.server.log.info(\"Added %s as allowed host\" % host)\n\n    def isHostAllowed(self, host):\n        if host in self.server.allowed_hosts:\n            return True\n\n        # Allow any IP address as they are not affected by DNS rebinding\n        # attacks\n        if helper.isIp(host):\n            self.learnHost(host)\n            return True\n\n        if \":\" in host and helper.isIp(host.rsplit(\":\", 1)[0]):  # Test without port\n            self.learnHost(host)\n            return True\n\n        if self.isProxyRequest():  # Support for chrome extension proxy\n            if self.isDomain(host):\n                return True\n            else:\n                return False\n\n        return False\n\n    def isDomain(self, address):\n        return self.server.site_manager.isDomainCached(address)\n\n    def resolveDomain(self, domain):\n        return self.server.site_manager.resolveDomainCached(domain)\n\n    # Call the request handler function base on path\n    def route(self, path):\n        # Restict Ui access by ip\n        if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict:\n            return self.error403(details=False)\n\n        # Check if host allowed to do request\n        if not self.isHostAllowed(self.env.get(\"HTTP_HOST\")):\n            ret_error = next(self.error403(\"Invalid host: %s\" % self.env.get(\"HTTP_HOST\"), details=False))\n\n            http_get = self.env[\"PATH_INFO\"]\n            if self.env[\"QUERY_STRING\"]:\n                http_get += \"?{0}\".format(self.env[\"QUERY_STRING\"])\n            self_host = self.env[\"HTTP_HOST\"].split(\":\")[0]\n            self_ip = self.env[\"HTTP_HOST\"].replace(self_host, socket.gethostbyname(self_host))\n            link = \"http://{0}{1}\".format(self_ip, http_get)\n            ret_body = \"\"\"\n                <h4>Start the client with <code>--ui_host \"{host}\"</code> argument</h4>\n                <h4>or access via ip: <a href=\"{link}\">{link}</a></h4>\n            \"\"\".format(\n                host=html.escape(self.env[\"HTTP_HOST\"]),\n                link=html.escape(link)\n            ).encode(\"utf8\")\n            return iter([ret_error, ret_body])\n\n        # Prepend .bit host for transparent proxy\n        if self.isDomain(self.env.get(\"HTTP_HOST\")):\n            path = re.sub(\"^/\", \"/\" + self.env.get(\"HTTP_HOST\") + \"/\", path)\n        path = re.sub(\"^http://zero[/]+\", \"/\", path)  # Remove begining http://zero/ for chrome extension\n        path = re.sub(\"^http://\", \"/\", path)  # Remove begining http for chrome extension .bit access\n\n        # Sanitize request url\n        path = path.replace(\"\\\\\", \"/\")\n        if \"../\" in path or \"./\" in path:\n            return self.error403(\"Invalid path: %s\" % path)\n\n        if self.env[\"REQUEST_METHOD\"] == \"OPTIONS\":\n            if \"/\" not in path.strip(\"/\"):\n                content_type = self.getContentType(\"index.html\")\n            else:\n                content_type = self.getContentType(path)\n\n            extra_headers = {\"Access-Control-Allow-Origin\": \"null\"}\n\n            self.sendHeader(content_type=content_type, extra_headers=extra_headers, noscript=True)\n            return \"\"\n\n        if path == \"/\":\n            return self.actionIndex()\n        elif path in (\"/favicon.ico\", \"/apple-touch-icon.png\"):\n            return self.actionFile(\"src/Ui/media/img/%s\" % path)\n        # Internal functions\n        elif \"/ZeroNet-Internal/\" in path:\n            path = re.sub(\".*?/ZeroNet-Internal/\", \"/\", path)\n            func = getattr(self, \"action\" + path.strip(\"/\"), None)  # Check if we have action+request_path function\n            if func:\n                return func()\n            else:\n                return self.error404(path)\n        # Media\n        elif path.startswith(\"/uimedia/\"):\n            return self.actionUiMedia(path)\n        elif \"/uimedia/\" in path:\n            # uimedia within site dir (for chrome extension)\n            path = re.sub(\".*?/uimedia/\", \"/uimedia/\", path)\n            return self.actionUiMedia(path)\n        # Websocket\n        elif path == \"/Websocket\":\n            return self.actionWebsocket()\n        # Debug\n        elif path == \"/Debug\" and config.debug:\n            return self.actionDebug()\n        elif path == \"/Console\" and config.debug:\n            return self.actionConsole()\n        # Wrapper-less static files\n        elif path.startswith(\"/raw/\"):\n            return self.actionSiteMedia(path.replace(\"/raw\", \"/media\", 1), header_noscript=True)\n\n        elif path.startswith(\"/add/\"):\n            return self.actionSiteAdd()\n        # Site media wrapper\n        else:\n            if self.get.get(\"wrapper_nonce\"):\n                if self.get[\"wrapper_nonce\"] in self.server.wrapper_nonces:\n                    self.server.wrapper_nonces.remove(self.get[\"wrapper_nonce\"])\n                    return self.actionSiteMedia(\"/media\" + path)  # Only serve html files with frame\n                else:\n                    self.server.log.warning(\"Invalid wrapper nonce: %s\" % self.get[\"wrapper_nonce\"])\n                    body = self.actionWrapper(path)\n            else:\n                body = self.actionWrapper(path)\n            if body:\n                return body\n            else:\n                func = getattr(self, \"action\" + path.strip(\"/\"), None)  # Check if we have action+request_path function\n                if func:\n                    return func()\n                else:\n                    ret = self.error404(path)\n                    return ret\n\n    # The request is proxied by chrome extension or a transparent proxy\n    def isProxyRequest(self):\n        return self.env[\"PATH_INFO\"].startswith(\"http://\") or (self.server.allow_trans_proxy and self.isDomain(self.env.get(\"HTTP_HOST\")))\n\n    def isWebSocketRequest(self):\n        return self.env.get(\"HTTP_UPGRADE\") == \"websocket\"\n\n    def isAjaxRequest(self):\n        return self.env.get(\"HTTP_X_REQUESTED_WITH\") == \"XMLHttpRequest\"\n\n    # Get mime by filename\n    def getContentType(self, file_name):\n        file_name = file_name.lower()\n        ext = file_name.rsplit(\".\", 1)[-1]\n\n        if ext in content_types:\n            content_type = content_types[ext]\n        elif ext in (\"ttf\", \"woff\", \"otf\", \"woff2\", \"eot\", \"sfnt\", \"collection\"):\n            content_type = \"font/%s\" % ext\n        else:\n            content_type = mimetypes.guess_type(file_name)[0]\n\n        if not content_type:\n            content_type = \"application/octet-stream\"\n\n        return content_type.lower()\n\n    # Return: <dict> Posted variables\n    def getPosted(self):\n        if self.env['REQUEST_METHOD'] == \"POST\":\n            return dict(urllib.parse.parse_qsl(\n                self.env['wsgi.input'].readline().decode()\n            ))\n        else:\n            return {}\n\n    # Return: <dict> Cookies based on self.env\n    def getCookies(self):\n        raw_cookies = self.env.get('HTTP_COOKIE')\n        if raw_cookies:\n            cookies = urllib.parse.parse_qsl(raw_cookies)\n            return {key.strip(): val for key, val in cookies}\n        else:\n            return {}\n\n    def getCurrentUser(self):\n        if self.user:\n            return self.user  # Cache\n        self.user = UserManager.user_manager.get()  # Get user\n        if not self.user:\n            self.user = UserManager.user_manager.create()\n        return self.user\n\n    def getRequestUrl(self):\n        if self.isProxyRequest():\n            if self.env[\"PATH_INFO\"].startswith(\"http://zero/\"):\n                return self.env[\"PATH_INFO\"]\n            else:  # Add http://zero to direct domain access\n                return self.env[\"PATH_INFO\"].replace(\"http://\", \"http://zero/\", 1)\n        else:\n            return self.env[\"wsgi.url_scheme\"] + \"://\" + self.env[\"HTTP_HOST\"] + self.env[\"PATH_INFO\"]\n\n    def getReferer(self):\n        referer = self.env.get(\"HTTP_REFERER\")\n        if referer and self.isProxyRequest() and not referer.startswith(\"http://zero/\"):\n            return referer.replace(\"http://\", \"http://zero/\", 1)\n        else:\n            return referer\n\n    def isScriptNonceSupported(self):\n        user_agent = self.env.get(\"HTTP_USER_AGENT\")\n        if \"Edge/\" in user_agent:\n            is_script_nonce_supported = False\n        elif \"Safari/\" in user_agent and \"Chrome/\" not in user_agent:\n            is_script_nonce_supported = False\n        else:\n            is_script_nonce_supported = True\n        return is_script_nonce_supported\n\n    # Send response headers\n    def sendHeader(self, status=200, content_type=\"text/html\", noscript=False, allow_ajax=False, script_nonce=None, extra_headers=[]):\n        headers = {}\n        headers[\"Version\"] = \"HTTP/1.1\"\n        headers[\"Connection\"] = \"Keep-Alive\"\n        headers[\"Keep-Alive\"] = \"max=25, timeout=30\"\n        headers[\"X-Frame-Options\"] = \"SAMEORIGIN\"\n        if content_type != \"text/html\" and self.env.get(\"HTTP_REFERER\") and self.isSameOrigin(self.getReferer(), self.getRequestUrl()):\n            headers[\"Access-Control-Allow-Origin\"] = \"*\"  # Allow load font files from css\n\n        if noscript:\n            headers[\"Content-Security-Policy\"] = \"default-src 'none'; sandbox allow-top-navigation allow-forms; img-src *; font-src * data:; media-src *; style-src * 'unsafe-inline';\"\n        elif script_nonce and self.isScriptNonceSupported():\n            headers[\"Content-Security-Policy\"] = \"default-src 'none'; script-src 'nonce-{0}'; img-src 'self' blob: data:; style-src 'self' blob: 'unsafe-inline'; connect-src *; frame-src 'self' blob:\".format(script_nonce)\n\n        if allow_ajax:\n            headers[\"Access-Control-Allow-Origin\"] = \"null\"\n\n        if self.env[\"REQUEST_METHOD\"] == \"OPTIONS\":\n            # Allow json access\n            headers[\"Access-Control-Allow-Headers\"] = \"Origin, X-Requested-With, Content-Type, Accept, Cookie, Range\"\n            headers[\"Access-Control-Allow-Credentials\"] = \"true\"\n\n        # Download instead of display file types that can be dangerous\n        if re.findall(\"/svg|/xml|/x-shockwave-flash|/pdf\", content_type):\n            headers[\"Content-Disposition\"] = \"attachment\"\n\n        cacheable_type = (\n            self.env[\"REQUEST_METHOD\"] == \"OPTIONS\" or\n            content_type.split(\"/\", 1)[0] in (\"image\", \"video\", \"font\") or\n            content_type in (\"application/javascript\", \"text/css\")\n        )\n\n        if content_type in (\"text/plain\", \"text/html\", \"text/css\", \"application/javascript\", \"application/json\", \"application/manifest+json\"):\n            content_type += \"; charset=utf-8\"\n\n        if status in (200, 206) and cacheable_type:  # Cache Css, Js, Image files for 10min\n            headers[\"Cache-Control\"] = \"public, max-age=600\"  # Cache 10 min\n        else:\n            headers[\"Cache-Control\"] = \"no-cache, no-store, private, must-revalidate, max-age=0\"  # No caching at all\n        headers[\"Content-Type\"] = content_type\n        headers.update(extra_headers)\n        return self.start_response(status_texts[status], list(headers.items()))\n\n    # Renders a template\n    def render(self, template_path, *args, **kwargs):\n        template = open(template_path, encoding=\"utf8\").read()\n\n        def renderReplacer(m):\n            if m.group(1) in kwargs:\n                return \"%s\" % kwargs.get(m.group(1), \"\")\n            else:\n                return m.group(0)\n\n        template_rendered = re.sub(\"{(.*?)}\", renderReplacer, template)\n\n        return template_rendered.encode(\"utf8\")\n\n    def isWrapperNecessary(self, path):\n        match = re.match(r\"/(?P<address>[A-Za-z0-9\\._-]+)(?P<inner_path>/.*|$)\", path)\n\n        if not match:\n            return True\n\n        inner_path = match.group(\"inner_path\").lstrip(\"/\")\n        if not inner_path or path.endswith(\"/\"):  # It's a directory\n            content_type = self.getContentType(\"index.html\")\n        else:  # It's a file\n            content_type = self.getContentType(inner_path)\n\n        is_html_file = \"html\" in content_type or \"xhtml\" in content_type\n\n        return is_html_file\n\n    @helper.encodeResponse\n    def formatRedirect(self, url):\n        return \"\"\"\n            <html>\n            <body>\n            Redirecting to <a href=\"{0}\" target=\"_top\">{0}</a>\n            <script>\n            window.top.location = \"{0}\"\n            </script>\n            </body>\n            </html>\n        \"\"\".format(html.escape(url))\n\n    # - Actions -\n\n    # Redirect to an url\n    def actionRedirect(self, url):\n        self.start_response('301 Redirect', [('Location', str(url))])\n        yield self.formatRedirect(url)\n\n    def actionIndex(self):\n        return self.actionRedirect(\"/\" + config.homepage + \"/\")\n\n    # Render a file from media with iframe site wrapper\n    def actionWrapper(self, path, extra_headers=None):\n        if not extra_headers:\n            extra_headers = {}\n        script_nonce = self.getScriptNonce()\n\n        match = re.match(r\"/(?P<address>[A-Za-z0-9\\._-]+)(?P<inner_path>/.*|$)\", path)\n        just_added = False\n        if match:\n            address = match.group(\"address\")\n            inner_path = match.group(\"inner_path\").lstrip(\"/\")\n\n            if not self.isWrapperNecessary(path):\n                return self.actionSiteMedia(\"/media\" + path)  # Serve non-html files without wrapper\n\n            if self.isAjaxRequest():\n                return self.error403(\"Ajax request not allowed to load wrapper\")  # No ajax allowed on wrapper\n\n            if self.isWebSocketRequest():\n                return self.error403(\"WebSocket request not allowed to load wrapper\")  # No websocket\n\n            if \"text/html\" not in self.env.get(\"HTTP_ACCEPT\", \"\"):\n                return self.error403(\"Invalid Accept header to load wrapper: %s\" % self.env.get(\"HTTP_ACCEPT\", \"\"))\n            if \"prefetch\" in self.env.get(\"HTTP_X_MOZ\", \"\") or \"prefetch\" in self.env.get(\"HTTP_PURPOSE\", \"\"):\n                return self.error403(\"Prefetch not allowed to load wrapper\")\n\n            site = SiteManager.site_manager.get(address)\n\n            if site and site.content_manager.contents.get(\"content.json\"):\n                title = site.content_manager.contents[\"content.json\"][\"title\"]\n            else:\n                title = \"Loading %s...\" % address\n                site = SiteManager.site_manager.get(address)\n                if site:  # Already added, but not downloaded\n                    if time.time() - site.announcer.time_last_announce > 5:\n                        site.log.debug(\"Reannouncing site...\")\n                        gevent.spawn(site.update, announce=True)\n                else:  # If not added yet\n                    site = SiteManager.site_manager.need(address)\n                    just_added = True\n\n                if not site:\n                    return False\n\n            self.sendHeader(extra_headers=extra_headers, script_nonce=script_nonce)\n\n            min_last_announce = (time.time() - site.announcer.time_last_announce) / 60\n            if min_last_announce > 60 and site.isServing() and not just_added:\n                site.log.debug(\"Site requested, but not announced recently (last %.0fmin ago). Updating...\" % min_last_announce)\n                gevent.spawn(site.update, announce=True)\n\n            return iter([self.renderWrapper(site, path, inner_path, title, extra_headers, script_nonce=script_nonce)])\n            # Make response be sent at once (see https://github.com/HelloZeroNet/ZeroNet/issues/1092)\n\n        else:  # Bad url\n            return False\n\n    def getSiteUrl(self, address):\n        if self.isProxyRequest():\n            return \"http://zero/\" + address\n        else:\n            return \"/\" + address\n\n    def getWsServerUrl(self):\n        if self.isProxyRequest():\n            if self.env[\"REMOTE_ADDR\"] == \"127.0.0.1\":  # Local client, the server address also should be 127.0.0.1\n                server_url = \"http://127.0.0.1:%s\" % self.env[\"SERVER_PORT\"]\n            else:  # Remote client, use SERVER_NAME as server's real address\n                server_url = \"http://%s:%s\" % (self.env[\"SERVER_NAME\"], self.env[\"SERVER_PORT\"])\n        else:\n            server_url = \"\"\n        return server_url\n\n    def processQueryString(self, site, query_string):\n        match = re.search(\"zeronet_peers=(.*?)(&|$)\", query_string)\n        if match:\n            query_string = query_string.replace(match.group(0), \"\")\n            num_added = 0\n            for peer in match.group(1).split(\",\"):\n                if not re.match(\".*?:[0-9]+$\", peer):\n                    continue\n                ip, port = peer.rsplit(\":\", 1)\n                if site.addPeer(ip, int(port), source=\"query_string\"):\n                    num_added += 1\n            site.log.debug(\"%s peers added by query string\" % num_added)\n\n        return query_string\n\n    def renderWrapper(self, site, path, inner_path, title, extra_headers, show_loadingscreen=None, script_nonce=None):\n        file_inner_path = inner_path\n        if not file_inner_path:\n            file_inner_path = \"index.html\"  # If inner path defaults to index.html\n\n        if file_inner_path.endswith(\"/\"):\n            file_inner_path = file_inner_path + \"index.html\"\n\n        address = re.sub(\"/.*\", \"\", path.lstrip(\"/\"))\n        if self.isProxyRequest() and (not path or \"/\" in path[1:]):\n            if self.env[\"HTTP_HOST\"] == \"zero\":\n                root_url = \"/\" + address + \"/\"\n                file_url = \"/\" + address + \"/\" + inner_path\n            else:\n                file_url = \"/\" + inner_path\n                root_url = \"/\"\n\n        else:\n            file_url = \"/\" + address + \"/\" + inner_path\n            root_url = \"/\" + address + \"/\"\n\n        if self.isProxyRequest():\n            self.server.allowed_ws_origins.add(self.env[\"HTTP_HOST\"])\n\n        # Wrapper variable inits\n        body_style = \"\"\n        meta_tags = \"\"\n        postmessage_nonce_security = \"false\"\n\n        wrapper_nonce = self.getWrapperNonce()\n        inner_query_string = self.processQueryString(site, self.env.get(\"QUERY_STRING\", \"\"))\n\n        if \"?\" in inner_path:\n            sep = \"&\"\n        else:\n            sep = \"?\"\n\n        if inner_query_string:\n            inner_query_string = \"%s%s&wrapper_nonce=%s\" % (sep, inner_query_string, wrapper_nonce)\n        else:\n            inner_query_string = \"%swrapper_nonce=%s\" % (sep, wrapper_nonce)\n\n        if self.isProxyRequest():  # Its a remote proxy request\n            homepage = \"http://zero/\" + config.homepage\n        else:  # Use relative path\n            homepage = \"/\" + config.homepage\n\n        server_url = self.getWsServerUrl()  # Real server url for WS connections\n\n        user = self.getCurrentUser()\n        if user:\n            theme = user.settings.get(\"theme\", \"light\")\n        else:\n            theme = \"light\"\n\n        themeclass = \"theme-%-6s\" % re.sub(\"[^a-z]\", \"\", theme)\n\n        if site.content_manager.contents.get(\"content.json\"):  # Got content.json\n            content = site.content_manager.contents[\"content.json\"]\n            if content.get(\"background-color\"):\n                background_color = content.get(\"background-color-%s\" % theme, content[\"background-color\"])\n                body_style += \"background-color: %s;\" % html.escape(background_color)\n            if content.get(\"viewport\"):\n                meta_tags += '<meta name=\"viewport\" id=\"viewport\" content=\"%s\">' % html.escape(content[\"viewport\"])\n            if content.get(\"favicon\"):\n                meta_tags += '<link rel=\"icon\" href=\"%s%s\">' % (root_url, html.escape(content[\"favicon\"]))\n            if content.get(\"postmessage_nonce_security\"):\n                postmessage_nonce_security = \"true\"\n\n        sandbox_permissions = \"\"\n\n        if \"NOSANDBOX\" in site.settings[\"permissions\"]:\n            sandbox_permissions += \" allow-same-origin\"\n\n        if show_loadingscreen is None:\n            show_loadingscreen = not site.storage.isFile(file_inner_path)\n\n        return self.render(\n            \"src/Ui/template/wrapper.html\",\n            server_url=server_url,\n            inner_path=inner_path,\n            file_url=re.escape(file_url),\n            file_inner_path=re.escape(file_inner_path),\n            address=site.address,\n            title=html.escape(title),\n            body_style=body_style,\n            meta_tags=meta_tags,\n            query_string=re.escape(inner_query_string),\n            wrapper_key=site.settings[\"wrapper_key\"],\n            ajax_key=site.settings[\"ajax_key\"],\n            wrapper_nonce=wrapper_nonce,\n            postmessage_nonce_security=postmessage_nonce_security,\n            permissions=json.dumps(site.settings[\"permissions\"]),\n            show_loadingscreen=json.dumps(show_loadingscreen),\n            sandbox_permissions=sandbox_permissions,\n            rev=config.rev,\n            lang=config.language,\n            homepage=homepage,\n            themeclass=themeclass,\n            script_nonce=script_nonce\n        )\n\n    # Create a new wrapper nonce that allows to get one html file without the wrapper\n    def getWrapperNonce(self):\n        wrapper_nonce = CryptHash.random()\n        self.server.wrapper_nonces.append(wrapper_nonce)\n        return wrapper_nonce\n\n    def getScriptNonce(self):\n        if not self.script_nonce:\n            self.script_nonce = CryptHash.random(encoding=\"base64\")\n\n        return self.script_nonce\n\n    # Create a new wrapper nonce that allows to get one site\n    def getAddNonce(self):\n        add_nonce = CryptHash.random()\n        self.server.add_nonces.append(add_nonce)\n        return add_nonce\n\n    def isSameOrigin(self, url_a, url_b):\n        if not url_a or not url_b:\n            return False\n\n        url_a = url_a.replace(\"/raw/\", \"/\")\n        url_b = url_b.replace(\"/raw/\", \"/\")\n\n        origin_pattern = \"http[s]{0,1}://(.*?/.*?/).*\"\n        is_origin_full = re.match(origin_pattern, url_a)\n        if not is_origin_full:  # Origin looks trimmed to host, require only same host\n            origin_pattern = \"http[s]{0,1}://(.*?/).*\"\n\n        origin_a = re.sub(origin_pattern, \"\\\\1\", url_a)\n        origin_b = re.sub(origin_pattern, \"\\\\1\", url_b)\n\n        return origin_a == origin_b\n\n    # Return {address: 1Site.., inner_path: /data/users.json} from url path\n    def parsePath(self, path):\n        path = path.replace(\"\\\\\", \"/\")\n        path = path.replace(\"/index.html/\", \"/\")  # Base Backward compatibility fix\n        if path.endswith(\"/\"):\n            path = path + \"index.html\"\n\n        if \"../\" in path or \"./\" in path:\n            raise SecurityError(\"Invalid path\")\n\n        match = re.match(r\"/media/(?P<address>[A-Za-z0-9]+[A-Za-z0-9\\._-]+)(?P<inner_path>/.*|$)\", path)\n        if match:\n            path_parts = match.groupdict()\n            if self.isDomain(path_parts[\"address\"]):\n                path_parts[\"address\"] = self.resolveDomain(path_parts[\"address\"])\n            path_parts[\"request_address\"] = path_parts[\"address\"]  # Original request address (for Merger sites)\n            path_parts[\"inner_path\"] = path_parts[\"inner_path\"].lstrip(\"/\")\n            if not path_parts[\"inner_path\"]:\n                path_parts[\"inner_path\"] = \"index.html\"\n            return path_parts\n        else:\n            return None\n\n    # Serve a media for site\n    def actionSiteMedia(self, path, header_length=True, header_noscript=False):\n        try:\n            path_parts = self.parsePath(path)\n        except SecurityError as err:\n            return self.error403(err)\n\n        if not path_parts:\n            return self.error404(path)\n\n        address = path_parts[\"address\"]\n\n        file_path = \"%s/%s/%s\" % (config.data_dir, address, path_parts[\"inner_path\"])\n\n        if (config.debug or config.merge_media) and file_path.split(\"/\")[-1].startswith(\"all.\"):\n            # If debugging merge *.css to all.css and *.js to all.js\n            site = self.server.sites.get(address)\n            if site and site.settings[\"own\"]:\n                from Debug import DebugMedia\n                DebugMedia.merge(file_path)\n\n        if not address or address == \".\":\n            return self.error403(path_parts[\"inner_path\"])\n\n        header_allow_ajax = False\n        if self.get.get(\"ajax_key\"):\n            site = SiteManager.site_manager.get(path_parts[\"request_address\"])\n            if self.get[\"ajax_key\"] == site.settings[\"ajax_key\"]:\n                header_allow_ajax = True\n            else:\n                return self.error403(\"Invalid ajax_key\")\n\n        file_size = helper.getFilesize(file_path)\n\n        if file_size is not None:\n            return self.actionFile(file_path, header_length=header_length, header_noscript=header_noscript, header_allow_ajax=header_allow_ajax, file_size=file_size, path_parts=path_parts)\n\n        elif os.path.isdir(file_path):  # If this is actually a folder, add \"/\" and redirect\n            if path_parts[\"inner_path\"]:\n                return self.actionRedirect(\"./%s/\" % path_parts[\"inner_path\"].split(\"/\")[-1])\n            else:\n                return self.actionRedirect(\"./%s/\" % path_parts[\"address\"])\n\n        else:  # File not exists, try to download\n            if address not in SiteManager.site_manager.sites:  # Only in case if site already started downloading\n                return self.actionSiteAddPrompt(path)\n\n            site = SiteManager.site_manager.need(address)\n\n            if path_parts[\"inner_path\"].endswith(\"favicon.ico\"):  # Default favicon for all sites\n                return self.actionFile(\"src/Ui/media/img/favicon.ico\")\n\n            result = site.needFile(path_parts[\"inner_path\"], priority=15)  # Wait until file downloads\n            if result:\n                file_size = helper.getFilesize(file_path)\n                return self.actionFile(file_path, header_length=header_length, header_noscript=header_noscript, header_allow_ajax=header_allow_ajax, file_size=file_size, path_parts=path_parts)\n            else:\n                self.log.debug(\"File not found: %s\" % path_parts[\"inner_path\"])\n                return self.error404(path)\n\n    # Serve a media for ui\n    def actionUiMedia(self, path):\n        match = re.match(\"/uimedia/(?P<inner_path>.*)\", path)\n        if match:  # Looks like a valid path\n            file_path = \"src/Ui/media/%s\" % match.group(\"inner_path\")\n            allowed_dir = os.path.abspath(\"src/Ui/media\")  # Only files within data/sitehash allowed\n            if \"../\" in file_path or not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir):\n                # File not in allowed path\n                return self.error403()\n            else:\n                if (config.debug or config.merge_media) and match.group(\"inner_path\").startswith(\"all.\"):\n                    # If debugging merge *.css to all.css and *.js to all.js\n                    from Debug import DebugMedia\n                    DebugMedia.merge(file_path)\n                return self.actionFile(file_path, header_length=False)  # Dont's send site to allow plugins append content\n\n        else:  # Bad url\n            return self.error400()\n\n    def actionSiteAdd(self):\n        post_data = self.env[\"wsgi.input\"].read().decode()\n        post = dict(urllib.parse.parse_qsl(post_data))\n        if post[\"add_nonce\"] not in self.server.add_nonces:\n            return self.error403(\"Add nonce error.\")\n        self.server.add_nonces.remove(post[\"add_nonce\"])\n        SiteManager.site_manager.need(post[\"address\"])\n        return self.actionRedirect(post[\"url\"])\n\n    @helper.encodeResponse\n    def actionSiteAddPrompt(self, path):\n        path_parts = self.parsePath(path)\n        if not path_parts or not self.server.site_manager.isAddress(path_parts[\"address\"]):\n            return self.error404(path)\n\n        self.sendHeader(200, \"text/html\", noscript=True)\n        template = open(\"src/Ui/template/site_add.html\").read()\n        template = template.replace(\"{url}\", html.escape(self.env[\"PATH_INFO\"]))\n        template = template.replace(\"{address}\", path_parts[\"address\"])\n        template = template.replace(\"{add_nonce}\", self.getAddNonce())\n        return template\n\n    def replaceHtmlVariables(self, block, path_parts):\n        user = self.getCurrentUser()\n        themeclass = \"theme-%-6s\" % re.sub(\"[^a-z]\", \"\", user.settings.get(\"theme\", \"light\"))\n        block = block.replace(b\"{themeclass}\", themeclass.encode(\"utf8\"))\n\n        if path_parts:\n            site = self.server.sites.get(path_parts.get(\"address\"))\n            if site.settings[\"own\"]:\n                modified = int(time.time())\n            else:\n                modified = int(site.content_manager.contents[\"content.json\"][\"modified\"])\n            block = block.replace(b\"{site_modified}\", str(modified).encode(\"utf8\"))\n\n        return block\n\n    # Stream a file to client\n    def actionFile(self, file_path, block_size=64 * 1024, send_header=True, header_length=True, header_noscript=False, header_allow_ajax=False, extra_headers={}, file_size=None, file_obj=None, path_parts=None):\n        file_name = os.path.basename(file_path)\n\n        if file_size is None:\n            file_size = helper.getFilesize(file_path)\n\n        if file_size is not None:\n            # Try to figure out content type by extension\n            content_type = self.getContentType(file_name)\n\n            range = self.env.get(\"HTTP_RANGE\")\n            range_start = None\n\n            is_html_file = file_name.endswith(\".html\")\n            if is_html_file:\n                header_length = False\n\n            if send_header:\n                extra_headers = extra_headers.copy()\n                content_encoding = self.get.get(\"zeronet_content_encoding\", \"\")\n                if all(part.strip() in (\"gzip\", \"compress\", \"deflate\", \"identity\", \"br\") for part in content_encoding.split(\",\")):\n                    extra_headers[\"Content-Encoding\"] = content_encoding\n                extra_headers[\"Accept-Ranges\"] = \"bytes\"\n                if header_length:\n                    extra_headers[\"Content-Length\"] = str(file_size)\n                if range:\n                    range_start = int(re.match(\".*?([0-9]+)\", range).group(1))\n                    if re.match(\".*?-([0-9]+)\", range):\n                        range_end = int(re.match(\".*?-([0-9]+)\", range).group(1)) + 1\n                    else:\n                        range_end = file_size\n                    extra_headers[\"Content-Length\"] = str(range_end - range_start)\n                    extra_headers[\"Content-Range\"] = \"bytes %s-%s/%s\" % (range_start, range_end - 1, file_size)\n                if range:\n                    status = 206\n                else:\n                    status = 200\n                self.sendHeader(status, content_type=content_type, noscript=header_noscript, allow_ajax=header_allow_ajax, extra_headers=extra_headers)\n            if self.env[\"REQUEST_METHOD\"] != \"OPTIONS\":\n                if not file_obj:\n                    file_obj = open(file_path, \"rb\")\n\n                if range_start:\n                    file_obj.seek(range_start)\n                while 1:\n                    try:\n                        block = file_obj.read(block_size)\n                        if is_html_file:\n                            block = self.replaceHtmlVariables(block, path_parts)\n                        if block:\n                            yield block\n                        else:\n                            raise StopIteration\n                    except StopIteration:\n                        file_obj.close()\n                        break\n        else:  # File not exists\n            for part in self.error404(str(file_path)):\n                yield part\n\n    # On websocket connection\n    def actionWebsocket(self):\n        ws = self.env.get(\"wsgi.websocket\")\n\n        if ws:\n            # Allow only same-origin websocket requests\n            origin = self.env.get(\"HTTP_ORIGIN\")\n            host = self.env.get(\"HTTP_HOST\")\n            # Allow only same-origin websocket requests\n            if origin:\n                origin_host = origin.split(\"://\", 1)[-1]\n                if origin_host != host and origin_host not in self.server.allowed_ws_origins:\n                    error_message = \"Invalid origin: %s (host: %s, allowed: %s)\" % (origin, host, self.server.allowed_ws_origins)\n                    ws.send(json.dumps({\"error\": error_message}))\n                    return self.error403(error_message)\n\n            # Find site by wrapper_key\n            wrapper_key = self.get[\"wrapper_key\"]\n            site = None\n            for site_check in list(self.server.sites.values()):\n                if site_check.settings[\"wrapper_key\"] == wrapper_key:\n                    site = site_check\n\n            if site:  # Correct wrapper key\n                try:\n                    user = self.getCurrentUser()\n                except Exception as err:\n                    ws.send(json.dumps({\"error\": \"Error in data/user.json: %s\" % err}))\n                    return self.error500(\"Error in data/user.json: %s\" % err)\n                if not user:\n                    ws.send(json.dumps({\"error\": \"No user found\"}))\n                    return self.error403(\"No user found\")\n                ui_websocket = UiWebsocket(ws, site, self.server, user, self)\n                site.websockets.append(ui_websocket)  # Add to site websockets to allow notify on events\n                self.server.websockets.append(ui_websocket)\n                ui_websocket.start()\n                self.server.websockets.remove(ui_websocket)\n                for site_check in list(self.server.sites.values()):\n                    # Remove websocket from every site (admin sites allowed to join other sites event channels)\n                    if ui_websocket in site_check.websockets:\n                        site_check.websockets.remove(ui_websocket)\n                return [b\"Bye.\"]\n            else:  # No site found by wrapper key\n                ws.send(json.dumps({\"error\": \"Wrapper key not found: %s\" % wrapper_key}))\n                return self.error403(\"Wrapper key not found: %s\" % wrapper_key)\n        else:\n            self.start_response(\"400 Bad Request\", [])\n            return [b\"Not a websocket request!\"]\n\n    # Debug last error\n    def actionDebug(self):\n        # Raise last error from DebugHook\n        import main\n        last_error = main.DebugHook.last_error\n        if last_error:\n            raise last_error[0](last_error[1]).with_traceback(last_error[2])\n        else:\n            self.sendHeader()\n            return [b\"No error! :)\"]\n\n    # Just raise an error to get console\n    def actionConsole(self):\n        import sys\n        sites = self.server.sites\n        main = sys.modules[\"main\"]\n\n        def bench(code, times=100, init=None):\n            sites = self.server.sites\n            main = sys.modules[\"main\"]\n            s = time.time()\n            if init:\n                eval(compile(init, '<string>', 'exec'), globals(), locals())\n            for _ in range(times):\n                back = eval(code, globals(), locals())\n            return [\"%s run: %.3fs\" % (times, time.time() - s), back]\n        raise Exception(\"Here is your console\")\n\n    # - Tests -\n\n    def actionTestStream(self):\n        self.sendHeader()\n        yield \" \" * 1080  # Overflow browser's buffer\n        yield \"He\"\n        time.sleep(1)\n        yield \"llo!\"\n        # yield \"Running websockets: %s\" % len(self.server.websockets)\n        # self.server.sendMessage(\"Hello!\")\n\n    # - Errors -\n\n    # Send bad request error\n    def error400(self, message=\"\"):\n        self.sendHeader(400, noscript=True)\n        self.log.error(\"Error 400: %s\" % message)\n        return self.formatError(\"Bad Request\", message)\n\n    # You are not allowed to access this\n    def error403(self, message=\"\", details=True):\n        self.sendHeader(403, noscript=True)\n        self.log.warning(\"Error 403: %s\" % message)\n        return self.formatError(\"Forbidden\", message, details=details)\n\n    # Send file not found error\n    def error404(self, path=\"\"):\n        self.sendHeader(404, noscript=True)\n        return self.formatError(\"Not Found\", path, details=False)\n\n    # Internal server error\n    def error500(self, message=\":(\"):\n        self.sendHeader(500, noscript=True)\n        self.log.error(\"Error 500: %s\" % message)\n        return self.formatError(\"Server error\", message)\n\n    @helper.encodeResponse\n    def formatError(self, title, message, details=True):\n        import sys\n        import gevent\n\n        if details and config.debug:\n            details = {key: val for key, val in list(self.env.items()) if hasattr(val, \"endswith\") and \"COOKIE\" not in key}\n            details[\"version_zeronet\"] = \"%s r%s\" % (config.version, config.rev)\n            details[\"version_python\"] = sys.version\n            details[\"version_gevent\"] = gevent.__version__\n            details[\"plugins\"] = PluginManager.plugin_manager.plugin_names\n            arguments = {key: val for key, val in vars(config.arguments).items() if \"password\" not in key}\n            details[\"arguments\"] = arguments\n            return \"\"\"\n                <style>\n                * { font-family: Consolas, Monospace; color: #333 }\n                pre { padding: 10px; background-color: #EEE }\n                </style>\n                <h1>%s</h1>\n                <h2>%s</h3>\n                <h3>Please <a href=\"https://github.com/HelloZeroNet/ZeroNet/issues\" target=\"_top\">report it</a> if you think this an error.</h3>\n                <h4>Details:</h4>\n                <pre>%s</pre>\n            \"\"\" % (title, html.escape(message), html.escape(json.dumps(details, indent=4, sort_keys=True)))\n        else:\n            return \"\"\"\n                <style>\n                * { font-family: Consolas, Monospace; color: #333; }\n                code { font-family: Consolas, Monospace; background-color: #EEE }\n                </style>\n                <h1>%s</h1>\n                <h2>%s</h3>\n            \"\"\" % (title, html.escape(message))\n"
  },
  {
    "path": "src/Ui/UiServer.py",
    "content": "import logging\nimport time\nimport urllib\nimport socket\nimport gevent\n\nfrom gevent.pywsgi import WSGIServer\nfrom lib.gevent_ws import WebSocketHandler\n\nfrom .UiRequest import UiRequest\nfrom Site import SiteManager\nfrom Config import config\nfrom Debug import Debug\nimport importlib\n\n\n# Skip websocket handler if not necessary\nclass UiWSGIHandler(WebSocketHandler):\n\n    def __init__(self, *args, **kwargs):\n        self.server = args[2]\n        super(UiWSGIHandler, self).__init__(*args, **kwargs)\n        self.args = args\n        self.kwargs = kwargs\n\n    def handleError(self, err):\n        if config.debug:  # Allow websocket errors to appear on /Debug\n            import main\n            main.DebugHook.handleError()\n        else:\n            ui_request = UiRequest(self.server, {}, self.environ, self.start_response)\n            block_gen = ui_request.error500(\"UiWSGIHandler error: %s\" % Debug.formatExceptionMessage(err))\n            for block in block_gen:\n                self.write(block)\n\n    def run_application(self):\n        err_name = \"UiWSGIHandler websocket\" if \"HTTP_UPGRADE\" in self.environ else \"UiWSGIHandler\"\n        try:\n            super(UiWSGIHandler, self).run_application()\n        except (ConnectionAbortedError, ConnectionResetError, BrokenPipeError) as err:\n            logging.warning(\"%s connection error: %s\" % (err_name, err))\n        except Exception as err:\n            logging.warning(\"%s error: %s\" % (err_name, Debug.formatException(err)))\n            self.handleError(err)\n\n    def handle(self):\n        # Save socket to be able to close them properly on exit\n        self.server.sockets[self.client_address] = self.socket\n        super(UiWSGIHandler, self).handle()\n        del self.server.sockets[self.client_address]\n\n\nclass UiServer:\n    def __init__(self):\n        self.ip = config.ui_ip\n        self.port = config.ui_port\n        self.running = False\n        if self.ip == \"*\":\n            self.ip = \"0.0.0.0\"  # Bind all\n        if config.ui_host:\n            self.allowed_hosts = set(config.ui_host)\n        elif config.ui_ip == \"127.0.0.1\":\n            # IP Addresses are inherently allowed as they are immune to DNS\n            # rebinding attacks.\n            self.allowed_hosts = set([\"zero\", \"localhost:%s\" % config.ui_port])\n            # \"URI producers and normalizers should omit the port component and\n            # its ':' delimiter if port is empty or if its value would be the\n            # same as that of the scheme's default.\"\n            # Source: https://tools.ietf.org/html/rfc3986#section-3.2.3\n            # As a result, we need to support portless hosts if port 80 is in\n            # use.\n            if config.ui_port == 80:\n                self.allowed_hosts.update([\"localhost\"])\n        else:\n            self.allowed_hosts = set([])\n        self.allowed_ws_origins = set()\n        self.allow_trans_proxy = config.ui_trans_proxy\n\n        self.wrapper_nonces = []\n        self.add_nonces = []\n        self.websockets = []\n        self.site_manager = SiteManager.site_manager\n        self.sites = SiteManager.site_manager.list()\n        self.log = logging.getLogger(__name__)\n        config.error_logger.onNewRecord = self.handleErrorLogRecord\n\n    def handleErrorLogRecord(self, record):\n        self.updateWebsocket(log_event=record.levelname)\n\n    # After WebUI started\n    def afterStarted(self):\n        from util import Platform\n        Platform.setMaxfilesopened(config.max_files_opened)\n\n    # Handle WSGI request\n    def handleRequest(self, env, start_response):\n        path = bytes(env[\"PATH_INFO\"], \"raw-unicode-escape\").decode(\"utf8\")\n        if env.get(\"QUERY_STRING\"):\n            get = dict(urllib.parse.parse_qsl(env['QUERY_STRING']))\n        else:\n            get = {}\n        ui_request = UiRequest(self, get, env, start_response)\n        if config.debug:  # Let the exception catched by werkezung\n            return ui_request.route(path)\n        else:  # Catch and display the error\n            try:\n                return ui_request.route(path)\n            except Exception as err:\n                logging.debug(\"UiRequest error: %s\" % Debug.formatException(err))\n                return ui_request.error500(\"Err: %s\" % Debug.formatException(err))\n\n    # Reload the UiRequest class to prevent restarts in debug mode\n    def reload(self):\n        global UiRequest\n        import imp\n        import sys\n        importlib.reload(sys.modules[\"User.UserManager\"])\n        importlib.reload(sys.modules[\"Ui.UiWebsocket\"])\n        UiRequest = imp.load_source(\"UiRequest\", \"src/Ui/UiRequest.py\").UiRequest\n        # UiRequest.reload()\n\n    # Bind and run the server\n    def start(self):\n        self.running = True\n        handler = self.handleRequest\n\n        if config.debug:\n            # Auto reload UiRequest on change\n            from Debug import DebugReloader\n            DebugReloader.watcher.addCallback(self.reload)\n\n            # Werkzeug Debugger\n            try:\n                from werkzeug.debug import DebuggedApplication\n                handler = DebuggedApplication(self.handleRequest, evalex=True)\n            except Exception as err:\n                self.log.info(\"%s: For debugging please download Werkzeug (http://werkzeug.pocoo.org/)\" % err)\n                from Debug import DebugReloader\n        self.log.write = lambda msg: self.log.debug(msg.strip())  # For Wsgi access.log\n        self.log.info(\"--------------------------------------\")\n        if \":\" in config.ui_ip:\n            self.log.info(\"Web interface: http://[%s]:%s/\" % (config.ui_ip, config.ui_port))\n        else:\n            self.log.info(\"Web interface: http://%s:%s/\" % (config.ui_ip, config.ui_port))\n        self.log.info(\"--------------------------------------\")\n\n        if config.open_browser and config.open_browser != \"False\":\n            logging.info(\"Opening browser: %s...\", config.open_browser)\n            import webbrowser\n            try:\n                if config.open_browser == \"default_browser\":\n                    browser = webbrowser.get()\n                else:\n                    browser = webbrowser.get(config.open_browser)\n                url = \"http://%s:%s/%s\" % (config.ui_ip if config.ui_ip != \"*\" else \"127.0.0.1\", config.ui_port, config.homepage)\n                gevent.spawn_later(0.3, browser.open, url, new=2)\n            except Exception as err:\n                print(\"Error starting browser: %s\" % err)\n\n        self.server = WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log)\n        self.server.sockets = {}\n        self.afterStarted()\n        try:\n            self.server.serve_forever()\n        except Exception as err:\n            self.log.error(\"Web interface bind error, must be running already, exiting.... %s\" % err)\n            import main\n            main.file_server.stop()\n        self.log.debug(\"Stopped.\")\n\n    def stop(self):\n        self.log.debug(\"Stopping...\")\n        # Close WS sockets\n        if \"clients\" in dir(self.server):\n            for client in list(self.server.clients.values()):\n                client.ws.close()\n        # Close http sockets\n        sock_closed = 0\n        for sock in list(self.server.sockets.values()):\n            try:\n                sock.send(b\"bye\")\n                sock.shutdown(socket.SHUT_RDWR)\n                # sock._sock.close()\n                # sock.close()\n                sock_closed += 1\n            except Exception as err:\n                self.log.debug(\"Http connection close error: %s\" % err)\n        self.log.debug(\"Socket closed: %s\" % sock_closed)\n        time.sleep(0.1)\n        if config.debug:\n            from Debug import DebugReloader\n            DebugReloader.watcher.stop()\n\n        self.server.socket.close()\n        self.server.stop()\n        self.running = False\n        time.sleep(1)\n\n    def updateWebsocket(self, **kwargs):\n        if kwargs:\n            param = {\"event\": list(kwargs.items())[0]}\n        else:\n            param = None\n\n        for ws in self.websockets:\n            ws.event(\"serverChanged\", param)\n"
  },
  {
    "path": "src/Ui/UiWebsocket.py",
    "content": "import json\nimport time\nimport sys\nimport os\nimport shutil\nimport re\nimport copy\nimport logging\nimport stat\n\nimport gevent\n\nfrom Config import config\nfrom Site import SiteManager\nfrom Crypt import CryptBitcoin\nfrom Debug import Debug\nfrom util import QueryJson, RateLimit\nfrom Plugin import PluginManager\nfrom Translate import translate as _\nfrom util import helper\nfrom util import SafeRe\nfrom util.Flag import flag\nfrom Content.ContentManager import VerifyError, SignError\n\n\n@PluginManager.acceptPlugins\nclass UiWebsocket(object):\n    def __init__(self, ws, site, server, user, request):\n        self.ws = ws\n        self.site = site\n        self.user = user\n        self.log = site.log\n        self.request = request\n        self.permissions = []\n        self.server = server\n        self.next_message_id = 1\n        self.waiting_cb = {}  # Waiting for callback. Key: message_id, Value: function pointer\n        self.channels = []  # Channels joined to\n        self.state = {\"sending\": False}  # Shared state of websocket connection\n        self.send_queue = []  # Messages to send to client\n\n    # Start listener loop\n    def start(self):\n        ws = self.ws\n        if self.site.address == config.homepage and not self.site.page_requested:\n            # Add open fileserver port message or closed port error to homepage at first request after start\n            self.site.page_requested = True  # Dont add connection notification anymore\n            import main\n            file_server = main.file_server\n            if not file_server.port_opened or file_server.tor_manager.start_onions is None:\n                self.site.page_requested = False  # Not ready yet, check next time\n            else:\n                try:\n                    self.addHomepageNotifications()\n                except Exception as err:\n                    self.log.error(\"Uncaught Exception: \" + Debug.formatException(err))\n\n        for notification in self.site.notifications:  # Send pending notification messages\n            # send via WebSocket\n            self.cmd(\"notification\", notification)\n            # just in case, log them to terminal\n            if notification[0] == \"error\":\n                self.log.error(\"\\n*** %s\\n\" % self.dedent(notification[1]))\n\n        self.site.notifications = []\n\n        while True:\n            try:\n                if ws.closed:\n                    break\n                else:\n                    message = ws.receive()\n            except Exception as err:\n                self.log.error(\"WebSocket receive error: %s\" % Debug.formatException(err))\n                break\n\n            if message:\n                try:\n                    req = json.loads(message)\n                    self.handleRequest(req)\n                except Exception as err:\n                    if config.debug:  # Allow websocket errors to appear on /Debug\n                        import main\n                        main.DebugHook.handleError()\n                    self.log.error(\"WebSocket handleRequest error: %s \\n %s\" % (Debug.formatException(err), message))\n                    if not self.hasPlugin(\"Multiuser\"):\n                        self.cmd(\"error\", \"Internal error: %s\" % Debug.formatException(err, \"html\"))\n\n        self.onClosed()\n\n    def onClosed(self):\n        pass\n\n    def dedent(self, text):\n        return re.sub(\"[\\\\r\\\\n\\\\x20\\\\t]+\", \" \", text.strip().replace(\"<br>\", \" \"))\n\n    def addHomepageNotifications(self):\n        if not(self.hasPlugin(\"Multiuser\")) and not(self.hasPlugin(\"UiPassword\")):\n            bind_ip = getattr(config, \"ui_ip\", \"\")\n            whitelist = getattr(config, \"ui_restrict\", [])\n            # binds to the Internet, no IP whitelist, no UiPassword, no Multiuser\n            if (\"0.0.0.0\" == bind_ip or \"*\" == bind_ip) and (not whitelist):\n                self.site.notifications.append([\n                    \"error\",\n                    _(\"You are not going to set up a public gateway. However, <b>your Web UI is<br>\" +\n                        \"open to the whole Internet.</b> \" +\n                        \"Please check your configuration.\")\n                ])\n\n    def hasPlugin(self, name):\n        return name in PluginManager.plugin_manager.plugin_names\n\n    # Has permission to run the command\n    def hasCmdPermission(self, cmd):\n        flags = flag.db.get(self.getCmdFuncName(cmd), ())\n        if \"admin\" in flags and \"ADMIN\" not in self.permissions:\n            return False\n        else:\n            return True\n\n    # Has permission to access a site\n    def hasSitePermission(self, address, cmd=None):\n        if address != self.site.address and \"ADMIN\" not in self.site.settings[\"permissions\"]:\n            return False\n        else:\n            return True\n\n    def hasFilePermission(self, inner_path):\n        valid_signers = self.site.content_manager.getValidSigners(inner_path)\n        return self.site.settings[\"own\"] or self.user.getAuthAddress(self.site.address) in valid_signers\n\n    # Event in a channel\n    def event(self, channel, *params):\n        if channel in self.channels:  # We are joined to channel\n            if channel == \"siteChanged\":\n                site = params[0]\n                site_info = self.formatSiteInfo(site, create_user=False)\n                if len(params) > 1 and params[1]:  # Extra data\n                    site_info.update(params[1])\n                self.cmd(\"setSiteInfo\", site_info)\n            elif channel == \"serverChanged\":\n                server_info = self.formatServerInfo()\n                if len(params) > 0 and params[0]:  # Extra data\n                    server_info.update(params[0])\n                self.cmd(\"setServerInfo\", server_info)\n            elif channel == \"announcerChanged\":\n                site = params[0]\n                announcer_info = self.formatAnnouncerInfo(site)\n                if len(params) > 1 and params[1]:  # Extra data\n                    announcer_info.update(params[1])\n                self.cmd(\"setAnnouncerInfo\", announcer_info)\n\n    # Send response to client (to = message.id)\n    def response(self, to, result):\n        self.send({\"cmd\": \"response\", \"to\": to, \"result\": result})\n\n    # Send a command\n    def cmd(self, cmd, params={}, cb=None):\n        self.send({\"cmd\": cmd, \"params\": params}, cb)\n\n    # Encode to json and send message\n    def send(self, message, cb=None):\n        message[\"id\"] = self.next_message_id  # Add message id to allow response\n        self.next_message_id += 1\n        if cb:  # Callback after client responded\n            self.waiting_cb[message[\"id\"]] = cb\n        self.send_queue.append(message)\n        if self.state[\"sending\"]:\n            return  # Already sending\n        try:\n            while self.send_queue:\n                self.state[\"sending\"] = True\n                message = self.send_queue.pop(0)\n                self.ws.send(json.dumps(message))\n                self.state[\"sending\"] = False\n        except Exception as err:\n            self.log.debug(\"Websocket send error: %s\" % Debug.formatException(err))\n            self.state[\"sending\"] = False\n\n    def getPermissions(self, req_id):\n        permissions = self.site.settings[\"permissions\"]\n        if req_id >= 1000000:  # Its a wrapper command, allow admin commands\n            permissions = permissions[:]\n            permissions.append(\"ADMIN\")\n        return permissions\n\n    def asyncWrapper(self, func):\n        def asyncErrorWatcher(func, *args, **kwargs):\n            try:\n                result = func(*args, **kwargs)\n                if result is not None:\n                    self.response(args[0], result)\n            except Exception as err:\n                if config.debug:  # Allow websocket errors to appear on /Debug\n                    import main\n                    main.DebugHook.handleError()\n                self.log.error(\"WebSocket handleRequest error: %s\" % Debug.formatException(err))\n                self.cmd(\"error\", \"Internal error: %s\" % Debug.formatException(err, \"html\"))\n\n        def wrapper(*args, **kwargs):\n            gevent.spawn(asyncErrorWatcher, func, *args, **kwargs)\n        return wrapper\n\n    def getCmdFuncName(self, cmd):\n        func_name = \"action\" + cmd[0].upper() + cmd[1:]\n        return func_name\n\n    # Handle incoming messages\n    def handleRequest(self, req):\n\n        cmd = req.get(\"cmd\")\n        params = req.get(\"params\")\n        self.permissions = self.getPermissions(req[\"id\"])\n\n        if cmd == \"response\":  # It's a response to a command\n            return self.actionResponse(req[\"to\"], req[\"result\"])\n        else:  # Normal command\n            func_name = self.getCmdFuncName(cmd)\n            func = getattr(self, func_name, None)\n            if self.site.settings.get(\"deleting\"):\n                return self.response(req[\"id\"], {\"error\": \"Site is deleting\"})\n\n            if not func:  # Unknown command\n                return self.response(req[\"id\"], {\"error\": \"Unknown command: %s\" % cmd})\n\n            if not self.hasCmdPermission(cmd):  # Admin commands\n                return self.response(req[\"id\"], {\"error\": \"You don't have permission to run %s\" % cmd})\n\n        # Execute in parallel\n        func_flags = flag.db.get(self.getCmdFuncName(cmd), ())\n        if func_flags and \"async_run\" in func_flags:\n            func = self.asyncWrapper(func)\n\n        # Support calling as named, unnamed parameters and raw first argument too\n        if type(params) is dict:\n            result = func(req[\"id\"], **params)\n        elif type(params) is list:\n            result = func(req[\"id\"], *params)\n        elif params:\n            result = func(req[\"id\"], params)\n        else:\n            result = func(req[\"id\"])\n\n        if result is not None:\n            self.response(req[\"id\"], result)\n\n    # Format site info\n    def formatSiteInfo(self, site, create_user=True):\n        content = site.content_manager.contents.get(\"content.json\", {})\n        if content:  # Remove unnecessary data transfer\n            content = content.copy()\n            content[\"files\"] = len(content.get(\"files\", {}))\n            content[\"files_optional\"] = len(content.get(\"files_optional\", {}))\n            content[\"includes\"] = len(content.get(\"includes\", {}))\n            if \"sign\" in content:\n                del(content[\"sign\"])\n            if \"signs\" in content:\n                del(content[\"signs\"])\n            if \"signers_sign\" in content:\n                del(content[\"signers_sign\"])\n\n        settings = site.settings.copy()\n        del settings[\"wrapper_key\"]  # Dont expose wrapper key\n\n        ret = {\n            \"auth_address\": self.user.getAuthAddress(site.address, create=create_user),\n            \"cert_user_id\": self.user.getCertUserId(site.address),\n            \"address\": site.address,\n            \"address_short\": site.address_short,\n            \"address_hash\": site.address_hash.hex(),\n            \"settings\": settings,\n            \"content_updated\": site.content_updated,\n            \"bad_files\": len(site.bad_files),\n            \"size_limit\": site.getSizeLimit(),\n            \"next_size_limit\": site.getNextSizeLimit(),\n            \"peers\": max(site.settings.get(\"peers\", 0), len(site.peers)),\n            \"started_task_num\": site.worker_manager.started_task_num,\n            \"tasks\": len(site.worker_manager.tasks),\n            \"workers\": len(site.worker_manager.workers),\n            \"content\": content\n        }\n        if site.settings[\"own\"]:\n            ret[\"privatekey\"] = bool(self.user.getSiteData(site.address, create=create_user).get(\"privatekey\"))\n        if site.isServing() and content:\n            ret[\"peers\"] += 1  # Add myself if serving\n        return ret\n\n    def formatServerInfo(self):\n        import main\n        file_server = main.file_server\n        if file_server.port_opened == {}:\n            ip_external = None\n        else:\n            ip_external = any(file_server.port_opened.values())\n        back = {\n            \"ip_external\": ip_external,\n            \"port_opened\": file_server.port_opened,\n            \"platform\": sys.platform,\n            \"fileserver_ip\": config.fileserver_ip,\n            \"fileserver_port\": config.fileserver_port,\n            \"tor_enabled\": file_server.tor_manager.enabled,\n            \"tor_status\": file_server.tor_manager.status,\n            \"tor_has_meek_bridges\": file_server.tor_manager.has_meek_bridges,\n            \"tor_use_bridges\": config.tor_use_bridges,\n            \"ui_ip\": config.ui_ip,\n            \"ui_port\": config.ui_port,\n            \"version\": config.version,\n            \"rev\": config.rev,\n            \"timecorrection\": file_server.timecorrection,\n            \"language\": config.language,\n            \"debug\": config.debug,\n            \"offline\": config.offline,\n            \"plugins\": PluginManager.plugin_manager.plugin_names,\n            \"plugins_rev\": PluginManager.plugin_manager.plugins_rev,\n            \"user_settings\": self.user.settings\n        }\n        if \"ADMIN\" in self.site.settings[\"permissions\"]:\n            back[\"updatesite\"] = config.updatesite\n            back[\"dist_type\"] = config.dist_type\n            back[\"lib_verify_best\"] = CryptBitcoin.lib_verify_best\n        return back\n\n    def formatAnnouncerInfo(self, site):\n        return {\"address\": site.address, \"stats\": site.announcer.stats}\n\n    # - Actions -\n\n    def actionAs(self, to, address, cmd, params=[]):\n        if not self.hasSitePermission(address, cmd=cmd):\n            return self.response(to, \"No permission for site %s\" % address)\n        req_self = copy.copy(self)\n        req_self.site = self.server.sites.get(address)\n        req_self.hasCmdPermission = self.hasCmdPermission  # Use the same permissions as current site\n        req_obj = super(UiWebsocket, req_self)\n        req = {\"id\": to, \"cmd\": cmd, \"params\": params}\n        req_obj.handleRequest(req)\n\n    # Do callback on response {\"cmd\": \"response\", \"to\": message_id, \"result\": result}\n    def actionResponse(self, to, result):\n        if to in self.waiting_cb:\n            self.waiting_cb[to](result)  # Call callback function\n        else:\n            self.log.error(\"Websocket callback not found: %s, %s\" % (to, result))\n\n    # Send a simple pong answer\n    def actionPing(self, to):\n        self.response(to, \"pong\")\n\n    # Send site details\n    def actionSiteInfo(self, to, file_status=None):\n        ret = self.formatSiteInfo(self.site)\n        if file_status:  # Client queries file status\n            if self.site.storage.isFile(file_status):  # File exist, add event done\n                ret[\"event\"] = (\"file_done\", file_status)\n        self.response(to, ret)\n\n    def actionSiteBadFiles(self, to):\n        return list(self.site.bad_files.keys())\n\n    # Join to an event channel\n    def actionChannelJoin(self, to, channels):\n        if type(channels) != list:\n            channels = [channels]\n\n        for channel in channels:\n            if channel not in self.channels:\n                self.channels.append(channel)\n\n        self.response(to, \"ok\")\n\n    # Server variables\n    def actionServerInfo(self, to):\n        back = self.formatServerInfo()\n        self.response(to, back)\n\n    # Create a new wrapper nonce that allows to load html file\n    @flag.admin\n    def actionServerGetWrapperNonce(self, to):\n        wrapper_nonce = self.request.getWrapperNonce()\n        self.response(to, wrapper_nonce)\n\n    def actionAnnouncerInfo(self, to):\n        back = self.formatAnnouncerInfo(self.site)\n        self.response(to, back)\n\n    @flag.admin\n    def actionAnnouncerStats(self, to):\n        back = {}\n        trackers = self.site.announcer.getTrackers()\n        for site in list(self.server.sites.values()):\n            for tracker, stats in site.announcer.stats.items():\n                if tracker not in trackers:\n                    continue\n                if tracker not in back:\n                    back[tracker] = {}\n                is_latest_data = bool(stats[\"time_request\"] > back[tracker].get(\"time_request\", 0) and stats[\"status\"])\n                for key, val in stats.items():\n                    if key.startswith(\"num_\"):\n                        back[tracker][key] = back[tracker].get(key, 0) + val\n                    elif is_latest_data:\n                        back[tracker][key] = val\n\n        return back\n\n    # Sign content.json\n    def actionSiteSign(self, to, privatekey=None, inner_path=\"content.json\", remove_missing_optional=False, update_changed_files=False, response_ok=True):\n        self.log.debug(\"Signing: %s\" % inner_path)\n        site = self.site\n        extend = {}  # Extended info for signing\n\n        # Change to the file's content.json\n        file_info = site.content_manager.getFileInfo(inner_path)\n        if not inner_path.endswith(\"content.json\"):\n            if not file_info:\n                raise Exception(\"Invalid content.json file: %s\" % inner_path)\n            inner_path = file_info[\"content_inner_path\"]\n\n        # Add certificate to user files\n        is_user_content = file_info and (\"cert_signers\" in file_info or \"cert_signers_pattern\" in file_info)\n        if is_user_content and privatekey is None:\n            cert = self.user.getCert(self.site.address)\n            extend[\"cert_auth_type\"] = cert[\"auth_type\"]\n            extend[\"cert_user_id\"] = self.user.getCertUserId(site.address)\n            extend[\"cert_sign\"] = cert[\"cert_sign\"]\n            self.log.debug(\"Extending content.json with cert %s\" % extend[\"cert_user_id\"])\n\n        if not self.hasFilePermission(inner_path):\n            self.log.error(\"SiteSign error: you don't own this site & site owner doesn't allow you to do so.\")\n            return self.response(to, {\"error\": \"Forbidden, you can only modify your own sites\"})\n\n        if privatekey == \"stored\":  # Get privatekey from sites.json\n            privatekey = self.user.getSiteData(self.site.address).get(\"privatekey\")\n            if not privatekey:\n                self.cmd(\"notification\", [\"error\", _[\"Content signing failed\"] + \"<br><small>Private key not found in sites.json </small>\"])\n                self.response(to, {\"error\": \"Site sign failed: Private key not stored.\"})\n                self.log.error(\"Site sign failed: %s: Private key not stored in sites.json\" % inner_path)\n                return\n        if not privatekey:  # Get privatekey from users.json auth_address\n            privatekey = self.user.getAuthPrivatekey(self.site.address)\n\n        # Signing\n        # Reload content.json, ignore errors to make it up-to-date\n        site.content_manager.loadContent(inner_path, add_bad_files=False, force=True)\n        # Sign using private key sent by user\n        try:\n            site.content_manager.sign(inner_path, privatekey, extend=extend, update_changed_files=update_changed_files, remove_missing_optional=remove_missing_optional)\n        except (VerifyError, SignError) as err:\n            self.cmd(\"notification\", [\"error\", _[\"Content signing failed\"] + \"<br><small>%s</small>\" % err])\n            self.response(to, {\"error\": \"Site sign failed: %s\" % err})\n            self.log.error(\"Site sign failed: %s: %s\" % (inner_path, Debug.formatException(err)))\n            return\n        except Exception as err:\n            self.cmd(\"notification\", [\"error\", _[\"Content signing error\"] + \"<br><small>%s</small>\" % Debug.formatException(err)])\n            self.response(to, {\"error\": \"Site sign error: %s\" % Debug.formatException(err)})\n            self.log.error(\"Site sign error: %s: %s\" % (inner_path, Debug.formatException(err)))\n            return\n\n        site.content_manager.loadContent(inner_path, add_bad_files=False)  # Load new content.json, ignore errors\n\n        if update_changed_files:\n            self.site.updateWebsocket(file_done=inner_path)\n\n        if response_ok:\n            self.response(to, \"ok\")\n        else:\n            return inner_path\n\n    # Sign and publish content.json\n    def actionSitePublish(self, to, privatekey=None, inner_path=\"content.json\", sign=True, remove_missing_optional=False, update_changed_files=False):\n        if sign:\n            inner_path = self.actionSiteSign(\n                to, privatekey, inner_path, response_ok=False,\n                remove_missing_optional=remove_missing_optional, update_changed_files=update_changed_files\n            )\n            if not inner_path:\n                return\n        # Publishing\n        if not self.site.settings[\"serving\"]:  # Enable site if paused\n            self.site.settings[\"serving\"] = True\n            self.site.saveSettings()\n            self.site.announce()\n\n        if inner_path not in self.site.content_manager.contents:\n            return self.response(to, {\"error\": \"File %s not found\" % inner_path})\n\n        event_name = \"publish %s %s\" % (self.site.address, inner_path)\n        called_instantly = RateLimit.isAllowed(event_name, 30)\n        thread = RateLimit.callAsync(event_name, 30, self.doSitePublish, self.site, inner_path)  # Only publish once in 30 seconds\n        notification = \"linked\" not in dir(thread)  # Only display notification on first callback\n        thread.linked = True\n        if called_instantly:  # Allowed to call instantly\n            # At the end callback with request id and thread\n            self.cmd(\"progress\", [\"publish\", _[\"Content published to {0}/{1} peers.\"].format(0, 5), 0])\n            thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=notification))\n        else:\n            self.cmd(\n                \"notification\",\n                [\"info\", _[\"Content publish queued for {0:.0f} seconds.\"].format(RateLimit.delayLeft(event_name, 30)), 5000]\n            )\n            self.response(to, \"ok\")\n            # At the end display notification\n            thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=False))\n\n    def doSitePublish(self, site, inner_path):\n        def cbProgress(published, limit):\n            progress = int(float(published) / limit * 100)\n            self.cmd(\"progress\", [\n                \"publish\",\n                _[\"Content published to {0}/{1} peers.\"].format(published, limit),\n                progress\n            ])\n        diffs = site.content_manager.getDiffs(inner_path)\n        back = site.publish(limit=5, inner_path=inner_path, diffs=diffs, cb_progress=cbProgress)\n        if back == 0:  # Failed to publish to anyone\n            self.cmd(\"progress\", [\"publish\", _[\"Content publish failed.\"], -100])\n        else:\n            cbProgress(back, back)\n        return back\n\n    # Callback of site publish\n    def cbSitePublish(self, to, site, thread, notification=True, callback=True):\n        published = thread.value\n        if published > 0:  # Successfully published\n            if notification:\n                # self.cmd(\"notification\", [\"done\", _[\"Content published to {0} peers.\"].format(published), 5000])\n                site.updateWebsocket()  # Send updated site data to local websocket clients\n            if callback:\n                self.response(to, \"ok\")\n        else:\n            if len(site.peers) == 0:\n                import main\n                if any(main.file_server.port_opened.values()) or main.file_server.tor_manager.start_onions:\n                    if notification:\n                        self.cmd(\"notification\", [\"info\", _[\"No peers found, but your content is ready to access.\"]])\n                    if callback:\n                        self.response(to, \"ok\")\n                else:\n                    if notification:\n                        self.cmd(\"notification\", [\n                            \"info\",\n                            _(\"\"\"{_[Your network connection is restricted. Please, open <b>{0}</b> port]}<br>\n                            {_[on your router to make your site accessible for everyone.]}\"\"\").format(config.fileserver_port)\n                        ])\n                    if callback:\n                        self.response(to, {\"error\": \"Port not opened.\"})\n\n            else:\n                if notification:\n                    self.response(to, {\"error\": \"Content publish failed.\"})\n\n    def actionSiteReload(self, to, inner_path):\n        self.site.content_manager.loadContent(inner_path, add_bad_files=False)\n        self.site.storage.verifyFiles(quick_check=True)\n        self.site.updateWebsocket()\n        return \"ok\"\n\n    # Write a file to disk\n    def actionFileWrite(self, to, inner_path, content_base64, ignore_bad_files=False):\n        valid_signers = self.site.content_manager.getValidSigners(inner_path)\n        auth_address = self.user.getAuthAddress(self.site.address)\n        if not self.hasFilePermission(inner_path):\n            self.log.error(\"FileWrite forbidden %s not in valid_signers %s\" % (auth_address, valid_signers))\n            return self.response(to, {\"error\": \"Forbidden, you can only modify your own files\"})\n\n        # Try not to overwrite files currently in sync\n        content_inner_path = re.sub(\"^(.*)/.*?$\", \"\\\\1/content.json\", inner_path)  # Also check the content.json from same directory\n        if (self.site.bad_files.get(inner_path) or self.site.bad_files.get(content_inner_path)) and not ignore_bad_files:\n            found = self.site.needFile(inner_path, update=True, priority=10)\n            if not found:\n                self.cmd(\n                    \"confirm\",\n                    [_[\"This file still in sync, if you write it now, then the previous content may be lost.\"], _[\"Write content anyway\"]],\n                    lambda res: self.actionFileWrite(to, inner_path, content_base64, ignore_bad_files=True)\n                )\n                return False\n\n        try:\n            import base64\n            content = base64.b64decode(content_base64)\n            # Save old file to generate patch later\n            if (\n                inner_path.endswith(\".json\") and not inner_path.endswith(\"content.json\") and\n                self.site.storage.isFile(inner_path) and not self.site.storage.isFile(inner_path + \"-old\")\n            ):\n                try:\n                    self.site.storage.rename(inner_path, inner_path + \"-old\")\n                except Exception:\n                    # Rename failed, fall back to standard file write\n                    f_old = self.site.storage.open(inner_path, \"rb\")\n                    f_new = self.site.storage.open(inner_path + \"-old\", \"wb\")\n                    shutil.copyfileobj(f_old, f_new)\n\n            self.site.storage.write(inner_path, content)\n        except Exception as err:\n            self.log.error(\"File write error: %s\" % Debug.formatException(err))\n            return self.response(to, {\"error\": \"Write error: %s\" % Debug.formatException(err)})\n\n        if inner_path.endswith(\"content.json\"):\n            self.site.content_manager.loadContent(inner_path, add_bad_files=False, force=True)\n\n        self.response(to, \"ok\")\n\n        # Send sitechanged to other local users\n        for ws in self.site.websockets:\n            if ws != self:\n                ws.event(\"siteChanged\", self.site, {\"event\": [\"file_done\", inner_path]})\n\n    def actionFileDelete(self, to, inner_path):\n        if not self.hasFilePermission(inner_path):\n            self.log.error(\"File delete error: you don't own this site & you are not approved by the owner.\")\n            return self.response(to, {\"error\": \"Forbidden, you can only modify your own files\"})\n\n        need_delete = True\n        file_info = self.site.content_manager.getFileInfo(inner_path)\n        if file_info and file_info.get(\"optional\"):\n            # Non-existing optional files won't be removed from content.json, so we have to do it manually\n            self.log.debug(\"Deleting optional file: %s\" % inner_path)\n            relative_path = file_info[\"relative_path\"]\n            content_json = self.site.storage.loadJson(file_info[\"content_inner_path\"])\n            if relative_path in content_json.get(\"files_optional\", {}):\n                del content_json[\"files_optional\"][relative_path]\n                self.site.storage.writeJson(file_info[\"content_inner_path\"], content_json)\n                self.site.content_manager.loadContent(file_info[\"content_inner_path\"], add_bad_files=False, force=True)\n                need_delete = self.site.storage.isFile(inner_path)  # File sill exists after removing from content.json (owned site)\n\n        if need_delete:\n            try:\n                self.site.storage.delete(inner_path)\n            except Exception as err:\n                self.log.error(\"File delete error: %s\" % err)\n                return self.response(to, {\"error\": \"Delete error: %s\" % Debug.formatExceptionMessage(err)})\n\n        self.response(to, \"ok\")\n\n        # Send sitechanged to other local users\n        for ws in self.site.websockets:\n            if ws != self:\n                ws.event(\"siteChanged\", self.site, {\"event\": [\"file_deleted\", inner_path]})\n\n    # Find data in json files\n    def actionFileQuery(self, to, dir_inner_path, query=None):\n        # s = time.time()\n        dir_path = self.site.storage.getPath(dir_inner_path)\n        rows = list(QueryJson.query(dir_path, query or \"\"))\n        # self.log.debug(\"FileQuery %s %s done in %s\" % (dir_inner_path, query, time.time()-s))\n        return self.response(to, rows)\n\n    # List files in directory\n    @flag.async_run\n    def actionFileList(self, to, inner_path):\n        try:\n            return list(self.site.storage.walk(inner_path))\n        except Exception as err:\n            self.log.error(\"fileList %s error: %s\" % (inner_path, Debug.formatException(err)))\n            return {\"error\": Debug.formatExceptionMessage(err)}\n\n    # List directories in a directory\n    @flag.async_run\n    def actionDirList(self, to, inner_path, stats=False):\n        try:\n            if stats:\n                back = []\n                for file_name in self.site.storage.list(inner_path):\n                    file_stats = os.stat(self.site.storage.getPath(inner_path + \"/\" + file_name))\n                    is_dir = stat.S_ISDIR(file_stats.st_mode)\n                    back.append(\n                        {\"name\": file_name, \"size\": file_stats.st_size, \"is_dir\": is_dir}\n                    )\n                return back\n            else:\n                return list(self.site.storage.list(inner_path))\n        except Exception as err:\n            self.log.error(\"dirList %s error: %s\" % (inner_path, Debug.formatException(err)))\n            return {\"error\": Debug.formatExceptionMessage(err)}\n\n    # Sql query\n    def actionDbQuery(self, to, query, params=None, wait_for=None):\n        if config.debug or config.verbose:\n            s = time.time()\n        rows = []\n        try:\n            res = self.site.storage.query(query, params)\n        except Exception as err:  # Response the error to client\n            self.log.error(\"DbQuery error: %s\" % Debug.formatException(err))\n            return self.response(to, {\"error\": Debug.formatExceptionMessage(err)})\n        # Convert result to dict\n        for row in res:\n            rows.append(dict(row))\n        if config.verbose and time.time() - s > 0.1:  # Log slow query\n            self.log.debug(\"Slow query: %s (%.3fs)\" % (query, time.time() - s))\n        return self.response(to, rows)\n\n    # Return file content\n    @flag.async_run\n    def actionFileGet(self, to, inner_path, required=True, format=\"text\", timeout=300, priority=6):\n        try:\n            if required or inner_path in self.site.bad_files:\n                with gevent.Timeout(timeout):\n                    self.site.needFile(inner_path, priority=priority)\n            body = self.site.storage.read(inner_path, \"rb\")\n        except (Exception, gevent.Timeout) as err:\n            self.log.debug(\"%s fileGet error: %s\" % (inner_path, Debug.formatException(err)))\n            body = None\n\n        if not body:\n            body = None\n        elif format == \"base64\":\n            import base64\n            body = base64.b64encode(body).decode()\n        else:\n            try:\n                body = body.decode()\n            except Exception as err:\n                self.response(to, {\"error\": \"Error decoding text: %s\" % err})\n        self.response(to, body)\n\n    @flag.async_run\n    def actionFileNeed(self, to, inner_path, timeout=300, priority=6):\n        try:\n            with gevent.Timeout(timeout):\n                self.site.needFile(inner_path, priority=priority)\n        except (Exception, gevent.Timeout) as err:\n            return self.response(to, {\"error\": Debug.formatExceptionMessage(err)})\n        return self.response(to, \"ok\")\n\n    def actionFileRules(self, to, inner_path, use_my_cert=False, content=None):\n        if not content:  # No content defined by function call\n            content = self.site.content_manager.contents.get(inner_path)\n\n        if not content:  # File not created yet\n            cert = self.user.getCert(self.site.address)\n            if cert and cert[\"auth_address\"] in self.site.content_manager.getValidSigners(inner_path):\n                # Current selected cert if valid for this site, add it to query rules\n                content = {}\n                content[\"cert_auth_type\"] = cert[\"auth_type\"]\n                content[\"cert_user_id\"] = self.user.getCertUserId(self.site.address)\n                content[\"cert_sign\"] = cert[\"cert_sign\"]\n\n        rules = self.site.content_manager.getRules(inner_path, content)\n        if inner_path.endswith(\"content.json\") and rules:\n            if content:\n                rules[\"current_size\"] = len(json.dumps(content)) + sum([file[\"size\"] for file in list(content.get(\"files\", {}).values())])\n            else:\n                rules[\"current_size\"] = 0\n        return self.response(to, rules)\n\n    # Add certificate to user\n    def actionCertAdd(self, to, domain, auth_type, auth_user_name, cert):\n        try:\n            res = self.user.addCert(self.user.getAuthAddress(self.site.address), domain, auth_type, auth_user_name, cert)\n            if res is True:\n                self.cmd(\n                    \"notification\",\n                    [\"done\", _(\"{_[New certificate added]:} <b>{auth_type}/{auth_user_name}@{domain}</b>.\")]\n                )\n                self.user.setCert(self.site.address, domain)\n                self.site.updateWebsocket(cert_changed=domain)\n                self.response(to, \"ok\")\n            elif res is False:\n                # Display confirmation of change\n                cert_current = self.user.certs[domain]\n                body = _(\"{_[Your current certificate]:} <b>{cert_current[auth_type]}/{cert_current[auth_user_name]}@{domain}</b>\")\n                self.cmd(\n                    \"confirm\",\n                    [body, _(\"Change it to {auth_type}/{auth_user_name}@{domain}\")],\n                    lambda res: self.cbCertAddConfirm(to, domain, auth_type, auth_user_name, cert)\n                )\n            else:\n                self.response(to, \"Not changed\")\n        except Exception as err:\n            self.log.error(\"CertAdd error: Exception - %s (%s)\" % (err.message, Debug.formatException(err)))\n            self.response(to, {\"error\": err.message})\n\n    def cbCertAddConfirm(self, to, domain, auth_type, auth_user_name, cert):\n        self.user.deleteCert(domain)\n        self.user.addCert(self.user.getAuthAddress(self.site.address), domain, auth_type, auth_user_name, cert)\n        self.cmd(\n            \"notification\",\n            [\"done\", _(\"Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.\")]\n        )\n        self.user.setCert(self.site.address, domain)\n        self.site.updateWebsocket(cert_changed=domain)\n        self.response(to, \"ok\")\n\n    # Select certificate for site\n    def actionCertSelect(self, to, accepted_domains=[], accept_any=False, accepted_pattern=None):\n        accounts = []\n        accounts.append([\"\", _[\"No certificate\"], \"\"])  # Default option\n        active = \"\"  # Make it active if no other option found\n\n        # Add my certs\n        auth_address = self.user.getAuthAddress(self.site.address)  # Current auth address\n        site_data = self.user.getSiteData(self.site.address)  # Current auth address\n\n        if not accepted_domains and not accepted_pattern:  # Accept any if no filter defined\n            accept_any = True\n\n        for domain, cert in list(self.user.certs.items()):\n            if auth_address == cert[\"auth_address\"] and domain == site_data.get(\"cert\"):\n                active = domain\n            title = cert[\"auth_user_name\"] + \"@\" + domain\n            accepted_pattern_match = accepted_pattern and SafeRe.match(accepted_pattern, domain)\n            if domain in accepted_domains or accept_any or accepted_pattern_match:\n                accounts.append([domain, title, \"\"])\n            else:\n                accounts.append([domain, title, \"disabled\"])\n\n        # Render the html\n        body = \"<span style='padding-bottom: 5px; display: inline-block'>\" + _[\"Select account you want to use in this site:\"] + \"</span>\"\n        # Accounts\n        for domain, account, css_class in accounts:\n            if domain == active:\n                css_class += \" active\"  # Currently selected option\n                title = _(\"<b>%s</b> <small>({_[currently selected]})</small>\") % account\n            else:\n                title = \"<b>%s</b>\" % account\n            body += \"<a href='#Select+account' class='select select-close cert %s' title='%s'>%s</a>\" % (css_class, domain, title)\n        # More available  providers\n        more_domains = [domain for domain in accepted_domains if domain not in self.user.certs]  # Domains we not displayed yet\n        if more_domains:\n            # body+= \"<small style='margin-top: 10px; display: block'>Accepted authorization providers by the site:</small>\"\n            body += \"<div style='background-color: #F7F7F7; margin-right: -30px'>\"\n            for domain in more_domains:\n                body += _(\"\"\"\n                 <a href='/{domain}' target='_top' class='select'>\n                  <small style='float: right; margin-right: 40px; margin-top: -1px'>{_[Register]} &raquo;</small>{domain}\n                 </a>\n                \"\"\")\n            body += \"</div>\"\n\n        script = \"\"\"\n             $(\".notification .select.cert\").on(\"click\", function() {\n                $(\".notification .select\").removeClass('active')\n                zeroframe.response(%s, this.title)\n                return false\n             })\n        \"\"\" % self.next_message_id\n\n        self.cmd(\"notification\", [\"ask\", body], lambda domain: self.actionCertSet(to, domain))\n        self.cmd(\"injectScript\", script)\n\n    # - Admin actions -\n\n    @flag.admin\n    def actionPermissionAdd(self, to, permission):\n        if permission not in self.site.settings[\"permissions\"]:\n            self.site.settings[\"permissions\"].append(permission)\n            self.site.saveSettings()\n            self.site.updateWebsocket(permission_added=permission)\n        self.response(to, \"ok\")\n\n    @flag.admin\n    def actionPermissionRemove(self, to, permission):\n        self.site.settings[\"permissions\"].remove(permission)\n        self.site.saveSettings()\n        self.site.updateWebsocket(permission_removed=permission)\n        self.response(to, \"ok\")\n\n    @flag.admin\n    def actionPermissionDetails(self, to, permission):\n        if permission == \"ADMIN\":\n            self.response(to, _[\"Modify your client's configuration and access all site\"] + \" <span style='color: red'>\" + _[\"(Dangerous!)\"] + \"</span>\")\n        elif permission == \"NOSANDBOX\":\n            self.response(to, _[\"Modify your client's configuration and access all site\"] + \" <span style='color: red'>\" + _[\"(Dangerous!)\"] + \"</span>\")\n        elif permission == \"PushNotification\":\n            self.response(to, _[\"Send notifications\"])\n        else:\n            self.response(to, \"\")\n\n    # Set certificate that used for authenticate user for site\n    @flag.admin\n    def actionCertSet(self, to, domain):\n        self.user.setCert(self.site.address, domain)\n        self.site.updateWebsocket(cert_changed=domain)\n        self.response(to, \"ok\")\n\n    # List user's certificates\n    @flag.admin\n    def actionCertList(self, to):\n        back = []\n        auth_address = self.user.getAuthAddress(self.site.address)\n        for domain, cert in list(self.user.certs.items()):\n            back.append({\n                \"auth_address\": cert[\"auth_address\"],\n                \"auth_type\": cert[\"auth_type\"],\n                \"auth_user_name\": cert[\"auth_user_name\"],\n                \"domain\": domain,\n                \"selected\": cert[\"auth_address\"] == auth_address\n            })\n        return back\n\n    # List all site info\n    @flag.admin\n    def actionSiteList(self, to, connecting_sites=False):\n        ret = []\n        for site in list(self.server.sites.values()):\n            if not site.content_manager.contents.get(\"content.json\") and not connecting_sites:\n                continue  # Incomplete site\n            ret.append(self.formatSiteInfo(site, create_user=False))  # Dont generate the auth_address on listing\n        self.response(to, ret)\n\n    # Join to an event channel on all sites\n    @flag.admin\n    def actionChannelJoinAllsite(self, to, channel):\n        if channel not in self.channels:  # Add channel to channels\n            self.channels.append(channel)\n\n        for site in list(self.server.sites.values()):  # Add websocket to every channel\n            if self not in site.websockets:\n                site.websockets.append(self)\n\n        self.response(to, \"ok\")\n\n    # Update site content.json\n    def actionSiteUpdate(self, to, address, check_files=False, since=None, announce=False):\n        def updateThread():\n            site.update(announce=announce, check_files=check_files, since=since)\n            self.response(to, \"Updated\")\n\n        site = self.server.sites.get(address)\n        if site and (site.address == self.site.address or \"ADMIN\" in self.site.settings[\"permissions\"]):\n            if not site.settings[\"serving\"]:\n                site.settings[\"serving\"] = True\n                site.saveSettings()\n\n            gevent.spawn(updateThread)\n        else:\n            self.response(to, {\"error\": \"Unknown site: %s\" % address})\n\n    # Pause site serving\n    @flag.admin\n    def actionSitePause(self, to, address):\n        site = self.server.sites.get(address)\n        if site:\n            site.settings[\"serving\"] = False\n            site.saveSettings()\n            site.updateWebsocket()\n            site.worker_manager.stopWorkers()\n            self.response(to, \"Paused\")\n        else:\n            self.response(to, {\"error\": \"Unknown site: %s\" % address})\n\n    # Resume site serving\n    @flag.admin\n    def actionSiteResume(self, to, address):\n        site = self.server.sites.get(address)\n        if site:\n            site.settings[\"serving\"] = True\n            site.saveSettings()\n            gevent.spawn(site.update, announce=True)\n            time.sleep(0.001)  # Wait for update thread starting\n            site.updateWebsocket()\n            self.response(to, \"Resumed\")\n        else:\n            self.response(to, {\"error\": \"Unknown site: %s\" % address})\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionSiteDelete(self, to, address):\n        site = self.server.sites.get(address)\n        if site:\n            site.delete()\n            self.user.deleteSiteData(address)\n            self.response(to, \"Deleted\")\n            import gc\n            gc.collect(2)\n        else:\n            self.response(to, {\"error\": \"Unknown site: %s\" % address})\n\n    def cbSiteClone(self, to, address, root_inner_path=\"\", target_address=None, redirect=True):\n        self.cmd(\"notification\", [\"info\", _[\"Cloning site...\"]])\n        site = self.server.sites.get(address)\n        response = {}\n        if target_address:\n            target_site = self.server.sites.get(target_address)\n            privatekey = self.user.getSiteData(target_site.address).get(\"privatekey\")\n            site.clone(target_address, privatekey, root_inner_path=root_inner_path)\n            self.cmd(\"notification\", [\"done\", _[\"Site source code upgraded!\"]])\n            site.publish()\n            response = {\"address\": target_address}\n        else:\n            # Generate a new site from user's bip32 seed\n            new_address, new_address_index, new_site_data = self.user.getNewSiteData()\n            new_site = site.clone(new_address, new_site_data[\"privatekey\"], address_index=new_address_index, root_inner_path=root_inner_path)\n            new_site.settings[\"own\"] = True\n            new_site.saveSettings()\n            self.cmd(\"notification\", [\"done\", _[\"Site cloned\"]])\n            if redirect:\n                self.cmd(\"redirect\", \"/%s\" % new_address)\n            gevent.spawn(new_site.announce)\n            response = {\"address\": new_address}\n        self.response(to, response)\n        return \"ok\"\n\n    @flag.no_multiuser\n    def actionSiteClone(self, to, address, root_inner_path=\"\", target_address=None, redirect=True):\n        if not SiteManager.site_manager.isAddress(address):\n            self.response(to, {\"error\": \"Not a site: %s\" % address})\n            return\n\n        if not self.server.sites.get(address):\n            # Don't expose site existence\n            return\n\n        site = self.server.sites.get(address)\n        if site.bad_files:\n            for bad_inner_path in list(site.bad_files.keys()):\n                is_user_file = \"cert_signers\" in site.content_manager.getRules(bad_inner_path)\n                if not is_user_file and bad_inner_path != \"content.json\":\n                    self.cmd(\"notification\", [\"error\", _[\"Clone error: Site still in sync\"]])\n                    return {\"error\": \"Site still in sync\"}\n\n        if \"ADMIN\" in self.getPermissions(to):\n            self.cbSiteClone(to, address, root_inner_path, target_address, redirect)\n        else:\n            self.cmd(\n                \"confirm\",\n                [_[\"Clone site <b>%s</b>?\"] % address, _[\"Clone\"]],\n                lambda res: self.cbSiteClone(to, address, root_inner_path, target_address, redirect)\n            )\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionSiteSetLimit(self, to, size_limit):\n        self.site.settings[\"size_limit\"] = int(size_limit)\n        self.site.saveSettings()\n        self.response(to, \"ok\")\n        self.site.updateWebsocket()\n        self.site.download(blind_includes=True)\n\n    @flag.admin\n    def actionSiteAdd(self, to, address):\n        site_manager = SiteManager.site_manager\n        if address in site_manager.sites:\n            return {\"error\": \"Site already added\"}\n        else:\n            if site_manager.need(address):\n                return \"ok\"\n            else:\n                return {\"error\": \"Invalid address\"}\n\n    @flag.async_run\n    def actionSiteListModifiedFiles(self, to, content_inner_path=\"content.json\"):\n        content = self.site.content_manager.contents.get(content_inner_path)\n        if not content:\n            return {\"error\": \"content file not avaliable\"}\n\n        min_mtime = content.get(\"modified\", 0)\n        site_path = self.site.storage.directory\n        modified_files = []\n\n        # Load cache if not signed since last modified check\n        if content.get(\"modified\", 0) < self.site.settings[\"cache\"].get(\"time_modified_files_check\", 0):\n            min_mtime = self.site.settings[\"cache\"].get(\"time_modified_files_check\")\n            modified_files = self.site.settings[\"cache\"].get(\"modified_files\", [])\n\n        inner_paths = [content_inner_path] + list(content.get(\"includes\", {}).keys()) + list(content.get(\"files\", {}).keys())\n\n        if len(inner_paths) > 100:\n            return {\"error\": \"Too many files in content.json\"}\n\n        for relative_inner_path in inner_paths:\n            inner_path = helper.getDirname(content_inner_path) + relative_inner_path\n            try:\n                is_mtime_newer = os.path.getmtime(self.site.storage.getPath(inner_path)) > min_mtime + 1\n                if is_mtime_newer:\n                    if inner_path.endswith(\"content.json\"):\n                        is_modified = self.site.content_manager.isModified(inner_path)\n                    else:\n                        previous_size = content[\"files\"][inner_path][\"size\"]\n                        is_same_size = self.site.storage.getSize(inner_path) == previous_size\n                        ext = inner_path.rsplit(\".\", 1)[-1]\n                        is_text_file = ext in [\"json\", \"txt\", \"html\", \"js\", \"css\"]\n                        if is_same_size:\n                            if is_text_file:\n                                is_modified = self.site.content_manager.isModified(inner_path)  # Check sha512 hash\n                            else:\n                                is_modified = False\n                        else:\n                            is_modified = True\n\n                    # Check ran, modified back to original value, but in the cache\n                    if not is_modified and inner_path in modified_files:\n                        modified_files.remove(inner_path)\n                else:\n                    is_modified = False\n            except Exception as err:\n                if not self.site.storage.isFile(inner_path):  # File deleted\n                    is_modified = True\n                else:\n                    raise err\n            if is_modified and inner_path not in modified_files:\n                modified_files.append(inner_path)\n\n        self.site.settings[\"cache\"][\"time_modified_files_check\"] = time.time()\n        self.site.settings[\"cache\"][\"modified_files\"] = modified_files\n        return {\"modified_files\": modified_files}\n\n    @flag.admin\n    def actionSiteSetSettingsValue(self, to, key, value):\n        if key not in [\"modified_files_notification\"]:\n            return {\"error\": \"Can't change this key\"}\n\n        self.site.settings[key] = value\n\n        return \"ok\"\n\n    def actionUserGetSettings(self, to):\n        settings = self.user.sites.get(self.site.address, {}).get(\"settings\", {})\n        self.response(to, settings)\n\n    def actionUserSetSettings(self, to, settings):\n        self.user.setSiteSettings(self.site.address, settings)\n        self.response(to, \"ok\")\n\n    def actionUserGetGlobalSettings(self, to):\n        settings = self.user.settings\n        self.response(to, settings)\n\n    @flag.admin\n    def actionUserSetGlobalSettings(self, to, settings):\n        self.user.settings = settings\n        self.user.save()\n        self.response(to, \"ok\")\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionServerErrors(self, to):\n        return config.error_logger.lines\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionServerUpdate(self, to):\n        def cbServerUpdate(res):\n            self.response(to, res)\n            if not res:\n                return False\n            for websocket in self.server.websockets:\n                websocket.cmd(\n                    \"notification\",\n                    [\"info\", _[\"Updating ZeroNet client, will be back in a few minutes...\"], 20000]\n                )\n                websocket.cmd(\"updating\")\n\n            import main\n            main.update_after_shutdown = True\n            main.restart_after_shutdown = True\n            SiteManager.site_manager.save()\n            main.file_server.stop()\n            main.ui_server.stop()\n\n        self.cmd(\n            \"confirm\",\n            [_[\"Update <b>ZeroNet client</b> to latest version?\"], _[\"Update\"]],\n            cbServerUpdate\n        )\n\n    @flag.admin\n    @flag.async_run\n    @flag.no_multiuser\n    def actionServerPortcheck(self, to):\n        import main\n        file_server = main.file_server\n        file_server.portCheck()\n        self.response(to, file_server.port_opened)\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionServerShutdown(self, to, restart=False):\n        import main\n        def cbServerShutdown(res):\n            self.response(to, res)\n            if not res:\n                return False\n            if restart:\n                main.restart_after_shutdown = True\n            main.file_server.stop()\n            main.ui_server.stop()\n\n        if restart:\n            message = [_[\"Restart <b>ZeroNet client</b>?\"], _[\"Restart\"]]\n        else:\n            message = [_[\"Shut down <b>ZeroNet client</b>?\"], _[\"Shut down\"]]\n        self.cmd(\"confirm\", message, cbServerShutdown)\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionServerShowdirectory(self, to, directory=\"backup\", inner_path=\"\"):\n        if self.request.env[\"REMOTE_ADDR\"] != \"127.0.0.1\":\n            return self.response(to, {\"error\": \"Only clients from 127.0.0.1 allowed to run this command\"})\n\n        import webbrowser\n        if directory == \"backup\":\n            path = os.path.abspath(config.data_dir)\n        elif directory == \"log\":\n            path = os.path.abspath(config.log_dir)\n        elif directory == \"site\":\n            path = os.path.abspath(self.site.storage.getPath(helper.getDirname(inner_path)))\n\n        if os.path.isdir(path):\n            self.log.debug(\"Opening: %s\" % path)\n            webbrowser.open('file://' + path)\n            return self.response(to, \"ok\")\n        else:\n            return self.response(to, {\"error\": \"Not a directory\"})\n\n    @flag.admin\n    @flag.no_multiuser\n    def actionConfigSet(self, to, key, value):\n        import main\n\n        self.log.debug(\"Changing config %s value to %r\" % (key, value))\n        if key not in config.keys_api_change_allowed:\n            self.response(to, {\"error\": \"Forbidden: You cannot set this config key\"})\n            return\n\n        if key == \"open_browser\":\n            if value not in [\"default_browser\", \"False\"]:\n                self.response(to, {\"error\": \"Forbidden: Invalid value\"})\n                return\n\n        # Remove empty lines from lists\n        if type(value) is list:\n            value = [line for line in value if line]\n\n        config.saveValue(key, value)\n\n        if key not in config.keys_restart_need:\n            if value is None:  # Default value\n                setattr(config, key, config.parser.get_default(key))\n                setattr(config.arguments, key, config.parser.get_default(key))\n            else:\n                setattr(config, key, value)\n                setattr(config.arguments, key, value)\n        else:\n            config.need_restart = True\n            config.pending_changes[key] = value\n\n        if key == \"language\":\n            import Translate\n            for translate in Translate.translates:\n                translate.setLanguage(value)\n            message = _[\"You have successfully changed the web interface's language!\"] + \"<br>\"\n            message += _[\"Due to the browser's caching, the full transformation could take some minute.\"]\n            self.cmd(\"notification\", [\"done\", message, 10000])\n\n        if key == \"tor_use_bridges\":\n            if value is None:\n                value = False\n            else:\n                value = True\n            tor_manager = main.file_server.tor_manager\n            tor_manager.request(\"SETCONF UseBridges=%i\" % value)\n\n        if key == \"trackers_file\":\n            config.loadTrackersFile()\n\n        if key == \"log_level\":\n            logging.getLogger('').setLevel(logging.getLevelName(config.log_level))\n\n        if key == \"ip_external\":\n            gevent.spawn(main.file_server.portCheck)\n\n        if key == \"offline\":\n            if value:\n                main.file_server.closeConnections()\n            else:\n                gevent.spawn(main.file_server.checkSites, check_files=False, force_port_check=True)\n\n        self.response(to, \"ok\")\n"
  },
  {
    "path": "src/Ui/__init__.py",
    "content": "from .UiServer import UiServer\nfrom .UiRequest import UiRequest\nfrom .UiWebsocket import UiWebsocket"
  },
  {
    "path": "src/Ui/media/Fixbutton.coffee",
    "content": "class Fixbutton\n\tconstructor: ->\n\t\t@dragging = false\n\t\t$(\".fixbutton-bg\").on \"mouseover\", ->\n\t\t\t$(\".fixbutton-bg\").stop().animate({\"scale\": 0.7}, 800, \"easeOutElastic\")\n\t\t\t$(\".fixbutton-burger\").stop().animate({\"opacity\": 1.5, \"left\": 0}, 800, \"easeOutElastic\")\n\t\t\t$(\".fixbutton-text\").stop().animate({\"opacity\": 0, \"left\": 20}, 300, \"easeOutCubic\")\n\n\t\t$(\".fixbutton-bg\").on \"mouseout\", ->\n\t\t\tif $(\".fixbutton\").hasClass(\"dragging\")\n\t\t\t\treturn true\n\t\t\t$(\".fixbutton-bg\").stop().animate({\"scale\": 0.6}, 300, \"easeOutCubic\")\n\t\t\t$(\".fixbutton-burger\").stop().animate({\"opacity\": 0, \"left\": -20}, 300, \"easeOutCubic\")\n\t\t\t$(\".fixbutton-text\").stop().animate({\"opacity\": 0.9, \"left\": 0}, 300, \"easeOutBack\")\n\n\n\t\t###$(\".fixbutton-bg\").on \"click\", ->\n\t\t\treturn false\n\t\t###\n\n\t\t$(\".fixbutton-bg\").on \"mousedown\", ->\n\t\t\t# $(\".fixbutton-burger\").stop().animate({\"scale\": 0.7, \"left\": 0}, 300, \"easeOutCubic\")\n\t\t\t#$(\"#inner-iframe\").toggleClass(\"back\")\n\t\t\t#$(\".wrapper-iframe\").stop().animate({\"scale\": 0.9}, 600, \"easeOutCubic\")\n\t\t\t#$(\"body\").addClass(\"back\")\n\n\t\t$(\".fixbutton-bg\").on \"mouseup\", ->\n\t\t\t# $(\".fixbutton-burger\").stop().animate({\"scale\": 1, \"left\": 0}, 600, \"easeOutElastic\")\n\n\n\nwindow.Fixbutton = Fixbutton\n"
  },
  {
    "path": "src/Ui/media/Infopanel.coffee",
    "content": "class Infopanel\n\tconstructor: (@elem) ->\n\t\t@visible = false\n\n\tshow: (closed=false) =>\n\t\t@elem.parent().addClass(\"visible\")\n\t\tif closed\n\t\t\t@close()\n\t\telse\n\t\t\t@open()\n\n\tunfold: =>\n\t\t@elem.toggleClass(\"unfolded\")\n\t\treturn false\n\n\tupdateEvents: =>\n\t\t@elem.off(\"click\")\n\t\t@elem.find(\".close\").off(\"click\")\n\t\t@elem.find(\".line\").off(\"click\")\n\n\t\t@elem.find(\".line\").on(\"click\", @unfold)\n\n\t\tif @elem.hasClass(\"closed\")\n\t\t\t@elem.on \"click\", =>\n\t\t\t\t@onOpened()\n\t\t\t\t@open()\n\t\telse\n\t\t\t@elem.find(\".close\").on \"click\", =>\n\t\t\t\t@onClosed()\n\t\t\t\t@close()\n\n\thide: =>\n\t\t@elem.parent().removeClass(\"visible\")\n\n\tclose: =>\n\t\t@elem.addClass(\"closed\")\n\t\t@updateEvents()\n\t\treturn false\n\n\topen: =>\n\t\t@elem.removeClass(\"closed\")\n\t\t@updateEvents()\n\t\treturn false\n\n\tsetTitle: (line1, line2) =>\n\t\t@elem.find(\".line-1\").text(line1)\n\t\t@elem.find(\".line-2\").text(line2)\n\n\tsetClosedNum: (num) =>\n\t\t@elem.find(\".closed-num\").text(num)\n\n\tsetAction: (title, func) =>\n\t\t@elem.find(\".button\").text(title).off(\"click\").on(\"click\", func)\n\n\n\nwindow.Infopanel = Infopanel\n"
  },
  {
    "path": "src/Ui/media/Loading.coffee",
    "content": "class Loading\n\tconstructor: (@wrapper) ->\n\t\tif window.show_loadingscreen then @showScreen()\n\t\t@timer_hide = null\n\t\t@timer_set = null\n\n\tsetProgress: (percent) ->\n\t\tif @timer_hide\n\t\t\tclearInterval @timer_hide\n\t\t@timer_set = RateLimit 500, ->\n\t\t\t$(\".progressbar\").css(\"transform\": \"scaleX(#{parseInt(percent*100)/100})\").css(\"opacity\", \"1\").css(\"display\", \"block\")\n\n\thideProgress: ->\n\t\t@log \"hideProgress\"\n\t\tif @timer_set\n\t\t\tclearInterval @timer_set\n\t\t@timer_hide = setTimeout ( =>\n\t\t\t$(\".progressbar\").css(\"transform\": \"scaleX(1)\").css(\"opacity\", \"0\").hideLater(1000)\n\t\t), 300\n\n\n\tshowScreen: ->\n\t\t$(\".loadingscreen\").css(\"display\", \"block\").addClassLater(\"ready\")\n\t\t@screen_visible = true\n\t\t@printLine \"&nbsp;&nbsp;&nbsp;Connecting...\"\n\n\n\tshowTooLarge: (site_info) ->\n\t\t@log \"Displaying large site confirmation\"\n\t\tif $(\".console .button-setlimit\").length == 0 # Not displaying it yet\n\t\t\tline = @printLine(\"Site size: <b>#{parseInt(site_info.settings.size/1024/1024)}MB</b> is larger than default allowed #{parseInt(site_info.size_limit)}MB\", \"warning\")\n\t\t\tbutton = $(\"<a href='#Set+limit' class='button button-setlimit'>\" + \"Open site and set size limit to #{site_info.next_size_limit}MB\" + \"</a>\")\n\t\t\tbutton.on \"click\", =>\n\t\t\t\tbutton.addClass(\"loading\")\n\t\t\t\treturn @wrapper.setSizeLimit(site_info.next_size_limit)\n\t\t\tline.after(button)\n\t\t\tsetTimeout (=>\n\t\t\t\t@printLine('Ready.')\n\t\t\t), 100\n\n\tshowTrackerTorBridge: (server_info) ->\n\t\tif $(\".console .button-settrackerbridge\").length == 0 and not server_info.tor_use_meek_bridges\n\t\t\tline = @printLine(\"Tracker connection error detected.\", \"error\")\n\t\t\tbutton = $(\"<a href='#Enable+Tor+bridges' class='button button-settrackerbridge'>\" + \"Use Tor meek bridges for tracker connections\" + \"</a>\")\n\t\t\tbutton.on \"click\", =>\n\t\t\t\tbutton.addClass(\"loading\")\n\t\t\t\t@wrapper.ws.cmd \"configSet\", [\"tor_use_bridges\", \"\"]\n\t\t\t\t@wrapper.ws.cmd \"configSet\", [\"trackers_proxy\", \"tor\"]\n\t\t\t\t@wrapper.ws.cmd \"siteUpdate\", {address: @wrapper.site_info.address, announce: true}\n\t\t\t\t@wrapper.reloadIframe()\n\t\t\t\treturn false\n\t\t\tline.after(button)\n\t\t\tif not server_info.tor_has_meek_bridges\n\t\t\t\tbutton.addClass(\"disabled\")\n\t\t\t\t@printLine(\"No meek bridge support in your client, please <a href='https://github.com/HelloZeroNet/ZeroNet#how-to-join'>download the latest bundle</a>.\", \"warning\")\n\n\t# We dont need loadingscreen anymore\n\thideScreen: ->\n\t\t@log \"hideScreen\"\n\t\tif not $(\".loadingscreen\").hasClass(\"done\") # Only if its not animating already\n\t\t\tif @screen_visible # Hide with animate\n\t\t\t\t$(\".loadingscreen\").addClass(\"done\").removeLater(2000)\n\t\t\telse # Not visible, just remove\n\t\t\t\t$(\".loadingscreen\").remove()\n\t\t@screen_visible = false\n\n\n\t# Append text to last line of loadingscreen\n\tprint: (text, type=\"normal\") ->\n\t\tif not @screen_visible then return false\n\t\t$(\".loadingscreen .console .cursor\").remove() # Remove previous cursor\n\t\tlast_line = $(\".loadingscreen .console .console-line:last-child\")\n\t\tif type == \"error\" then text = \"<span class='console-error'>#{text}</span>\"\n\t\tlast_line.html(last_line.html()+text)\n\n\n\t# Add line to loading screen\n\tprintLine: (text, type=\"normal\") ->\n\t\tif not @screen_visible then return false\n\t\t$(\".loadingscreen .console .cursor\").remove() # Remove previous cursor\n\t\tif type == \"error\" then text = \"<span class='console-error'>#{text}</span>\" else text = text+\"<span class='cursor'> </span>\"\n\n\t\tline = $(\"<div class='console-line'>#{text}</div>\").appendTo(\".loadingscreen .console\")\n\t\tif type == \"warning\" then line.addClass(\"console-warning\")\n\t\treturn line\n\n\tlog: (args...) ->\n\t\tconsole.log \"[Loading]\", args...\n\n\nwindow.Loading = Loading\n"
  },
  {
    "path": "src/Ui/media/Notifications.coffee",
    "content": "class Notifications\n\tconstructor: (@elem) ->\n\t\t@\n\n\ttest: ->\n\t\tsetTimeout (=>\n\t\t\t@add(\"connection\", \"error\", \"Connection lost to <b>UiServer</b> on <b>localhost</b>!\")\n\t\t\t@add(\"message-Anyone\", \"info\", \"New  from <b>Anyone</b>.\")\n\t\t), 1000\n\t\tsetTimeout (=>\n\t\t\t@add(\"connection\", \"done\", \"<b>UiServer</b> connection recovered.\", 5000)\n\t\t), 3000\n\n\n\tadd: (id, type, body, timeout=0) ->\n\t\tid = id.replace /[^A-Za-z0-9-]/g, \"\"\n\t\t# Close notifications with same id\n\t\tfor elem in $(\".notification-#{id}\")\n\t\t\t@close $(elem)\n\n\t\t# Create element\n\t\telem = $(\".notification.template\", @elem).clone().removeClass(\"template\")\n\t\telem.addClass(\"notification-#{type}\").addClass(\"notification-#{id}\")\n\t\tif type == \"progress\"\n\t\t\telem.addClass(\"notification-done\")\n\n\t\t# Update text\n\t\tif type == \"error\"\n\t\t\t$(\".notification-icon\", elem).html(\"!\")\n\t\telse if type == \"done\"\n\t\t\t$(\".notification-icon\", elem).html(\"<div class='icon-success'></div>\")\n\t\telse if type == \"progress\"\n\t\t\t$(\".notification-icon\", elem).html(\"<div class='icon-success'></div>\")\n\t\telse if type == \"ask\"\n\t\t\t$(\".notification-icon\", elem).html(\"?\")\n\t\telse\n\t\t\t$(\".notification-icon\", elem).html(\"i\")\n\n\t\tif typeof(body) == \"string\"\n\t\t\t$(\".body\", elem).html(\"<div class='message'><span class='multiline'>\"+body+\"</span></div>\")\n\t\telse\n\t\t\t$(\".body\", elem).html(\"\").append(body)\n\n\t\telem.appendTo(@elem)\n\n\t\t# Timeout\n\t\tif timeout\n\t\t\t$(\".close\", elem).remove() # No need of close button\n\t\t\tsetTimeout (=>\n\t\t\t\t@close elem\n\t\t\t), timeout\n\n\t\t# Animate\n\t\twidth = Math.min(elem.outerWidth() + 50, 580)\n\t\tif not timeout then width += 20 # Add space for close button\n\t\tif elem.outerHeight() > 55 then elem.addClass(\"long\")\n\t\telem.css({\"width\": \"50px\", \"transform\": \"scale(0.01)\"})\n\t\telem.animate({\"scale\": 1}, 800, \"easeOutElastic\")\n\t\telem.animate({\"width\": width}, 700, \"easeInOutCubic\")\n\t\t$(\".body\", elem).css(\"width\": (width - 50))\n\t\t$(\".body\", elem).cssLater(\"box-shadow\", \"0px 0px 5px rgba(0,0,0,0.1)\", 1000)\n\n\t\t# Close button or Confirm button\n\t\t$(\".close, .button\", elem).on \"click\", =>\n\t\t\t@close elem\n\t\t\treturn false\n\n\t\t# Select list\n\t\t$(\".select\", elem).on \"click\", =>\n\t\t\t@close elem\n\n\t\t# Input enter\n\t\t$(\"input\", elem).on \"keyup\", (e) =>\n\t\t\tif e.keyCode == 13\n\t\t\t\t@close elem\n\n\t\treturn elem\n\n\n\tclose: (elem) ->\n\t\telem.stop().animate {\"width\": 0, \"opacity\": 0}, 700, \"easeInOutCubic\"\n\t\telem.slideUp 300, (-> elem.remove())\n\n\n\tlog: (args...) ->\n\t\tconsole.log \"[Notifications]\", args...\n\n\nwindow.Notifications = Notifications\n"
  },
  {
    "path": "src/Ui/media/Wrapper.coffee",
    "content": "class Wrapper\n\tconstructor: (ws_url) ->\n\t\t@log \"Created!\"\n\n\t\t@loading = new Loading(@)\n\t\t@notifications = new Notifications($(\".notifications\"))\n\t\t@infopanel = new Infopanel($(\".infopanel\"))\n\t\t@infopanel.onClosed = =>\n\t\t\t@ws.cmd(\"siteSetSettingsValue\", [\"modified_files_notification\", false])\n\t\t@infopanel.onOpened = =>\n\t\t\t@ws.cmd(\"siteSetSettingsValue\", [\"modified_files_notification\", true])\n\t\t@fixbutton = new Fixbutton()\n\n\t\twindow.addEventListener(\"message\", @onMessageInner, false)\n\t\t@inner = document.getElementById(\"inner-iframe\").contentWindow\n\t\t@ws = new ZeroWebsocket(ws_url)\n\t\t@ws.next_message_id = 1000000 # Avoid messageid collision :)\n\t\t@ws.onOpen = @onOpenWebsocket\n\t\t@ws.onClose = @onCloseWebsocket\n\t\t@ws.onMessage = @onMessageWebsocket\n\t\t@ws.connect()\n\t\t@ws_error = null # Ws error message\n\n\t\t@next_cmd_message_id = -1\n\n\t\t@site_info = null # Hold latest site info\n\t\t@server_info = null # Hold latest server info\n\t\t@event_site_info =  $.Deferred() # Event when site_info received\n\t\t@inner_loaded = false # If iframe loaded or not\n\t\t@inner_ready = false # Inner frame ready to receive messages\n\t\t@wrapperWsInited = false # Wrapper notified on websocket open\n\t\t@site_error = null # Latest failed file download\n\t\t@address = null\n\t\t@opener_tested = false\n\t\t@announcer_line = null\n\t\t@web_notifications = {}\n\t\t@is_title_changed = false\n\n\t\t@allowed_event_constructors = [window.MouseEvent, window.KeyboardEvent, window.PointerEvent] # Allowed event constructors\n\n\t\twindow.onload = @onPageLoad # On iframe loaded\n\t\twindow.onhashchange = (e) => # On hash change\n\t\t\t@log \"Hashchange\", window.location.hash\n\t\t\tif window.location.hash\n\t\t\t\tsrc = $(\"#inner-iframe\").attr(\"src\").replace(/#.*/, \"\")+window.location.hash\n\t\t\t\t$(\"#inner-iframe\").attr(\"src\", src)\n\n\t\twindow.onpopstate = (e) =>\n\t\t\t@sendInner {\"cmd\": \"wrapperPopState\", \"params\": {\"href\": document.location.href, \"state\": e.state}}\n\n\t\t$(\"#inner-iframe\").focus()\n\n\n\tverifyEvent: (allowed_target, e) =>\n\t\tif not e.originalEvent.isTrusted\n\t\t\tthrow \"Event not trusted\"\n\n\t\tif e.originalEvent.constructor not in @allowed_event_constructors\n\t\t\tthrow \"Invalid event constructor: #{e.constructor} not in #{JSON.stringify(@allowed_event_constructors)}\"\n\n\t\tif e.originalEvent.currentTarget != allowed_target[0]\n\t\t\tthrow \"Invalid event target: #{e.originalEvent.currentTarget} != #{allowed_target[0]}\"\n\n\t# Incoming message from UiServer websocket\n\tonMessageWebsocket: (e) =>\n\t\tmessage = JSON.parse(e.data)\n\t\t@handleMessageWebsocket(message)\n\n\thandleMessageWebsocket: (message) =>\n\t\tcmd = message.cmd\n\t\tif cmd == \"response\"\n\t\t\tif @ws.waiting_cb[message.to]? # We are waiting for response\n\t\t\t\t@ws.waiting_cb[message.to](message.result)\n\t\t\telse\n\t\t\t\t@sendInner message # Pass message to inner frame\n\t\telse if cmd == \"notification\" # Display notification\n\t\t\ttype = message.params[0]\n\t\t\tid = \"notification-ws-#{message.id}\"\n\t\t\tif \"-\" in message.params[0]  # - in first param: message id defined\n\t\t\t\t[id, type] = message.params[0].split(\"-\")\n\t\t\t@notifications.add(id, type, message.params[1], message.params[2])\n\t\telse if cmd == \"progress\" # Display notification\n\t\t\t@actionProgress(message)\n\t\telse if cmd == \"prompt\" # Prompt input\n\t\t\t@displayPrompt message.params[0], message.params[1], message.params[2], message.params[3], (res) =>\n\t\t\t\t@ws.response message.id, res\n\t\telse if cmd == \"confirm\" # Confirm action\n\t\t\t@displayConfirm message.params[0], message.params[1], (res) =>\n\t\t\t\t@ws.response message.id, res\n\t\telse if cmd == \"setSiteInfo\"\n\t\t\t@sendInner message # Pass to inner frame\n\t\t\tif message.params.address == @address # Current page\n\t\t\t\t@setSiteInfo message.params\n\t\t\t@updateProgress message.params\n\t\telse if cmd == \"setAnnouncerInfo\"\n\t\t\t@sendInner message # Pass to inner frame\n\t\t\tif message.params.address == @address # Current page\n\t\t\t\t@setAnnouncerInfo message.params\n\t\t\t@updateProgress message.params\n\t\telse if cmd == \"error\"\n\t\t\t@notifications.add(\"notification-#{message.id}\", \"error\", message.params, 0)\n\t\telse if cmd == \"updating\" # Close connection\n\t\t\t@log \"Updating: Closing websocket\"\n\t\t\t@ws.ws.close()\n\t\t\t@ws.onCloseWebsocket(null, 4000)\n\t\telse if cmd == \"redirect\"\n\t\t\twindow.top.location = message.params\n\t\telse if cmd == \"injectHtml\"\n\t\t\t$(\"body\").append(message.params)\n\t\telse if cmd == \"injectScript\"\n\t\t\tscript_tag = $(\"<script>\")\n\t\t\tscript_tag.attr(\"nonce\", @script_nonce)\n\t\t\tscript_tag.html(message.params)\n\t\t\tdocument.head.appendChild(script_tag[0])\n\t\telse\n\t\t\t@sendInner message # Pass message to inner frame\n\n\t# Incoming message from inner frame\n\tonMessageInner: (e) =>\n\t\t# No nonce security enabled, test if window opener present\n\t\tif not window.postmessage_nonce_security and @opener_tested == false\n\t\t\tif window.opener and window.opener != window\n\t\t\t\t@log \"Opener present\", window.opener\n\t\t\t\t@displayOpenerDialog()\n\t\t\t\treturn false\n\t\t\telse\n\t\t\t\t@opener_tested = true\n\n\t\tmessage = e.data\n\t\t# Invalid message (probably not for us)\n\t\tif not message.cmd\n\t\t\t@log \"Invalid message:\", message\n\t\t\treturn false\n\n\t\t# Test nonce security to avoid third-party messages\n\t\tif window.postmessage_nonce_security and message.wrapper_nonce != window.wrapper_nonce\n\t\t\t@log \"Message nonce error:\", message.wrapper_nonce, '!=', window.wrapper_nonce\n\t\t\treturn\n\n\t\t@handleMessage message\n\n\tcmd: (cmd, params={}, cb=null) =>\n\t\tmessage = {}\n\t\tmessage.cmd = cmd\n\t\tmessage.params = params\n\t\tmessage.id = @next_cmd_message_id\n\t\tif cb\n\t\t\t@ws.waiting_cb[message.id] = cb\n\t\t@next_cmd_message_id -= 1\n\n\t\t@handleMessage(message)\n\n\thandleMessage: (message) =>\n\t\tcmd = message.cmd\n\t\tif cmd == \"innerReady\"\n\t\t\t@inner_ready = true\n\t\t\tif @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened\n\t\t\t\t@sendInner {\"cmd\": \"wrapperOpenedWebsocket\"}\n\t\t\t\t@wrapperWsInited = true\n\t\telse if cmd == \"innerLoaded\" or cmd == \"wrapperInnerLoaded\"\n\t\t\tif window.location.hash\n\t\t\t\t$(\"#inner-iframe\")[0].src += window.location.hash # Hash tag\n\t\t\t\t@log \"Added hash to location\", $(\"#inner-iframe\")[0].src\n\t\telse if cmd == \"wrapperNotification\" # Display notification\n\t\t\t@actionNotification(message)\n\t\telse if cmd == \"wrapperConfirm\" # Display confirm message\n\t\t\t@actionConfirm(message)\n\t\telse if cmd == \"wrapperPrompt\" # Prompt input\n\t\t\t@actionPrompt(message)\n\t\telse if cmd == \"wrapperProgress\" # Progress bar\n\t\t\t@actionProgress(message)\n\t\telse if cmd == \"wrapperSetViewport\" # Set the viewport\n\t\t\t@actionSetViewport(message)\n\t\telse if cmd == \"wrapperSetTitle\"\n\t\t\t@log \"wrapperSetTitle\", message.params\n\t\t\t$(\"head title\").text(message.params)\n\t\t\t@is_title_changed = true\n\t\telse if cmd == \"wrapperReload\" # Reload current page\n\t\t\t@actionReload(message)\n\t\telse if cmd == \"wrapperGetLocalStorage\"\n\t\t\t@actionGetLocalStorage(message)\n\t\telse if cmd == \"wrapperSetLocalStorage\"\n\t\t\t@actionSetLocalStorage(message)\n\t\telse if cmd == \"wrapperPushState\"\n\t\t\tquery = @toRelativeQuery(message.params[2])\n\t\t\twindow.history.pushState(message.params[0], message.params[1], query)\n\t\telse if cmd == \"wrapperReplaceState\"\n\t\t\tquery = @toRelativeQuery(message.params[2])\n\t\t\twindow.history.replaceState(message.params[0], message.params[1], query)\n\t\telse if cmd == \"wrapperGetState\"\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": window.history.state}\n\t\telse if cmd == \"wrapperGetAjaxKey\"\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": window.ajax_key}\n\t\telse if cmd == \"wrapperOpenWindow\"\n\t\t\t@actionOpenWindow(message.params)\n\t\telse if cmd == \"wrapperPermissionAdd\"\n\t\t\t@actionPermissionAdd(message)\n\t\telse if cmd == \"wrapperRequestFullscreen\"\n\t\t\t@actionRequestFullscreen()\n\t\telse if cmd == \"wrapperWebNotification\"\n\t\t\t@actionWebNotification(message)\n\t\telse if cmd == \"wrapperCloseWebNotification\"\n\t\t\t@actionCloseWebNotification(message)\n\t\telse # Send to websocket\n\t\t\tif message.id < 1000000\n\t\t\t\tif message.cmd == \"fileWrite\" and not @modified_panel_updater_timer and site_info?.settings?.own\n\t\t\t\t\t@modified_panel_updater_timer = setTimeout ( => @updateModifiedPanel(); @modified_panel_updater_timer = null ), 1000\n\t\t\t\t@ws.send(message) # Pass message to websocket\n\t\t\telse\n\t\t\t\t@log \"Invalid inner message id\"\n\n\ttoRelativeQuery: (query=null) ->\n\t\tif query == null\n\t\t\tquery = window.location.search\n\t\tback = window.location.pathname\n\t\tif back.match /^\\/[^\\/]+$/ # Add / after site address if called without it\n\t\t\tback += \"/\"\n\t\tif query.startsWith(\"#\")\n\t\t\tback = query\n\t\telse if query.replace(\"?\", \"\")\n\t\t\tback += \"?\"+query.replace(\"?\", \"\")\n\t\treturn back\n\n\n\tdisplayOpenerDialog: ->\n\t\telem = $(\"<div class='opener-overlay'><div class='dialog'>You have opened this page by clicking on a link. Please, confirm if you want to load this site.<a href='?' target='_blank' class='button'>Open site</a></div></div>\")\n\t\telem.find('a').on \"click\", ->\n\t\t\twindow.open(\"?\", \"_blank\")\n\t\t\twindow.close()\n\t\t\treturn false\n\t\t$(\"body\").prepend(elem)\n\n\t# - Actions -\n\n\tactionOpenWindow: (params) ->\n\t\tif typeof(params) == \"string\"\n\t\t\tw = window.open()\n\t\t\tw.opener = null\n\t\t\tw.location = params\n\t\telse\n\t\t\tw = window.open(null, params[1], params[2])\n\t\t\tw.opener = null\n\t\t\tw.location = params[0]\n\n\tactionRequestFullscreen: ->\n\t\telem = document.getElementById(\"inner-iframe\")\n\t\trequest_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen\n\t\trequest_fullscreen.call(elem)\n\n\tactionWebNotification: (message) ->\n\t\t$.when(@event_site_info).done =>\n\t\t\t# Check that the wrapper may send notifications\n\t\t\tif Notification.permission == \"granted\"\n\t\t\t\t@displayWebNotification message\n\t\t\telse if Notification.permission == \"denied\"\n\t\t\t\tres = {\"error\": \"Web notifications are disabled by the user\"}\n\t\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": res}\n\t\t\telse\n\t\t\t\tNotification.requestPermission().then (permission) =>\n\t\t\t\t\tif permission == \"granted\"\n\t\t\t\t\t\t@displayWebNotification message\n\n\tactionCloseWebNotification: (message) ->\n\t\t$.when(@event_site_info).done =>\n\t\t\tid = message.params[0]\n\t\t\t@web_notifications[id].close()\n\n\tdisplayWebNotification: (message) ->\n\t\ttitle = message.params[0]\n\t\tid = message.params[1]\n\t\toptions = message.params[2]\n\t\tnotification = new Notification(title, options)\n\t\t@web_notifications[id] = notification\n\t\tnotification.onshow = () =>\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": \"ok\"}\n\t\tnotification.onclick = (e) =>\n\t\t\tif not options.focus_tab\n\t\t\t\te.preventDefault()\n\t\t\t@sendInner {\"cmd\": \"webNotificationClick\", \"params\": {\"id\": id}}\n\t\tnotification.onclose = () =>\n\t\t\t@sendInner {\"cmd\": \"webNotificationClose\", \"params\": {\"id\": id}}\n\t\t\tdelete @web_notifications[id]\n\n\tactionPermissionAdd: (message) ->\n\t\tpermission = message.params\n\t\t$.when(@event_site_info).done =>\n\t\t\tif permission in @site_info.settings.permissions\n\t\t\t\treturn false\n\t\t\t@ws.cmd \"permissionDetails\", permission, (permission_details) =>\n\t\t\t\t@displayConfirm \"This site requests permission:\" + \" <b>#{@toHtmlSafe(permission)}</b>\" + \"<br><small style='color: #4F4F4F'>#{permission_details}</small>\", \"Grant\", =>\n\t\t\t\t\t@ws.cmd \"permissionAdd\", permission, (res) =>\n\t\t\t\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": res}\n\n\tactionNotification: (message) ->\n\t\tmessage.params = @toHtmlSafe(message.params) # Escape html\n\t\tbody =  $(\"<span class='message'>\"+message.params[1]+\"</span>\")\n\t\t@notifications.add(\"notification-#{message.id}\", message.params[0], body, message.params[2])\n\n\tdisplayConfirm: (body, captions, cb) ->\n\t\tbody = $(\"<span class='message-outer'><span class='message'>\"+body+\"</span></span>\")\n\t\tbuttons = $(\"<span class='buttons'></span>\")\n\t\tif captions not instanceof Array then captions = [captions]  # Convert to list if necessary\n\t\tfor caption, i in captions\n\t\t\tbutton = $(\"<a></a>\", {href: \"#\" + caption, class: \"button button-confirm button-#{caption} button-#{i+1}\", \"data-value\": i + 1})  # Add confirm button\n\t\t\tbutton.text(caption)\n\t\t\t((button) =>\n\t\t\t\tbutton.on \"click\", (e) =>\n\t\t\t\t\t@verifyEvent button, e\n\t\t\t\t\tcb(parseInt(e.currentTarget.dataset.value))\n\t\t\t\t\treturn false\n\t\t\t)(button)\n\t\t\tbuttons.append(button)\n\t\tbody.append(buttons)\n\t\t@notifications.add(\"notification-#{caption}\", \"ask\", body)\n\n\t\tbuttons.first().focus()\n\t\t$(\".notification\").scrollLeft(0)\n\n\n\tactionConfirm: (message, cb=false) ->\n\t\tmessage.params = @toHtmlSafe(message.params) # Escape html\n\t\tif message.params[1] then caption = message.params[1] else caption = \"ok\"\n\t\t@displayConfirm message.params[0], caption, (res) =>\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": res} # Response to confirm\n\t\t\treturn false\n\n\n\tdisplayPrompt: (message, type, caption, placeholder, cb) ->\n\t\tbody = $(\"<span class='message'></span>\").html(message)\n\t\tplaceholder ?= \"\"\n\n\t\tinput = $(\"<input/>\", {type: type, class: \"input button-#{type}\", placeholder: placeholder}) # Add input\n\t\tinput.on \"keyup\", (e) => # Send on enter\n\t\t\t@verifyEvent input, e\n\t\t\tif e.keyCode == 13\n\t\t\t\tcb input.val() # Response to confirm\n\t\tbody.append(input)\n\n\t\tbutton = $(\"<a></a>\", {href: \"#\" + caption, class: \"button button-#{caption}\"}).text(caption) # Add confirm button\n\t\tbutton.on \"click\", (e) => # Response on button click\n\t\t\t@verifyEvent button, e\n\t\t\tcb input.val()\n\t\t\treturn false\n\t\tbody.append(button)\n\n\t\t@notifications.add(\"notification-#{message.id}\", \"ask\", body)\n\n\t\tinput.focus()\n\t\t$(\".notification\").scrollLeft(0)\n\n\n\tactionPrompt: (message) ->\n\t\tmessage.params = @toHtmlSafe(message.params) # Escape html\n\t\tif message.params[1] then type = message.params[1] else type = \"text\"\n\t\tcaption = if message.params[2] then message.params[2] else \"OK\"\n\t\tif message.params[3]?\n\t\t\tplaceholder = message.params[3]\n\t\telse\n\t\t\tplaceholder = \"\"\n\n\t\t@displayPrompt message.params[0], type, caption, placeholder, (res) =>\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": res} # Response to confirm\n\n\tdisplayProgress: (type, body, percent) ->\n\t\tpercent = Math.min(100, percent)/100\n\t\toffset = 75-(percent*75)\n\t\tcircle = \"\"\"\n\t\t\t<div class=\"circle\"><svg class=\"circle-svg\" width=\"30\" height=\"30\" viewport=\"0 0 30 30\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n  \t\t\t\t<circle r=\"12\" cx=\"15\" cy=\"15\" fill=\"transparent\" class=\"circle-bg\"></circle>\n  \t\t\t\t<circle r=\"12\" cx=\"15\" cy=\"15\" fill=\"transparent\" class=\"circle-fg\" style=\"stroke-dashoffset: #{offset}\"></circle>\n\t\t\t</svg></div>\n\t\t\"\"\"\n\t\tbody = \"<span class='message'>\"+body+\"</span>\" + circle\n\t\telem = $(\".notification-#{type}\")\n\t\tif elem.length\n\t\t\twidth = $(\".body .message\", elem).outerWidth()\n\t\t\t$(\".body .message\", elem).html(body)\n\t\t\tif $(\".body .message\", elem).css(\"width\") == \"\"\n\t\t\t\t$(\".body .message\", elem).css(\"width\", width)\n\t\t\t$(\".body .circle-fg\", elem).css(\"stroke-dashoffset\", offset)\n\t\telse\n\t\t\telem = @notifications.add(type, \"progress\", $(body))\n\t\tif percent > 0\n\t\t\t$(\".body .circle-bg\", elem).css {\"animation-play-state\": \"paused\", \"stroke-dasharray\": \"180px\"}\n\n\t\tif $(\".notification-icon\", elem).data(\"done\")\n\t\t\treturn false\n\t\telse if percent >= 1  # Done\n\t\t\t$(\".circle-fg\", elem).css(\"transition\", \"all 0.3s ease-in-out\")\n\t\t\tsetTimeout (->\n\t\t\t\t$(\".notification-icon\", elem).css {transform: \"scale(1)\", opacity: 1}\n\t\t\t\t$(\".notification-icon .icon-success\", elem).css {transform: \"rotate(45deg) scale(1)\"}\n\t\t\t), 300\n\t\t\tsetTimeout (=>\n\t\t\t\t@notifications.close elem\n\t\t\t), 3000\n\t\t\t$(\".notification-icon\", elem).data(\"done\", true)\n\t\telse if percent < 0  # Error\n\t\t\t$(\".body .circle-fg\", elem).css(\"stroke\", \"#ec6f47\").css(\"transition\", \"transition: all 0.3s ease-in-out\")\n\t\t\tsetTimeout (=>\n\t\t\t\t$(\".notification-icon\", elem).css {transform: \"scale(1)\", opacity: 1}\n\t\t\t\telem.removeClass(\"notification-done\").addClass(\"notification-error\")\n\t\t\t\t$(\".notification-icon .icon-success\", elem).removeClass(\"icon-success\").html(\"!\")\n\t\t\t), 300\n\t\t\t$(\".notification-icon\", elem).data(\"done\", true)\n\n\n\tactionProgress: (message) ->\n\t\tmessage.params = @toHtmlSafe(message.params) # Escape html\n\t\t@displayProgress(message.params[0], message.params[1], message.params[2])\n\n\tactionSetViewport: (message) ->\n\t\t@log \"actionSetViewport\", message\n\t\tif $(\"#viewport\").length > 0\n\t\t\t$(\"#viewport\").attr(\"content\", @toHtmlSafe message.params)\n\t\telse\n\t\t\t$('<meta name=\"viewport\" id=\"viewport\">').attr(\"content\", @toHtmlSafe message.params).appendTo(\"head\")\n\n\tactionReload: (message) ->\n\t\t@reload(message.params[0])\n\n\treload: (url_post=\"\") ->\n\t\t@log \"Reload\"\n\t\tcurrent_url = window.location.toString().replace(/#.*/g, \"\")\n\t\tif url_post\n\t\t\tif current_url.indexOf(\"?\") > 0\n\t\t\t\twindow.location = current_url + \"&\" + url_post\n\t\t\telse\n\t\t\t\twindow.location = current_url + \"?\" + url_post\n\t\telse\n\t\t\twindow.location.reload()\n\n\n\tactionGetLocalStorage: (message) ->\n\t\t$.when(@event_site_info).done =>\n\t\t\tdata = localStorage.getItem \"site.#{@site_info.address}.#{@site_info.auth_address}\"\n\t\t\tif not data # Migrate from non auth_address based local storage\n\t\t\t\tdata = localStorage.getItem \"site.#{@site_info.address}\"\n\t\t\t\tif data\n\t\t\t\t\tlocalStorage.setItem \"site.#{@site_info.address}.#{@site_info.auth_address}\", data\n\t\t\t\t\tlocalStorage.removeItem \"site.#{@site_info.address}\"\n\t\t\t\t\t@log \"Migrated LocalStorage from global to auth_address based\"\n\t\t\tif data then data = JSON.parse(data)\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": data}\n\n\n\tactionSetLocalStorage: (message) ->\n\t\t$.when(@event_site_info).done =>\n\t\t\tback = localStorage.setItem \"site.#{@site_info.address}.#{@site_info.auth_address}\", JSON.stringify(message.params)\n\t\t\t@sendInner {\"cmd\": \"response\", \"to\": message.id, \"result\": back}\n\n\n\t# EOF actions\n\n\n\tonOpenWebsocket: (e) =>\n\t\tif window.show_loadingscreen   # Get info on modifications\n\t\t\t@ws.cmd \"channelJoin\", {\"channels\": [\"siteChanged\", \"serverChanged\", \"announcerChanged\"]}\n\t\telse\n\t\t\t@ws.cmd \"channelJoin\", {\"channels\": [\"siteChanged\", \"serverChanged\"]}\n\t\tif not @wrapperWsInited and @inner_ready\n\t\t\t@sendInner {\"cmd\": \"wrapperOpenedWebsocket\"} # Send to inner frame\n\t\t\t@wrapperWsInited = true\n\t\tif window.show_loadingscreen\n\t\t\t@ws.cmd \"serverInfo\", [], (server_info) =>\n\t\t\t\t@server_info = server_info\n\n\t\t\t@ws.cmd \"announcerInfo\", [], (announcer_info) =>\n\t\t\t\t@setAnnouncerInfo(announcer_info)\n\n\t\tif @inner_loaded # Update site info\n\t\t\t@reloadSiteInfo()\n\n\t\t# If inner frame not loaded for 2 sec show peer informations on loading screen by loading site info\n\t\tsetTimeout (=>\n\t\t\tif not @site_info then @reloadSiteInfo()\n\t\t), 2000\n\n\t\tif @ws_error\n\t\t\t@notifications.add(\"connection\", \"done\", \"Connection with <b>UiServer Websocket</b> recovered.\", 6000)\n\t\t\t@ws_error = null\n\n\n\tonCloseWebsocket: (e) =>\n\t\t@wrapperWsInited = false\n\t\tsetTimeout (=> # Wait a bit, maybe its page closing\n\t\t\t@sendInner {\"cmd\": \"wrapperClosedWebsocket\"} # Send to inner frame\n\t\t\tif e and e.code == 1000 and e.wasClean == false # Server error please reload page\n\t\t\t\t@ws_error = @notifications.add(\"connection\", \"error\", \"UiServer Websocket error, please reload the page.\")\n\t\t\telse if e and e.code == 1001 and e.wasClean == true  # Navigating to other page\n\t\t\t\treturn\n\t\t\telse if not @ws_error\n\t\t\t\t@ws_error = @notifications.add(\"connection\", \"error\", \"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\")\n\t\t), 1000\n\n\n\t# Iframe loaded\n\tonPageLoad: (e) =>\n\t\t@log \"onPageLoad\"\n\t\t@inner_loaded = true\n\t\tif not @inner_ready then @sendInner {\"cmd\": \"wrapperReady\"} # Inner frame loaded before wrapper\n\t\t#if not @site_error then @loading.hideScreen() # Hide loading screen\n\t\tif @ws.ws.readyState == 1 and not @site_info # Ws opened\n\t\t\t@reloadSiteInfo()\n\t\telse if @site_info and @site_info.content?.title? and not @is_title_changed\n\t\t\twindow.document.title = @site_info.content.title + \" - ZeroNet\"\n\t\t\t@log \"Setting title to\", window.document.title\n\n\tonWrapperLoad: =>\n\t\t@script_nonce = window.script_nonce\n\t\t@wrapper_key = window.wrapper_key\n\t\t# Cleanup secret variables\n\t\tdelete window.wrapper\n\t\tdelete window.wrapper_key\n\t\tdelete window.script_nonce\n\t\t$(\"#script_init\").remove()\n\n\t# Send message to innerframe\n\tsendInner: (message) ->\n\t\t@inner.postMessage(message, '*')\n\n\n\t# Get site info from UiServer\n\treloadSiteInfo: ->\n\t\tif @loading.screen_visible # Loading screen visible\n\t\t\tparams = {\"file_status\": window.file_inner_path} # Query the current required file status\n\t\telse\n\t\t\tparams = {}\n\n\t\t@ws.cmd \"siteInfo\", params, (site_info) =>\n\t\t\t@address = site_info.address\n\t\t\t@setSiteInfo site_info\n\n\t\t\tif site_info.settings.size > site_info.size_limit * 1024 * 1024 and not @loading.screen_visible  # Site size too large and not displaying it yet\n\t\t\t\t@displayConfirm \"Site is larger than allowed: #{(site_info.settings.size/1024/1024).toFixed(1)}MB/#{site_info.size_limit}MB\", \"Set limit to #{site_info.next_size_limit}MB\", =>\n\t\t\t\t\t@ws.cmd \"siteSetLimit\", [site_info.next_size_limit], (res) =>\n\t\t\t\t\t\tif res == \"ok\"\n\t\t\t\t\t\t\t@notifications.add(\"size_limit\", \"done\", \"Site storage limit modified!\", 5000)\n\n\t\t\tif site_info.content?.title? and not @is_title_changed\n\t\t\t\twindow.document.title = site_info.content.title + \" - ZeroNet\"\n\t\t\t\t@log \"Setting title to\", window.document.title\n\n\n\t# Got setSiteInfo from websocket UiServer\n\tsetSiteInfo: (site_info) ->\n\t\tif site_info.event? # If loading screen visible add event to it\n\t\t\t# File started downloading\n\t\t\tif site_info.event[0] == \"file_added\" and site_info.bad_files\n\t\t\t\t@loading.printLine(\"#{site_info.bad_files} files needs to be downloaded\")\n\t\t\t# File finished downloading\n\t\t\telse if site_info.event[0] == \"file_done\"\n\t\t\t\t@loading.printLine(\"#{site_info.event[1]} downloaded\")\n\t\t\t\tif site_info.event[1] == window.file_inner_path # File downloaded we currently on\n\t\t\t\t\t@loading.hideScreen()\n\t\t\t\t\tif not @site_info then @reloadSiteInfo()\n\t\t\t\t\tif site_info.content and not @is_title_changed\n\t\t\t\t\t\twindow.document.title = site_info.content.title + \" - ZeroNet\"\n\t\t\t\t\t\t@log \"Required file #{window.file_inner_path} done, setting title to\", window.document.title\n\t\t\t\t\tif not window.show_loadingscreen\n\t\t\t\t\t\t@notifications.add(\"modified\", \"info\", \"New version of this page has just released.<br>Reload to see the modified content.\")\n\t\t\t# File failed downloading\n\t\t\telse if site_info.event[0] == \"file_failed\"\n\t\t\t\t@site_error = site_info.event[1]\n\t\t\t\tif site_info.settings.size > site_info.size_limit*1024*1024 # Site size too large and not displaying it yet\n\t\t\t\t\t@loading.showTooLarge(site_info)\n\n\t\t\t\telse\n\t\t\t\t\t@loading.printLine(\"#{site_info.event[1]} download failed\", \"error\")\n\t\t\t# New peers found\n\t\t\telse if site_info.event[0] == \"peers_added\"\n\t\t\t\t@loading.printLine(\"Peers found: #{site_info.peers}\")\n\n\t\tif @loading.screen_visible and not @site_info # First site info display current peers\n\t\t\tif site_info.peers > 1\n\t\t\t\t@loading.printLine \"Peers found: #{site_info.peers}\"\n\t\t\telse\n\t\t\t\t@site_error = \"No peers found\"\n\t\t\t\t@loading.printLine \"No peers found\"\n\n\t\tif not @site_info and not @loading.screen_visible and $(\"#inner-iframe\").attr(\"src\").replace(\"?wrapper=False\", \"\").replace(/\\?wrapper_nonce=[A-Za-z0-9]+/, \"\").indexOf(\"?\") == -1 # First site info and we are on mainpage (does not have other parameter thatn wrapper)\n\t\t\tif site_info.size_limit*1.1 < site_info.next_size_limit # Need upgrade soon\n\t\t\t\t@displayConfirm \"Running out of size limit (#{(site_info.settings.size/1024/1024).toFixed(1)}MB/#{site_info.size_limit}MB)\", \"Set limit to #{site_info.next_size_limit}MB\", =>\n\t\t\t\t\t@ws.cmd \"siteSetLimit\", [site_info.next_size_limit], (res) =>\n\t\t\t\t\t\tif res == \"ok\"\n\t\t\t\t\t\t\t@notifications.add(\"size_limit\", \"done\", \"Site storage limit modified!\", 5000)\n\t\t\t\t\treturn false\n\n\t\tif @loading.screen_visible and @inner_loaded and site_info.settings.size < site_info.size_limit * 1024 * 1024 and site_info.settings.size > 0 # Loading screen still visible, but inner loaded\n\t\t\t@log \"Loading screen visible, but inner loaded\"\n\t\t\t@loading.hideScreen()\n\n\t\tif site_info?.settings?.own and site_info?.settings?.modified != @site_info?.settings?.modified\n\t\t\t@updateModifiedPanel()\n\n\t\tif @loading.screen_visible and site_info.settings.size > site_info.size_limit * 1024 * 1024\n\t\t\t@log \"Site too large\"\n\t\t\t@loading.showTooLarge(site_info)\n\n\t\t@site_info = site_info\n\t\t@event_site_info.resolve()\n\n\tsiteSign: (inner_path, cb) =>\n\t\tif @site_info.privatekey\n\t\t\t# Privatekey stored in users.json\n\t\t\t@infopanel.elem.find(\".button\").addClass(\"loading\")\n\t\t\t@ws.cmd \"siteSign\", {privatekey: \"stored\", inner_path: inner_path, update_changed_files: true}, (res) =>\n\t\t\t\tif res == \"ok\"\n\t\t\t\t\tcb?(true)\n\t\t\t\telse\n\t\t\t\t\tcb?(false)\n\t\t\t\t@infopanel.elem.find(\".button\").removeClass(\"loading\")\n\t\telse\n\t\t\t# Ask the user for privatekey\n\t\t\t@displayPrompt \"Enter your private key:\", \"password\", \"Sign\", \"\", (privatekey) => # Prompt the private key\n\t\t\t\t@infopanel.elem.find(\".button\").addClass(\"loading\")\n\t\t\t\t@ws.cmd \"siteSign\", {privatekey: privatekey, inner_path: inner_path, update_changed_files: true}, (res) =>\n\t\t\t\t\tif res == \"ok\"\n\t\t\t\t\t\tcb?(true)\n\t\t\t\t\telse\n\t\t\t\t\t\tcb?(false)\n\t\t\t\t\t@infopanel.elem.find(\".button\").removeClass(\"loading\")\n\n\tsitePublish: (inner_path) =>\n\t\t@ws.cmd \"sitePublish\", {\"inner_path\": inner_path, \"sign\": false}\n\n\tupdateModifiedPanel: =>\n\t\t@ws.cmd \"siteListModifiedFiles\", [], (res) =>\n\t\t\tnum = res.modified_files?.length\n\t\t\tif num > 0\n\t\t\t\tclosed = @site_info.settings.modified_files_notification == false\n\t\t\t\t@infopanel.show(closed)\n\t\t\telse\n\t\t\t\t@infopanel.hide()\n\n\t\t\tif num > 0\n\t\t\t\t@infopanel.setTitle(\n\t\t\t\t\t\"#{res.modified_files.length} modified file#{if num > 1 then 's' else ''}\",\n\t\t\t\t\tres.modified_files.join(\", \")\n\t\t\t\t)\n\t\t\t\t@infopanel.setClosedNum(num)\n\t\t\t\t@infopanel.setAction \"Sign & Publish\", =>\n\t\t\t\t\t@siteSign \"content.json\", (res) =>\n\t\t\t\t\t\tif (res)\n\t\t\t\t\t\t\t@notifications.add \"sign\", \"done\", \"content.json Signed!\", 5000\n\t\t\t\t\t\t\t@sitePublish(\"content.json\")\n\t\t\t\t\treturn false\n\t\t\t@log \"siteListModifiedFiles\", num, res\n\n\tsetAnnouncerInfo: (announcer_info) ->\n\t\tstatus_db = {announcing: [], error: [], announced: []}\n\t\tfor key, val of announcer_info.stats\n\t\t\tif val.status\n\t\t\t\tstatus_db[val.status].push(val)\n\t\tstatus_line = \"Trackers announcing: #{status_db.announcing.length}, error: #{status_db.error.length}, done: #{status_db.announced.length}\"\n\t\tif @announcer_line\n\t\t\t@announcer_line.text(status_line)\n\t\telse\n\t\t\t@announcer_line = @loading.printLine(status_line)\n\n\t\tif status_db.error.length > (status_db.announced.length + status_db.announcing.length) and status_db.announced.length < 3\n\t\t\t@loading.showTrackerTorBridge(@server_info)\n\n\tupdateProgress: (site_info) ->\n\t\tif site_info.tasks > 0 and site_info.started_task_num > 0\n\t\t\t@loading.setProgress 1-(Math.max(site_info.tasks, site_info.bad_files) / site_info.started_task_num)\n\t\telse\n\t\t\t@loading.hideProgress()\n\n\n\ttoHtmlSafe: (values) ->\n\t\tif values not instanceof Array then values = [values] # Convert to array if its not\n\t\tfor value, i in values\n\t\t\tif value instanceof Array\n\t\t\t\tvalue = @toHtmlSafe(value)\n\t\t\telse\n\t\t\t\tvalue = String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;') # Escape dangerous characters\n\t\t\t\tvalue = value.replace(/&lt;([\\/]{0,1}(br|b|u|i|small))&gt;/g, \"<$1>\") # Unescape b, i, u, br tags\n\t\t\tvalues[i] = value\n\t\treturn values\n\n\n\tsetSizeLimit: (size_limit, reload=true) =>\n\t\t@log \"setSizeLimit: #{size_limit}, reload: #{reload}\"\n\t\t@inner_loaded = false  # Inner frame not loaded, just a 404 page displayed\n\t\t@ws.cmd \"siteSetLimit\", [size_limit], (res) =>\n\t\t\tif res != \"ok\"\n\t\t\t\treturn false\n\t\t\t@loading.printLine res\n\t\t\t@inner_loaded = false\n\t\t\tif reload then @reloadIframe()\n\t\treturn false\n\n\treloadIframe: =>\n\t\tsrc = $(\"iframe\").attr(\"src\")\n\t\t@ws.cmd \"serverGetWrapperNonce\", [], (wrapper_nonce) =>\n\t\t\tsrc = src.replace(/wrapper_nonce=[A-Za-z0-9]+/, \"wrapper_nonce=\" + wrapper_nonce)\n\t\t\t@log \"Reloading iframe using url\", src\n\t\t\t$(\"iframe\").attr \"src\", src\n\n\tlog: (args...) ->\n\t\tconsole.log \"[Wrapper]\", args...\n\norigin = window.server_url or window.location.href.replace(/(\\:\\/\\/.*?)\\/.*/, \"$1\")\n\nif origin.indexOf(\"https:\") == 0\n\tproto = { ws: 'wss', http: 'https' }\nelse\n\tproto = { ws: 'ws', http: 'http' }\n\nws_url = proto.ws + \":\" + origin.replace(proto.http+\":\", \"\") + \"/ZeroNet-Internal/Websocket?wrapper_key=\" + window.wrapper_key\n\nwindow.wrapper = new Wrapper(ws_url)\n\n"
  },
  {
    "path": "src/Ui/media/Wrapper.css",
    "content": "body { margin: 0; padding: 0; height: 100%; background-color: #D2CECD; overflow: hidden }\nbody.back { background-color: #090909 }\na { color: black }\n\n.unsupported { text-align: center; z-index: 999; position: relative; margin: auto; width: 480px; background-color: white; padding: 20px; border-bottom: 2px solid #e74c3c; box-shadow: 0px 0px 15px #DDD; font-family: monospace; }\n.template { display: none !important }\n\n#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0;  } /*; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out*/\n#inner-iframe.back { transform: scale(0.95) translate(-300px, 0); opacity: 0.4 }\n\n.button {\n\tpadding: 5px 10px; margin-left: 10px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E;\n\tborder-radius: 2px; text-decoration: none; transition: all 0.5s; background-position: left center; white-space: nowrap;\n}\n.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; transition: none }\n.button:active { position: relative; top: 1px }\n.button:focus { outline: none }\n\n.button-Delete { background-color: #e74c3c; border-bottom-color: #c0392b; color: white }\n.button-Delete:hover { background-color: #FF5442; border-bottom-color: #8E2B21 }\n\n.button.loading {\n\tcolor: rgba(0,0,0,0); background: #999 url(img/loading.gif) no-repeat center center;\n\ttransition: all 0.5s ease-out ; pointer-events: none; border-bottom: 2px solid #666\n}\n.button.disabled { pointer-events: none; border-bottom: 2px solid #666; background-color: #999; opacity: 0.5 }\n.button.button-2 { background-color: transparent; border: 1px solid #EEE; color: #555 }\n.button.button-2:hover { border: 1px solid #CCC; color: #000 }\n\n/* Fixbutton */\n\n.fixbutton {\n\tposition: absolute; right: 35px; top: 15px; width: 40px;  z-index: 999;\n\ttext-align: center; color: white; font-family: Consolas, Monaco, monospace; font-size: 25px;\n}\n.fixbutton-bg {\n\tborder-radius: 80px; background-color: rgba(180, 180, 180, 0.5); cursor: pointer;\n\tdisplay: block; width: 80px; height: 80px; transition: background-color 0.2s, box-shadow 0.5s; transform: scale(0.6); margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */\n\t/*box-shadow: inset 105px 260px 0 -200px rgba(0,0,0,0.1);*/ /* box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); */\n}\n.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; backface-visibility: hidden; perspective: 1000px; line-height: 0; padding-top: 5px; opacity: 0.9 }\n.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px; font-size: 40px; line-height: 0; font-family: Verdana, sans-serif; margin-top: 17px }\n.fixbutton-bg:hover { background-color: #AF3BFF }\n.fixbutton-bg:active { background-color: #9E2FEA; top: 1px; transition: none }\n\n/* Notification */\n\n.notifications { position: absolute; top: 0; right: 80px; display: inline-block; z-index: 999; white-space: nowrap }\n.notification {\n\tposition: relative; float: right; clear: both; margin: 10px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden;\n\tperspective: 1000px; padding-bottom: 5px; color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif;\n\tfont-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/\n}\n.notification-icon {\n\tdisplay: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 2;\n\ttext-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;\n}\n.notification .body {\n\tpadding-left: 14px; padding-right: 60px; height: 50px; vertical-align: middle; display: table; padding-right: 20px; box-sizing: border-box;\n\tbackground-color: white; left: 50px; top: 0; position: relative; padding-top: 5px; padding-bottom: 5px;\n}\n.notification .message-outer { display: table-row }\n.notification .buttons { display: table-cell; vertical-align: top; padding-top: 9px; padding-right: 20px; text-align: right; }\n.notification.long .body { padding-top: 10px; padding-bottom: 10px }\n.notification .message { display: table-cell; vertical-align: middle; max-width: 500px; white-space: normal; }\n\n.notification.visible { max-width: 350px }\n\n.notification .close { position: absolute; top: 0; right: 0; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }\n.notification .close:hover { color: black }\n.notification .close:active, .notification .close:focus { color: #AF3BFF }\n.notification small { color: #AAA }\n.notification .multiline { white-space: normal; word-break: break-word; max-width: 300px; }\n.body-white .notification { box-shadow: 0 1px 9px rgba(0,0,0,0.1) }\n\n/* Notification select */\n.notification .select {\n\tdisplay: block; padding: 10px; margin-right: -32px; text-decoration: none; border-left: 3px solid #EEE;\n\tmargin-top: 1px; transition: all 0.3s; color: #666\n}\n.notification .select:hover, .notification .select.active { background-color: #007AFF; border-left: 3px solid #5D68FF; color: white; transition: none }\n.notification .select:active, .notification .select:focus { background-color: #3396FF; color: white; transition: none; border-left-color: #3396FF }\n.notification .select.disabled { opacity: 0.5; pointer-events: none }\n.notification .select small { color: inherit; }\n\n/* Notification types */\n.notification-ask .notification-icon { background-color: #f39c12; }\n.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }\n.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }\n\n/* Notification input */\n.notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; border-radius: 1px; margin-right: -11px; transition: all 0.3s }\n.notification .input:focus { border-color: #95a5a6; outline: none }\n\n/* Notification progress */\n.notification .circle { width: 50px; height: 50px; position: absolute; left: -50px; top: 0px; background-color: #e2e9ec; z-index: 1; background: linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef); }\n.notification .circle-svg { margin-left: 10px; margin-top: 10px; transform: rotateZ(-90deg); }\n.notification .circle-bg { stroke: #FFF; stroke-width: 2px; animation: rolling 0.4s infinite linear; stroke-dasharray: 40px; transition: all 1s }\n.notification .circle-fg { stroke-dashoffset: 200; stroke: #2ecc71; stroke-width: 2px; stroke-dasharray: 75px; transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); }\n.notification-progress .notification-icon { opacity: 0; transform: scale(0); transition: all 0.3s ease-in-out }\n.notification-progress .icon-success { transform: rotate(45deg) scale(0); transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); }\n@keyframes rolling {\n    0%   { stroke-dashoffset: 80px }\n    100% { stroke-dashoffset: 0px }\n}\n\n/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */\n\n.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }\n\n\n/* Infopanel */\n.infopanel-container { width: 100%; height: 100%; overflow: hidden; position: absolute; display: none; }\n.infopanel-container.visible { display: block; }\n.infopanel {\n\tposition: absolute; z-index: 999; padding: 15px 15px; bottom: 25px; right: 50px; border: 1px solid #eff3fe;\n\tfont-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; box-shadow: 0px 10px 55px rgba(58, 39, 176, 0.17);\n\tbackground-color: white; border-left: 4px solid #9a61f8; border-top-left-radius: 4px; border-bottom-left-radius: 4px;\n\ttransition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1);\n}\n.infopanel.closed { box-shadow: none; transform: translateX(100%); right: 0px; cursor: pointer; }\n.infopanel .message { font-size: 13px; line-height: 15px; display: inline-block; vertical-align: -9px; }\n.infopanel .message .line { max-width: 200px; display: inline-block; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }\n.infopanel .message .line-1 { font-weight: bold; }\n.infopanel .close { font-size: 16px; text-decoration: none; color: #AAA; padding: 5px; margin-right: -12px; vertical-align: 1px; display: inline-block; }\n.infopanel .close:hover { color: black }\n.infopanel .close:active, .infopanel .close:focus { color: #AF3BFF }\n.infopanel.closed .closed-num { opacity: 1; margin-left: -36px; pointer-events: inherit; }\n.infopanel .closed-num {\n\tposition: absolute; margin-top: 6px; background-color: #6666663d; color: white; width: 10px; text-align: center;\n\tpadding: 4px; border-top-left-radius: 6px; border-bottom-left-radius: 6px; font-size: 10px;\n    opacity: 0; margin-left: 0px; pointer-events: none; transition: all 0.6s;\n}\n.infopanel.unfolded .message .line { overflow: visible; white-space: normal; }\n.body-sidebar .infopanel { right: 425px; }\n.body-sidebar .infopanel.closed { right: 0px; }\n\n/* Loading screen */\n\n.loadingscreen { width: 100%; height: 100%; position: absolute; background-color: #EEE; z-index: 1; overflow: auto; display: none }\n.theme-dark .loadingscreen { background-color: #180922; }\n.loading-text { text-align: center; vertical-align: middle; top: 50%; position: absolute; margin-top: 39px; width: 100% }\n.loading-config {\n\tmargin: 20px; display: inline-block; text-transform: uppercase; font-family: Consolas, monospace; position: relative;;\n\ttext-decoration: none; letter-spacing: 1px; font-size: 12px; border-bottom: 1px solid #999; top: -60px; transition: all 1s cubic-bezier(1, 0, 0, 1); transition-delay: 0.3s;\n}\n.loading-config:hover { border-bottom-color: #000; transition: none; }\n.theme-dark .loading-config { color: white }\n.loadingscreen.ready .loading-config { top: 0px; }\n\n\n/* Loading console */\n.loadingscreen .console { line-height: 24px; font-family: monospace; font-size: 14px; color: #ADADAD; text-transform: uppercase; opacity: 0; transform: translateY(-20px); }\n.loadingscreen .console-line:last-child { color: #6C6767 }\n.loadingscreen .console .cursor {\n\tbackground-color: #999; color: #999; animation: pulse 1.5s infinite ease-in-out; margin-right: -9px;\n\tdisplay: inline-block; width: 9px; height: 19px; vertical-align: -4px;\n}\n.loadingscreen .console .console-error { color: #e74c3c; font-weight: bold; animation: pulse 2s infinite linear }\n.loadingscreen .console .console-warning { color: #8e44ad; }\n.loadingscreen .console .button { margin: 20px; display: inline-block; text-transform: none; padding: 10px 20px }\n\n\n/* Flipper loading anim */\n.flipper-container { width: 40px; height: 40px; position: absolute; top: 0%; left: 50%; transform: translate3d(-50%, -50%, 0); perspective: 1200; opacity: 0 }\n.flipper { position: relative; display: block; height: inherit; width: inherit; animation: flip 1.2s infinite ease-in-out; -webkit-transform-style: preserve-3d; }\n.flipper .front, .flipper .back {\n\tposition: absolute; top: 0; left: 0; backface-visibility: hidden; /*transform-style: preserve-3d;*/ display: block;\n\tbackground-color: #d50000; height: 100%; width: 100%; /* outline: 1px solid transparent; /* FF AA fix */\n}\n.flipper .back { background-color: white; z-index: 800; transform: rotateY(-180deg) }\n\n/* Loading ready */\n.loadingscreen.ready .console { opacity: 1; transform: translateY(0px); transition: all 0.3s }\n.loadingscreen.ready .flipper-container { top: 50%; opacity: 1; transition: all 1s cubic-bezier(1, 0, 0, 1); }\n\n\n/* Loading done */\n.loadingscreen.done { height: 0%; transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); }\n.loadingscreen.done .console { transform: translateY(300px); opacity: 0; transition: all 1.5s }\n.loadingscreen.done .flipper-container { opacity: 0; transition: all 1.5s }\n\n\n.progressbar {\n\tbackground: #26C281; position: fixed; width: 100%; z-index: 100; top: 0; left: 0; transform: scaleX(0); transform-origin: 0% 0%; transform:translate3d(0,0,0);\n\theight: 2px; transition: transform 1s, opacity 1s; display: none; backface-visibility: hidden; transform-style: preserve-3d;\n}\n.progressbar .peg {\n\tdisplay: block; position: absolute; right: 0; width: 100px; height: 100%;\n\tbox-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; opacity: 1.0; transform: rotate(3deg) translate(0px, -4px);\n}\n\n/* Opener overlay */\n.opener-overlay { position: fixed; z-index: 9999; width: 100%; text-align: center; background-color: rgba(100,100,100,0.5); height: 100%; vertical-align: middle; }\n.opener-overlay .dialog { background-color: white; padding: 40px; display: inline-block; color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; }\n\n/* Icons */\n.icon-profile { font-size: 6px; top: 0em; border-radius: 0.7em 0.7em 0 0; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }\n.icon-profile::before { position: absolute; content: \"\"; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; border-radius: 50%; background: #FFFFFF }\n\n/* Animations */\n\n@keyframes flip {\n  0%   { transform: perspective(120px) rotateX(0deg) rotateY(0deg); }\n  50%  { transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }\n  100% { transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); }\n}\n\n@keyframes pulse {\n  0%   { opacity: 0 }\n  5%   { opacity: 1 }\n  30%   { opacity: 1 }\n  70%  { opacity: 0 }\n  100% { opacity: 0 }\n}\n\n/* Print styles */\n@media print {\n  #inner-iframe { position: fixed; }\n  .progressbar, .fixbutton, .notifications, .loadingscreen { visibility: hidden; }\n}\n\n/* Small screen */\n@media screen and (max-width: 600px) {\n    .notification .message { white-space: normal; }\n    .notification .buttons { padding-right: 22px; padding-right: 40px; }\n    .notification .button { white-space: nowrap; }\n    .notification { margin: 0px; }\n    .notifications { right: 0px; max-width: 80%; }\n}\n"
  },
  {
    "path": "src/Ui/media/WrapperZeroFrame.coffee",
    "content": "class WrapperZeroFrame\n\tconstructor: (wrapper) ->\n\t\t@wrapperCmd = wrapper.cmd\n\t\t@wrapperResponse = wrapper.ws.response\n\t\tconsole.log \"WrapperZeroFrame\", wrapper\n\n\tcmd: (cmd, params={}, cb=null) =>\n\t\t@wrapperCmd(cmd, params, cb)\n\n\tresponse: (to, result) =>\n\t\t@wrapperResponse(to, result)\n\n\tisProxyRequest: ->\n\t\treturn window.location.pathname == \"/\"\n\n\tcertSelectGotoSite: (elem) =>\n\t\thref = $(elem).attr(\"href\")\n\t\tif @isProxyRequest() # Fix for proxy request\n\t\t\t$(elem).attr(\"href\", \"http://zero#{href}\")\n\n\nwindow.zeroframe = new WrapperZeroFrame(window.wrapper)\n"
  },
  {
    "path": "src/Ui/media/ZeroSiteTheme.coffee",
    "content": "DARK = \"(prefers-color-scheme: dark)\"\nLIGHT = \"(prefers-color-scheme: light)\"\n\nmqDark = window.matchMedia(DARK)\nmqLight = window.matchMedia(LIGHT)\n\n\nchangeColorScheme = (theme) ->\n    zeroframe.cmd \"userGetGlobalSettings\", [], (user_settings) ->\n        if user_settings.theme != theme\n            user_settings.theme = theme\n            zeroframe.cmd \"userSetGlobalSettings\", [user_settings], (status) ->\n                if status == \"ok\"\n                    location.reload()\n                return\n        return\n    return\n\n\ndisplayNotification = ({matches, media}) ->\n    if !matches\n        return\n\n    zeroframe.cmd \"siteInfo\", [], (site_info) ->\n        if \"ADMIN\" in site_info.settings.permissions\n            zeroframe.cmd \"wrapperNotification\", [\"info\", \"Your system's theme has been changed.<br>Please reload site to use it.\"]\n        else\n            zeroframe.cmd \"wrapperNotification\", [\"info\", \"Your system's theme has been changed.<br>Please open ZeroHello to use it.\"]\n        return\n    return\n\n\ndetectColorScheme = ->\n    if mqDark.matches\n        changeColorScheme(\"dark\")\n    else if mqLight.matches\n        changeColorScheme(\"light\")\n\n    mqDark.addListener(displayNotification)\n    mqLight.addListener(displayNotification)\n\n    return\n\n\nzeroframe.cmd \"userGetGlobalSettings\", [], (user_settings) ->\n    if user_settings.use_system_theme == true\n        detectColorScheme()\n\n    return\n"
  },
  {
    "path": "src/Ui/media/all.css",
    "content": "\n/* ---- Wrapper.css ---- */\n\n\nbody { margin: 0; padding: 0; height: 100%; background-color: #D2CECD; overflow: hidden }\nbody.back { background-color: #090909 }\na { color: black }\n\n.unsupported { text-align: center; z-index: 999; position: relative; margin: auto; width: 480px; background-color: white; padding: 20px; border-bottom: 2px solid #e74c3c; -webkit-box-shadow: 0px 0px 15px #DDD; -moz-box-shadow: 0px 0px 15px #DDD; -o-box-shadow: 0px 0px 15px #DDD; -ms-box-shadow: 0px 0px 15px #DDD; box-shadow: 0px 0px 15px #DDD ; font-family: monospace; }\n.template { display: none !important }\n\n#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0;  } /*; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out*/\n#inner-iframe.back { -webkit-transform: scale(0.95) translate(-300px, 0); -moz-transform: scale(0.95) translate(-300px, 0); -o-transform: scale(0.95) translate(-300px, 0); -ms-transform: scale(0.95) translate(-300px, 0); transform: scale(0.95) translate(-300px, 0) ; opacity: 0.4 }\n\n.button {\n\tpadding: 5px 10px; margin-left: 10px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E;\n\t-webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; text-decoration: none; -webkit-transition: all 0.5s; -moz-transition: all 0.5s; -o-transition: all 0.5s; -ms-transition: all 0.5s; transition: all 0.5s ; background-position: left center; white-space: nowrap;\n}\n.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n.button:active { position: relative; top: 1px }\n.button:focus { outline: none }\n\n.button-Delete { background-color: #e74c3c; border-bottom-color: #c0392b; color: white }\n.button-Delete:hover { background-color: #FF5442; border-bottom-color: #8E2B21 }\n\n.button.loading {\n\tcolor: rgba(0,0,0,0); background: #999 url(img/loading.gif) no-repeat center center;\n\t-webkit-transition: all 0.5s ease-out ; -moz-transition: all 0.5s ease-out ; -o-transition: all 0.5s ease-out ; -ms-transition: all 0.5s ease-out ; transition: all 0.5s ease-out  ; pointer-events: none; border-bottom: 2px solid #666\n}\n.button.disabled { pointer-events: none; border-bottom: 2px solid #666; background-color: #999; opacity: 0.5 }\n.button.button-2 { background-color: transparent; border: 1px solid #EEE; color: #555 }\n.button.button-2:hover { border: 1px solid #CCC; color: #000 }\n\n/* Fixbutton */\n\n.fixbutton {\n\tposition: absolute; right: 35px; top: 15px; width: 40px;  z-index: 999;\n\ttext-align: center; color: white; font-family: Consolas, Monaco, monospace; font-size: 25px;\n}\n.fixbutton-bg {\n\t-webkit-border-radius: 80px; -moz-border-radius: 80px; -o-border-radius: 80px; -ms-border-radius: 80px; border-radius: 80px ; background-color: rgba(180, 180, 180, 0.5); cursor: pointer;\n\tdisplay: block; width: 80px; height: 80px; -webkit-transition: background-color 0.2s, box-shadow 0.5s; -moz-transition: background-color 0.2s, box-shadow 0.5s; -o-transition: background-color 0.2s, box-shadow 0.5s; -ms-transition: background-color 0.2s, box-shadow 0.5s; transition: background-color 0.2s, box-shadow 0.5s ; -webkit-transform: scale(0.6); -moz-transform: scale(0.6); -o-transform: scale(0.6); -ms-transform: scale(0.6); transform: scale(0.6) ; margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */\n\t/*box-shadow: inset 105px 260px 0 -200px rgba(0,0,0,0.1);*/ /* -webkit-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); -moz-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); -o-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); -ms-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1) ; */\n}\n.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ; line-height: 0; padding-top: 5px; opacity: 0.9 }\n.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px; font-size: 40px; line-height: 0; font-family: Verdana, sans-serif; margin-top: 17px }\n.fixbutton-bg:hover { background-color: #AF3BFF }\n.fixbutton-bg:active { background-color: #9E2FEA; top: 1px; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n\n/* Notification */\n\n.notifications { position: absolute; top: 0; right: 80px; display: inline-block; z-index: 999; white-space: nowrap }\n.notification {\n\tposition: relative; float: right; clear: both; margin: 10px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; overflow: hidden; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ;\n\t-webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ; padding-bottom: 5px; color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif;\n\tfont-size: 14px; line-height: 20px; /*border: 1px solid rgba(210, 206, 205, 0.2)*/\n}\n.notification-icon {\n\tdisplay: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 2;\n\ttext-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;\n}\n.notification .body {\n\tpadding-left: 14px; padding-right: 60px; height: 50px; vertical-align: middle; display: table; padding-right: 20px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;\n\tbackground-color: white; left: 50px; top: 0; position: relative; padding-top: 5px; padding-bottom: 5px;\n}\n.notification .message-outer { display: table-row }\n.notification .buttons { display: table-cell; vertical-align: top; padding-top: 9px; padding-right: 20px; text-align: right; }\n.notification.long .body { padding-top: 10px; padding-bottom: 10px }\n.notification .message { display: table-cell; vertical-align: middle; max-width: 500px; white-space: normal; }\n\n.notification.visible { max-width: 350px }\n\n.notification .close { position: absolute; top: 0; right: 0; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }\n.notification .close:hover { color: black }\n.notification .close:active, .notification .close:focus { color: #AF3BFF }\n.notification small { color: #AAA }\n.notification .multiline { white-space: normal; word-break: break-word; max-width: 300px; }\n.body-white .notification { -webkit-box-shadow: 0 1px 9px rgba(0,0,0,0.1) ; -moz-box-shadow: 0 1px 9px rgba(0,0,0,0.1) ; -o-box-shadow: 0 1px 9px rgba(0,0,0,0.1) ; -ms-box-shadow: 0 1px 9px rgba(0,0,0,0.1) ; box-shadow: 0 1px 9px rgba(0,0,0,0.1)  }\n\n/* Notification select */\n.notification .select {\n\tdisplay: block; padding: 10px; margin-right: -32px; text-decoration: none; border-left: 3px solid #EEE;\n\tmargin-top: 1px; -webkit-transition: all 0.3s; -moz-transition: all 0.3s; -o-transition: all 0.3s; -ms-transition: all 0.3s; transition: all 0.3s ; color: #666\n}\n.notification .select:hover, .notification .select.active { background-color: #007AFF; border-left: 3px solid #5D68FF; color: white; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none  }\n.notification .select:active, .notification .select:focus { background-color: #3396FF; color: white; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; border-left-color: #3396FF }\n.notification .select.disabled { opacity: 0.5; pointer-events: none }\n.notification .select small { color: inherit; }\n\n/* Notification types */\n.notification-ask .notification-icon { background-color: #f39c12; }\n.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }\n.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }\n\n/* Notification input */\n.notification .input { padding: 6px; border: 1px solid #DDD; margin-left: 10px; border-bottom: 2px solid #DDD; -webkit-border-radius: 1px; -moz-border-radius: 1px; -o-border-radius: 1px; -ms-border-radius: 1px; border-radius: 1px ; margin-right: -11px; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.notification .input:focus { border-color: #95a5a6; outline: none }\n\n/* Notification progress */\n.notification .circle { width: 50px; height: 50px; position: absolute; left: -50px; top: 0px; background-color: #e2e9ec; z-index: 1; background: -webkit-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -moz-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -o-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: -ms-linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef);background: linear-gradient(405deg, rgba(226, 233, 236, 0.8), #efefef); }\n.notification .circle-svg { margin-left: 10px; margin-top: 10px; -webkit-transform: rotateZ(-90deg); -moz-transform: rotateZ(-90deg); -o-transform: rotateZ(-90deg); -ms-transform: rotateZ(-90deg); transform: rotateZ(-90deg) ; }\n.notification .circle-bg { stroke: #FFF; stroke-width: 2px; -webkit-animation: rolling 0.4s infinite linear; -moz-animation: rolling 0.4s infinite linear; -o-animation: rolling 0.4s infinite linear; -ms-animation: rolling 0.4s infinite linear; animation: rolling 0.4s infinite linear ; stroke-dasharray: 40px; -webkit-transition: all 1s ; -moz-transition: all 1s ; -o-transition: all 1s ; -ms-transition: all 1s ; transition: all 1s  }\n.notification .circle-fg { stroke-dashoffset: 200; stroke: #2ecc71; stroke-width: 2px; stroke-dasharray: 75px; -webkit-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -moz-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -o-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); -ms-transition: all 5s cubic-bezier(0.19, 1, 0.22, 1); transition: all 5s cubic-bezier(0.19, 1, 0.22, 1) ; }\n.notification-progress .notification-icon { opacity: 0; -webkit-transform: scale(0); -moz-transform: scale(0); -o-transform: scale(0); -ms-transform: scale(0); transform: scale(0) ; -webkit-transition: all 0.3s ease-in-out ; -moz-transition: all 0.3s ease-in-out ; -o-transition: all 0.3s ease-in-out ; -ms-transition: all 0.3s ease-in-out ; transition: all 0.3s ease-in-out  }\n.notification-progress .icon-success { -webkit-transform: rotate(45deg) scale(0); -moz-transform: rotate(45deg) scale(0); -o-transform: rotate(45deg) scale(0); -ms-transform: rotate(45deg) scale(0); transform: rotate(45deg) scale(0) ; -webkit-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -moz-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -o-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); -ms-transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) ; }\n@keyframes rolling {\n    0%   { stroke-dashoffset: 80px }\n    100% { stroke-dashoffset: 0px }\n}\n@-webkit-keyframes rolling {\n    0%   { stroke-dashoffset: 80px }\n    100% { stroke-dashoffset: 0px }\n}\n@-moz-keyframes rolling {\n    0%   { stroke-dashoffset: 80px }\n    100% { stroke-dashoffset: 0px }\n}\n\n\n/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */\n\n.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }\n\n\n/* Infopanel */\n.infopanel-container { width: 100%; height: 100%; overflow: hidden; position: absolute; display: none; }\n.infopanel-container.visible { display: block; }\n.infopanel {\n\tposition: absolute; z-index: 999; padding: 15px 15px; bottom: 25px; right: 50px; border: 1px solid #eff3fe;\n\tfont-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; -webkit-box-shadow: 0px 10px 55px rgba(58, 39, 176, 0.17); -moz-box-shadow: 0px 10px 55px rgba(58, 39, 176, 0.17); -o-box-shadow: 0px 10px 55px rgba(58, 39, 176, 0.17); -ms-box-shadow: 0px 10px 55px rgba(58, 39, 176, 0.17); box-shadow: 0px 10px 55px rgba(58, 39, 176, 0.17) ;\n\tbackground-color: white; border-left: 4px solid #9a61f8; border-top-left-radius: 4px; border-bottom-left-radius: 4px;\n\t-webkit-transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1); -moz-transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1); -o-transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1); -ms-transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1); transition: all 0.8s cubic-bezier(0.215, 0.61, 0.355, 1) ;\n}\n.infopanel.closed { -webkit-box-shadow: none; -moz-box-shadow: none; -o-box-shadow: none; -ms-box-shadow: none; box-shadow: none ; -webkit-transform: translateX(100%); -moz-transform: translateX(100%); -o-transform: translateX(100%); -ms-transform: translateX(100%); transform: translateX(100%) ; right: 0px; cursor: pointer; }\n.infopanel .message { font-size: 13px; line-height: 15px; display: inline-block; vertical-align: -9px; }\n.infopanel .message .line { max-width: 200px; display: inline-block; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }\n.infopanel .message .line-1 { font-weight: bold; }\n.infopanel .close { font-size: 16px; text-decoration: none; color: #AAA; padding: 5px; margin-right: -12px; vertical-align: 1px; display: inline-block; }\n.infopanel .close:hover { color: black }\n.infopanel .close:active, .infopanel .close:focus { color: #AF3BFF }\n.infopanel.closed .closed-num { opacity: 1; margin-left: -36px; pointer-events: inherit; }\n.infopanel .closed-num {\n\tposition: absolute; margin-top: 6px; background-color: #6666663d; color: white; width: 10px; text-align: center;\n\tpadding: 4px; border-top-left-radius: 6px; border-bottom-left-radius: 6px; font-size: 10px;\n    opacity: 0; margin-left: 0px; pointer-events: none; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ;\n}\n.infopanel.unfolded .message .line { overflow: visible; white-space: normal; }\n.body-sidebar .infopanel { right: 425px; }\n.body-sidebar .infopanel.closed { right: 0px; }\n\n/* Loading screen */\n\n.loadingscreen { width: 100%; height: 100%; position: absolute; background-color: #EEE; z-index: 1; overflow: auto; display: none }\n.theme-dark .loadingscreen { background-color: #180922; }\n.loading-text { text-align: center; vertical-align: middle; top: 50%; position: absolute; margin-top: 39px; width: 100% }\n.loading-config {\n\tmargin: 20px; display: inline-block; text-transform: uppercase; font-family: Consolas, monospace; position: relative;;\n\ttext-decoration: none; letter-spacing: 1px; font-size: 12px; border-bottom: 1px solid #999; top: -60px; -webkit-transition: all 1s cubic-bezier(1, 0, 0, 1); -moz-transition: all 1s cubic-bezier(1, 0, 0, 1); -o-transition: all 1s cubic-bezier(1, 0, 0, 1); -ms-transition: all 1s cubic-bezier(1, 0, 0, 1); transition: all 1s cubic-bezier(1, 0, 0, 1) ; transition-delay: 0.3s;\n}\n.loading-config:hover { border-bottom-color: #000; -webkit-transition: none; -moz-transition: none; -o-transition: none; -ms-transition: none; transition: none ; }\n.theme-dark .loading-config { color: white }\n.loadingscreen.ready .loading-config { top: 0px; }\n\n\n/* Loading console */\n.loadingscreen .console { line-height: 24px; font-family: monospace; font-size: 14px; color: #ADADAD; text-transform: uppercase; opacity: 0; -webkit-transform: translateY(-20px); -moz-transform: translateY(-20px); -o-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px) ; }\n.loadingscreen .console-line:last-child { color: #6C6767 }\n.loadingscreen .console .cursor {\n\tbackground-color: #999; color: #999; -webkit-animation: pulse 1.5s infinite ease-in-out; -moz-animation: pulse 1.5s infinite ease-in-out; -o-animation: pulse 1.5s infinite ease-in-out; -ms-animation: pulse 1.5s infinite ease-in-out; animation: pulse 1.5s infinite ease-in-out ; margin-right: -9px;\n\tdisplay: inline-block; width: 9px; height: 19px; vertical-align: -4px;\n}\n.loadingscreen .console .console-error { color: #e74c3c; font-weight: bold; -webkit-animation: pulse 2s infinite linear ; -moz-animation: pulse 2s infinite linear ; -o-animation: pulse 2s infinite linear ; -ms-animation: pulse 2s infinite linear ; animation: pulse 2s infinite linear  }\n.loadingscreen .console .console-warning { color: #8e44ad; }\n.loadingscreen .console .button { margin: 20px; display: inline-block; text-transform: none; padding: 10px 20px }\n\n\n/* Flipper loading anim */\n.flipper-container { width: 40px; height: 40px; position: absolute; top: 0%; left: 50%; -webkit-transform: translate3d(-50%, -50%, 0); -moz-transform: translate3d(-50%, -50%, 0); -o-transform: translate3d(-50%, -50%, 0); -ms-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0) ; -webkit-perspective: 1200; -moz-perspective: 1200; -o-perspective: 1200; -ms-perspective: 1200; perspective: 1200 ; opacity: 0 }\n.flipper { position: relative; display: block; height: inherit; width: inherit; -webkit-animation: flip 1.2s infinite ease-in-out; -moz-animation: flip 1.2s infinite ease-in-out; -o-animation: flip 1.2s infinite ease-in-out; -ms-animation: flip 1.2s infinite ease-in-out; animation: flip 1.2s infinite ease-in-out ; -webkit-transform-style: preserve-3d; }\n.flipper .front, .flipper .back {\n\tposition: absolute; top: 0; left: 0; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; /*transform-style: preserve-3d;*/ display: block;\n\tbackground-color: #d50000; height: 100%; width: 100%; /* outline: 1px solid transparent; /* FF AA fix */\n}\n.flipper .back { background-color: white; z-index: 800; -webkit-transform: rotateY(-180deg) ; -moz-transform: rotateY(-180deg) ; -o-transform: rotateY(-180deg) ; -ms-transform: rotateY(-180deg) ; transform: rotateY(-180deg)  }\n\n/* Loading ready */\n.loadingscreen.ready .console { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -o-transform: translateY(0px); -ms-transform: translateY(0px); transform: translateY(0px) ; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s  }\n.loadingscreen.ready .flipper-container { top: 50%; opacity: 1; -webkit-transition: all 1s cubic-bezier(1, 0, 0, 1); -moz-transition: all 1s cubic-bezier(1, 0, 0, 1); -o-transition: all 1s cubic-bezier(1, 0, 0, 1); -ms-transition: all 1s cubic-bezier(1, 0, 0, 1); transition: all 1s cubic-bezier(1, 0, 0, 1) ; }\n\n\n/* Loading done */\n.loadingscreen.done { height: 0%; -webkit-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -moz-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -o-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -ms-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045) ; }\n.loadingscreen.done .console { -webkit-transform: translateY(300px); -moz-transform: translateY(300px); -o-transform: translateY(300px); -ms-transform: translateY(300px); transform: translateY(300px) ; opacity: 0; -webkit-transition: all 1.5s ; -moz-transition: all 1.5s ; -o-transition: all 1.5s ; -ms-transition: all 1.5s ; transition: all 1.5s  }\n.loadingscreen.done .flipper-container { opacity: 0; -webkit-transition: all 1.5s ; -moz-transition: all 1.5s ; -o-transition: all 1.5s ; -ms-transition: all 1.5s ; transition: all 1.5s  }\n\n\n.progressbar {\n\tbackground: #26C281; position: fixed; width: 100%; z-index: 100; top: 0; left: 0; -webkit-transform: scaleX(0); -moz-transform: scaleX(0); -o-transform: scaleX(0); -ms-transform: scaleX(0); transform: scaleX(0) ; transform-origin: 0% 0%; transform:translate3d(0,0,0);\n\theight: 2px; -webkit-transition: transform 1s, opacity 1s; -moz-transition: transform 1s, opacity 1s; -o-transition: transform 1s, opacity 1s; -ms-transition: transform 1s, opacity 1s; transition: transform 1s, opacity 1s ; display: none; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; transform-style: preserve-3d;\n}\n.progressbar .peg {\n\tdisplay: block; position: absolute; right: 0; width: 100px; height: 100%;\n\t-webkit-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -moz-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -o-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; -ms-box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d; box-shadow: 0 0 10px #AF3BFF, 0 0 5px #29d ; opacity: 1.0; -webkit-transform: rotate(3deg) translate(0px, -4px); -moz-transform: rotate(3deg) translate(0px, -4px); -o-transform: rotate(3deg) translate(0px, -4px); -ms-transform: rotate(3deg) translate(0px, -4px); transform: rotate(3deg) translate(0px, -4px) ;\n}\n\n/* Opener overlay */\n.opener-overlay { position: fixed; z-index: 9999; width: 100%; text-align: center; background-color: rgba(100,100,100,0.5); height: 100%; vertical-align: middle; }\n.opener-overlay .dialog { background-color: white; padding: 40px; display: inline-block; color: #4F4F4F; font-family: 'Lucida Grande', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; }\n\n/* Icons */\n.icon-profile { font-size: 6px; top: 0em; -webkit-border-radius: 0.7em 0.7em 0 0; -moz-border-radius: 0.7em 0.7em 0 0; -o-border-radius: 0.7em 0.7em 0 0; -ms-border-radius: 0.7em 0.7em 0 0; border-radius: 0.7em 0.7em 0 0 ; background: #FFFFFF; width: 1.5em; height: 0.7em; position: relative; display: inline-block; margin-right: 4px }\n.icon-profile::before { position: absolute; content: \"\"; top: -1em; left: 0.38em; width: 0.8em; height: 0.85em; -webkit-border-radius: 50%; -moz-border-radius: 50%; -o-border-radius: 50%; -ms-border-radius: 50%; border-radius: 50% ; background: #FFFFFF }\n\n/* Animations */\n\n@keyframes flip {\n  0%   { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }\n  50%  { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)  }\n  100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }\n}\n@-webkit-keyframes flip {\n  0%   { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }\n  50%  { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)  }\n  100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }\n}\n@-moz-keyframes flip {\n  0%   { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }\n  50%  { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)  }\n  100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }\n}\n\n\n@keyframes pulse {\n  0%   { opacity: 0 }\n  5%   { opacity: 1 }\n  30%   { opacity: 1 }\n  70%  { opacity: 0 }\n  100% { opacity: 0 }\n}\n@-webkit-keyframes pulse {\n  0%   { opacity: 0 }\n  5%   { opacity: 1 }\n  30%   { opacity: 1 }\n  70%  { opacity: 0 }\n  100% { opacity: 0 }\n}\n@-moz-keyframes pulse {\n  0%   { opacity: 0 }\n  5%   { opacity: 1 }\n  30%   { opacity: 1 }\n  70%  { opacity: 0 }\n  100% { opacity: 0 }\n}\n\n\n/* Print styles */\n@media print {\n  #inner-iframe { position: fixed; }\n  .progressbar, .fixbutton, .notifications, .loadingscreen { visibility: hidden; }\n}\n\n/* Small screen */\n@media screen and (max-width: 600px) {\n    .notification .message { white-space: normal; }\n    .notification .buttons { padding-right: 22px; padding-right: 40px; }\n    .notification .button { white-space: nowrap; }\n    .notification { margin: 0px; }\n    .notifications { right: 0px; max-width: 80%; }\n}\n"
  },
  {
    "path": "src/Ui/media/all.js",
    "content": "\n/* ---- lib/00-jquery.min.js ---- */\n\n\n/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */\n!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(e,t){\"use strict\";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return\"function\"==typeof t&&\"number\"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement(\"script\");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?l[c.call(e)]||\"object\":typeof e}var b=\"3.3.1\",w=function(e,t){return new w.fn.init(e,t)},T=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;w.fn=w.prototype={jquery:\"3.3.1\",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for(\"boolean\"==typeof a&&(l=a,a=arguments[s]||{},s++),\"object\"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:\"jQuery\"+(\"3.3.1\"+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||\"[object Object]\"!==c.call(e))&&(!(t=i(e))||\"function\"==typeof(n=f.call(t,\"constructor\")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?\"\":(e+\"\").replace(T,\"\")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,\"string\"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),\"function\"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\" \"),function(e,t){l[\"[object \"+t+\"]\"]=t.toLowerCase()});function C(e){var t=!!e&&\"length\"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&(\"array\"===n||0===t||\"number\"==typeof t&&t>0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b=\"sizzle\"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},P=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",M=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",R=\"(?:\\\\\\\\.|[\\\\w-]|[^\\0-\\\\xa0])+\",I=\"\\\\[\"+M+\"*(\"+R+\")(?:\"+M+\"*([*^$|!~]?=)\"+M+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+R+\"))|)\"+M+\"*\\\\]\",W=\":(\"+R+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+I+\")*)|.*)\\\\)|)\",$=new RegExp(M+\"+\",\"g\"),B=new RegExp(\"^\"+M+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+M+\"+$\",\"g\"),F=new RegExp(\"^\"+M+\"*,\"+M+\"*\"),_=new RegExp(\"^\"+M+\"*([>+~]|\"+M+\")\"+M+\"*\"),z=new RegExp(\"=\"+M+\"*([^\\\\]'\\\"]*?)\"+M+\"*\\\\]\",\"g\"),X=new RegExp(W),U=new RegExp(\"^\"+R+\"$\"),V={ID:new RegExp(\"^#(\"+R+\")\"),CLASS:new RegExp(\"^\\\\.(\"+R+\")\"),TAG:new RegExp(\"^(\"+R+\"|[*])\"),ATTR:new RegExp(\"^\"+I),PSEUDO:new RegExp(\"^\"+W),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+M+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+M+\"*(?:([+-]|)\"+M+\"*(\\\\d+)|))\"+M+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+P+\")$\",\"i\"),needsContext:new RegExp(\"^\"+M+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+M+\"*((?:-\\\\d)?\\\\d*)\"+M+\"*\\\\)|)(?=[^-]|$)\",\"i\")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\\d$/i,Q=/^[^{]+\\{\\s*\\[native \\w/,J=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,K=/[+~]/,Z=new RegExp(\"\\\\\\\\([\\\\da-f]{1,6}\"+M+\"?|(\"+M+\")|.)\",\"ig\"),ee=function(e,t,n){var r=\"0x\"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,ne=function(e,t){return t?\"\\0\"===e?\"\\ufffd\":e.slice(0,-1)+\"\\\\\"+e.charCodeAt(e.length-1).toString(16)+\" \":\"\\\\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&(\"form\"in e||\"label\"in e)},{dir:\"parentNode\",next:\"legend\"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],\"string\"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+\" \"]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if(\"object\"!==t.nodeName.toLowerCase()){(c=t.getAttribute(\"id\"))?c=c.replace(te,ne):t.setAttribute(\"id\",c=b),s=(h=a(e)).length;while(s--)h[s]=\"#\"+c+\" \"+ve(h[s]);v=h.join(\",\"),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute(\"id\")}}}return u(e.replace(B,\"$1\"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+\" \")>r.cacheLength&&delete t[e.shift()],t[n+\" \"]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement(\"fieldset\");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split(\"|\"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return\"input\"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return(\"input\"===n||\"button\"===n)&&t.type===e}}function de(e){return function(t){return\"form\"in t?t.parentNode&&!1===t.disabled?\"label\"in t?\"label\"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:\"label\"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&\"undefined\"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&\"HTML\"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener(\"unload\",re,!1):i.attachEvent&&i.attachEvent(\"onunload\",re)),n.attributes=ue(function(e){return e.className=\"i\",!e.getAttribute(\"className\")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment(\"\")),!e.getElementsByTagName(\"*\").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute(\"id\")===t}},r.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n=\"undefined\"!=typeof e.getAttributeNode&&e.getAttributeNode(\"id\");return n&&n.value===t}},r.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return\"undefined\"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if(\"*\"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if(\"undefined\"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML=\"<a id='\"+b+\"'></a><select id='\"+b+\"-\\r\\\\' msallowcapture=''><option selected=''></option></select>\",e.querySelectorAll(\"[msallowcapture^='']\").length&&y.push(\"[*^$]=\"+M+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\"[selected]\").length||y.push(\"\\\\[\"+M+\"*(?:value|\"+P+\")\"),e.querySelectorAll(\"[id~=\"+b+\"-]\").length||y.push(\"~=\"),e.querySelectorAll(\":checked\").length||y.push(\":checked\"),e.querySelectorAll(\"a#\"+b+\"+*\").length||y.push(\".#.+[+~]\")}),ue(function(e){e.innerHTML=\"<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>\";var t=d.createElement(\"input\");t.setAttribute(\"type\",\"hidden\"),e.appendChild(t).setAttribute(\"name\",\"D\"),e.querySelectorAll(\"[name=d]\").length&&y.push(\"name\"+M+\"*[*^$|!~]?=\"),2!==e.querySelectorAll(\":enabled\").length&&y.push(\":enabled\",\":disabled\"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(\":disabled\").length&&y.push(\":enabled\",\":disabled\"),e.querySelectorAll(\"*,:x\"),y.push(\",.*:\")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,\"*\"),m.call(e,\"[s!='']:x\"),v.push(\"!=\",W)}),y=y.length&&new RegExp(y.join(\"|\")),v=v.length&&new RegExp(v.join(\"|\")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,\"='$1']\"),n.matchesSelector&&g&&!S[t+\" \"]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+\"\").replace(te,ne)},oe.error=function(e){throw new Error(\"Syntax error, unrecognized expression: \"+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n=\"\",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if(\"string\"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||\"\").replace(Z,ee),\"~=\"===e[2]&&(e[3]=\" \"+e[3]+\" \"),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),\"nth\"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*(\"even\"===e[3]||\"odd\"===e[3])),e[5]=+(e[7]+e[8]||\"odd\"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||\"\":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(\")\",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return\"*\"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+\" \"];return t||(t=new RegExp(\"(^|\"+M+\")\"+e+\"(\"+M+\"|$)\"))&&E(e,function(e){return t.test(\"string\"==typeof e.className&&e.className||\"undefined\"!=typeof e.getAttribute&&e.getAttribute(\"class\")||\"\")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?\"!=\"===t:!t||(i+=\"\",\"=\"===t?i===n:\"!=\"===t?i!==n:\"^=\"===t?n&&0===i.indexOf(n):\"*=\"===t?n&&i.indexOf(n)>-1:\"$=\"===t?n&&i.slice(-n.length)===n:\"~=\"===t?(\" \"+i.replace($,\" \")+\" \").indexOf(n)>-1:\"|=\"===t&&(i===n||i.slice(0,n.length+1)===n+\"-\"))}},CHILD:function(e,t,n,r,i){var o=\"nth\"!==e.slice(0,3),a=\"last\"!==e.slice(-4),s=\"of-type\"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?\"nextSibling\":\"previousSibling\",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g=\"only\"===e&&!h&&\"nextSibling\"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error(\"unsupported pseudo: \"+e);return i[b]?i(t):i.length>1?(n=[e,e,\"\",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,\"$1\"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||\"\")||oe.error(\"unsupported lang: \"+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute(\"xml:lang\")||t.getAttribute(\"lang\"))return(n=n.toLowerCase())===e||0===n.indexOf(e+\"-\")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&!!e.checked||\"option\"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&\"button\"===e.type||\"button\"===t},text:function(e){var t;return\"input\"===e.nodeName.toLowerCase()&&\"text\"===e.type&&(null==(t=e.getAttribute(\"type\"))||\"text\"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:he(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:he(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=r.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=fe(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=pe(t);function ye(){}ye.prototype=r.filters=r.pseudos,r.setFilters=new ye,a=oe.tokenize=function(e,t){var n,i,o,a,s,u,l,c=k[e+\" \"];if(c)return t?0:c.slice(0);s=e,u=[],l=r.preFilter;while(s){n&&!(i=F.exec(s))||(i&&(s=s.slice(i[0].length)||s),u.push(o=[])),n=!1,(i=_.exec(s))&&(n=i.shift(),o.push({value:n,type:i[0].replace(B,\" \")}),s=s.slice(n.length));for(a in r.filter)!(i=V[a].exec(s))||l[a]&&!(i=l[a](i))||(n=i.shift(),o.push({value:n,type:a,matches:i}),s=s.slice(n.length));if(!n)break}return t?s.length:s?oe.error(e):k(e,u).slice(0)};function ve(e){for(var t=0,n=e.length,r=\"\";t<n;t++)r+=e[t].value;return r}function me(e,t,n){var r=t.dir,i=t.next,o=i||r,a=n&&\"parentNode\"===o,s=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||a)return e(t,n,i);return!1}:function(t,n,u){var l,c,f,p=[T,s];if(u){while(t=t[r])if((1===t.nodeType||a)&&e(t,n,u))return!0}else while(t=t[r])if(1===t.nodeType||a)if(f=t[b]||(t[b]={}),c=f[t.uniqueID]||(f[t.uniqueID]={}),i&&i===t.nodeName.toLowerCase())t=t[r]||t;else{if((l=c[o])&&l[0]===T&&l[1]===s)return p[2]=l[2];if(c[o]=p,p[2]=e(t,n,u))return!0}return!1}}function xe(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r<i;r++)oe(e,t[r],n);return n}function we(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Te(e,t,n,r,i,o){return r&&!r[b]&&(r=Te(r)),i&&!i[b]&&(i=Te(i,o)),se(function(o,a,s,u){var l,c,f,p=[],d=[],h=a.length,g=o||be(t||\"*\",s.nodeType?[s]:s,[]),y=!e||!o&&t?g:we(g,p,e,s,u),v=n?i||(o?e:h||r)?[]:a:y;if(n&&n(y,v,s,u),r){l=we(v,d),r(l,[],s,u),c=l.length;while(c--)(f=l[c])&&(v[d[c]]=!(y[d[c]]=f))}if(o){if(i||e){if(i){l=[],c=v.length;while(c--)(f=v[c])&&l.push(y[c]=f);i(null,v=[],l,u)}c=v.length;while(c--)(f=v[c])&&(l=i?O(o,f):p[c])>-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[\" \"],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u<o;u++)if(n=r.relative[e[u].type])p=[me(xe(p),n)];else{if((n=r.filter[e[u].type].apply(null,e[u].matches))[b]){for(i=++u;i<o;i++)if(r.relative[e[i].type])break;return Te(u>1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:\" \"===e[u-2].type?\"*\":\"\"})).replace(B,\"$1\"),n,u<i&&Ce(e.slice(u,i)),i<o&&Ce(e=e.slice(i)),i<o&&ve(e))}p.push(n)}return xe(p)}function Ee(e,t){var n=t.length>0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m=\"0\",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG(\"*\",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+\" \"];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p=\"function\"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&\"ID\"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split(\"\").sort(D).join(\"\")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement(\"fieldset\"))}),ue(function(e){return e.innerHTML=\"<a href='#'></a>\",\"#\"===e.firstChild.getAttribute(\"href\")})||le(\"type|href|height|width\",function(e,t,n){if(!n)return e.getAttribute(t,\"type\"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML=\"<input/>\",e.firstChild.setAttribute(\"value\",\"\"),\"\"===e.firstChild.getAttribute(\"value\")})||le(\"value\",function(e,t,n){if(!n&&\"input\"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute(\"disabled\")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[\":\"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):\"string\"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=\":not(\"+e+\")\"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if(\"string\"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t<r;t++)if(w.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)w.find(e,i[t],n);return r>1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,\"string\"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,\"string\"==typeof e){if(!(i=\"<\"===e[0]&&\">\"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(w.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a=\"string\"!=typeof e&&w(e);if(!D.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?\"string\"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,\"parentNode\")},parentsUntil:function(e,t,n){return k(e,\"parentNode\",n)},next:function(e){return P(e,\"nextSibling\")},prev:function(e){return P(e,\"previousSibling\")},nextAll:function(e){return k(e,\"nextSibling\")},prevAll:function(e){return k(e,\"previousSibling\")},nextUntil:function(e,t,n){return k(e,\"nextSibling\",n)},prevUntil:function(e,t,n){return k(e,\"previousSibling\",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,\"iframe\")?e.contentDocument:(N(e,\"template\")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return\"Until\"!==e.slice(-5)&&(r=n),r&&\"string\"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\\x20\\t\\r\\n\\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e=\"string\"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s<o.length)!1===o[s].apply(n[0],n[1])&&e.stopOnFalse&&(s=o.length,n=!1)}e.memory||(n=!1),t=!1,i&&(o=n?[]:\"\")},l={add:function(){return o&&(n&&!t&&(s=o.length-1,a.push(n)),function t(n){w.each(n,function(n,r){g(r)?e.unique&&l.has(r)||o.push(r):r&&r.length&&\"string\"!==x(r)&&t(r)})}(arguments),n&&!t&&u()),this},remove:function(){return w.each(arguments,function(e,t){var n;while((n=w.inArray(t,o,n))>-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n=\"\",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=\"\"),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[[\"notify\",\"progress\",w.Callbacks(\"memory\"),w.Callbacks(\"memory\"),2],[\"resolve\",\"done\",w.Callbacks(\"once memory\"),w.Callbacks(\"once memory\"),0,\"resolved\"],[\"reject\",\"fail\",w.Callbacks(\"once memory\"),w.Callbacks(\"once memory\"),1,\"rejected\"]],r=\"pending\",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},\"catch\":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+\"With\"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t<o)){if((e=r.apply(s,u))===n.promise())throw new TypeError(\"Thenable self-resolution\");l=e&&(\"object\"==typeof e||\"function\"==typeof e)&&e.then,g(l)?i?l.call(e,a(o,n,I,i),a(o,n,W,i)):(o++,l.call(e,a(o,n,I,i),a(o,n,W,i),a(o,n,I,n.notifyWith))):(r!==I&&(s=void 0,u=[e]),(i||n.resolveWith)(s,u))}},c=i?l:function(){try{l()}catch(e){w.Deferred.exceptionHook&&w.Deferred.exceptionHook(e,c.stackTrace),t+1>=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+\"With\"](this===o?void 0:this,arguments),this},o[t[0]+\"With\"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),\"pending\"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn(\"jQuery.Deferred exception: \"+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)[\"catch\"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener(\"DOMContentLoaded\",_),e.removeEventListener(\"load\",_),w.ready()}\"complete\"===r.readyState||\"loading\"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener(\"DOMContentLoaded\",_),e.addEventListener(\"load\",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if(\"object\"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},X=/^-ms-/,U=/-([a-z])/g;function V(e,t){return t.toUpperCase()}function G(e){return e.replace(X,\"ms-\").replace(U,V)}var Y=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function Q(){this.expando=w.expando+Q.uid++}Q.uid=1,Q.prototype={cache:function(e){var t=e[this.expando];return t||(t={},Y(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if(\"string\"==typeof t)i[G(t)]=n;else for(r in t)i[G(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][G(t)]},access:function(e,t,n){return void 0===t||t&&\"string\"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(G):(t=G(t))in r?[t]:t.match(M)||[]).length;while(n--)delete r[t[n]]}(void 0===t||w.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!w.isEmptyObject(t)}};var J=new Q,K=new Q,Z=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,ee=/[A-Z]/g;function te(e){return\"true\"===e||\"false\"!==e&&(\"null\"===e?null:e===+e+\"\"?+e:Z.test(e)?JSON.parse(e):e)}function ne(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r=\"data-\"+t.replace(ee,\"-$&\").toLowerCase(),\"string\"==typeof(n=e.getAttribute(r))){try{n=te(n)}catch(e){}K.set(e,t,n)}else n=void 0;return n}w.extend({hasData:function(e){return K.hasData(e)||J.hasData(e)},data:function(e,t,n){return K.access(e,t,n)},removeData:function(e,t){K.remove(e,t)},_data:function(e,t,n){return J.access(e,t,n)},_removeData:function(e,t){J.remove(e,t)}}),w.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=K.get(o),1===o.nodeType&&!J.get(o,\"hasDataAttrs\"))){n=a.length;while(n--)a[n]&&0===(r=a[n].name).indexOf(\"data-\")&&(r=G(r.slice(5)),ne(o,r,i[r]));J.set(o,\"hasDataAttrs\",!0)}return i}return\"object\"==typeof e?this.each(function(){K.set(this,e)}):z(this,function(t){var n;if(o&&void 0===t){if(void 0!==(n=K.get(o,e)))return n;if(void 0!==(n=ne(o,e)))return n}else this.each(function(){K.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||\"fx\")+\"queue\",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||\"fx\";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};\"inprogress\"===i&&(i=n.shift(),r--),i&&(\"fx\"===t&&n.unshift(\"inprogress\"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+\"queueHooks\";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks(\"once memory\").add(function(){J.remove(e,[t+\"queue\",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return\"string\"!=typeof e&&(t=e,e=\"fx\",n--),arguments.length<n?w.queue(this[0],e):void 0===t?this:this.each(function(){var n=w.queue(this,e,t);w._queueHooks(this,e),\"fx\"===e&&\"inprogress\"!==n[0]&&w.dequeue(this,e)})},dequeue:function(e){return this.each(function(){w.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||\"fx\",[])},promise:function(e,t){var n,r=1,i=w.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};\"string\"!=typeof e&&(t=e,e=void 0),e=e||\"fx\";while(a--)(n=J.get(o[a],e+\"queueHooks\"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var re=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,ie=new RegExp(\"^(?:([+-])=|)(\"+re+\")([a-z%]*)$\",\"i\"),oe=[\"Top\",\"Right\",\"Bottom\",\"Left\"],ae=function(e,t){return\"none\"===(e=t||e).style.display||\"\"===e.style.display&&w.contains(e.ownerDocument,e)&&\"none\"===w.css(e,\"display\")},se=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i};function ue(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return w.css(e,t,\"\")},u=s(),l=n&&n[3]||(w.cssNumber[t]?\"\":\"px\"),c=(w.cssNumber[t]||\"px\"!==l&&+u)&&ie.exec(w.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)w.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,w.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var le={};function ce(e){var t,n=e.ownerDocument,r=e.nodeName,i=le[r];return i||(t=n.body.appendChild(n.createElement(r)),i=w.css(t,\"display\"),t.parentNode.removeChild(t),\"none\"===i&&(i=\"block\"),le[r]=i,i)}function fe(e,t){for(var n,r,i=[],o=0,a=e.length;o<a;o++)(r=e[o]).style&&(n=r.style.display,t?(\"none\"===n&&(i[o]=J.get(r,\"display\")||null,i[o]||(r.style.display=\"\")),\"\"===r.style.display&&ae(r)&&(i[o]=ce(r))):\"none\"!==n&&(i[o]=\"none\",J.set(r,\"display\",n)));for(o=0;o<a;o++)null!=i[o]&&(e[o].style.display=i[o]);return e}w.fn.extend({show:function(){return fe(this,!0)},hide:function(){return fe(this)},toggle:function(e){return\"boolean\"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?w(this).show():w(this).hide()})}});var pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]+)/i,he=/^$|^module$|\\/(?:java|ecma)script/i,ge={option:[1,\"<select multiple='multiple'>\",\"</select>\"],thead:[1,\"<table>\",\"</table>\"],col:[2,\"<table><colgroup>\",\"</colgroup></table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:[0,\"\",\"\"]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n=\"undefined\"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||\"*\"):\"undefined\"!=typeof e.querySelectorAll?e.querySelectorAll(t||\"*\"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n<r;n++)J.set(e[n],\"globalEval\",!t||J.get(t[n],\"globalEval\"))}var me=/<|&#?\\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if(\"object\"===x(o))w.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement(\"div\")),s=(de.exec(o)||[\"\",\"\"])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+w.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;w.merge(p,a.childNodes),(a=f.firstChild).textContent=\"\"}else p.push(t.createTextNode(o));f.textContent=\"\",d=0;while(o=p[d++])if(r&&w.inArray(o,r)>-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),\"script\"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||\"\")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement(\"div\")),t=r.createElement(\"input\");t.setAttribute(\"type\",\"radio\"),t.setAttribute(\"checked\",\"checked\"),t.setAttribute(\"name\",\"t\"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML=\"<textarea>x</textarea>\",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if(\"object\"==typeof t){\"string\"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&(\"string\"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return\"undefined\"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||\"\").match(M)||[\"\"]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(\".\")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||\"\").match(M)||[\"\"]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||\"\").split(\".\").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&(\"**\"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,\"handle events\")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,\"events\")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n<arguments.length;n++)u[n]=arguments[n];if(t.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,t)){s=w.event.handlers.call(this,t,l),n=0;while((o=s[n++])&&!t.isPropagationStopped()){t.currentTarget=o.elem,r=0;while((a=o.handlers[r++])&&!t.isImmediatePropagationStopped())t.rnamespace&&!t.rnamespace.test(a.namespace)||(t.handleObj=a,t.data=a.data,void 0!==(i=((w.event.special[a.origType]||{}).handle||a.handler).apply(o.elem,u))&&!1===(t.result=i)&&(t.preventDefault(),t.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,t),t.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!(\"click\"===e.type&&e.button>=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&(\"click\"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+\" \"]&&(a[i]=r.needsContext?w(i,this).index(l)>-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(e,t){Object.defineProperty(w.Event.prototype,e,{enumerable:!0,configurable:!0,get:g(t)?function(){if(this.originalEvent)return t(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[e]},set:function(t){Object.defineProperty(this,e,{enumerable:!0,configurable:!0,writable:!0,value:t})}})},fix:function(e){return e[w.expando]?e:new w.Event(e)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==Se()&&this.focus)return this.focus(),!1},delegateType:\"focusin\"},blur:{trigger:function(){if(this===Se()&&this.blur)return this.blur(),!1},delegateType:\"focusout\"},click:{trigger:function(){if(\"checkbox\"===this.type&&this.click&&N(this,\"input\"))return this.click(),!1},_default:function(e){return N(e.target,\"a\")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},w.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},w.Event=function(e,t){if(!(this instanceof w.Event))return new w.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ee:ke,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&w.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[w.expando]=!0},w.Event.prototype={constructor:w.Event,isDefaultPrevented:ke,isPropagationStopped:ke,isImmediatePropagationStopped:ke,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ee,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ee,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ee,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},w.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,\"char\":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&we.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Te.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},w.event.addProp),w.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(e,t){w.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return i&&(i===r||w.contains(r,i))||(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),w.fn.extend({on:function(e,t,n,r){return De(this,e,t,n,r)},one:function(e,t,n,r){return De(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,w(e.delegateTarget).off(r.namespace?r.origType+\".\"+r.namespace:r.origType,r.selector,r.handler),this;if(\"object\"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&\"function\"!=typeof t||(n=t,t=void 0),!1===n&&(n=ke),this.each(function(){w.event.remove(this,e,n,t)})}});var Ne=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)[^>]*)\\/>/gi,Ae=/<script|<style|<link/i,je=/checked\\s*(?:[^=]|=\\s*.checked.)/i,qe=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g;function Le(e,t){return N(e,\"table\")&&N(11!==t.nodeType?t:t.firstChild,\"tr\")?w(e).children(\"tbody\")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute(\"type\"))+\"/\"+e.type,e}function Oe(e){return\"true/\"===(e.type||\"\").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute(\"type\"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n<r;n++)w.event.add(t,i,l[i][n])}K.hasData(e)&&(s=K.access(e),u=w.extend({},s),K.set(t,u))}}function Me(e,t){var n=t.nodeName.toLowerCase();\"input\"===n&&pe.test(e.type)?t.checked=e.checked:\"input\"!==n&&\"textarea\"!==n||(t.defaultValue=e.defaultValue)}function Re(e,t,n,r){t=a.apply([],t);var i,o,s,u,l,c,f=0,p=e.length,d=p-1,y=t[0],v=g(y);if(v||p>1&&\"string\"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,\"script\"),He)).length;f<p;f++)l=i,f!==d&&(l=w.clone(l,!0,!0),u&&w.merge(s,ye(l,\"script\"))),n.call(e[f],l,f);if(u)for(c=s[s.length-1].ownerDocument,w.map(s,Oe),f=0;f<u;f++)l=s[f],he.test(l.type||\"\")&&!J.access(l,\"globalEval\")&&w.contains(c,l)&&(l.src&&\"module\"!==(l.type||\"\").toLowerCase()?w._evalUrl&&w._evalUrl(l.src):m(l.textContent.replace(qe,\"\"),c,l))}return e}function Ie(e,t,n){for(var r,i=t?w.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||w.cleanData(ye(r)),r.parentNode&&(n&&w.contains(r.ownerDocument,r)&&ve(ye(r,\"script\")),r.parentNode.removeChild(r));return e}w.extend({htmlPrefilter:function(e){return e.replace(Ne,\"<$1></$2>\")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r<i;r++)Me(o[r],a[r]);if(t)if(n)for(o=o||ye(e),a=a||ye(s),r=0,i=o.length;r<i;r++)Pe(o[r],a[r]);else Pe(e,s);return(a=ye(s,\"script\")).length>0&&ve(a,!u&&ye(e,\"script\")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent=\"\");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if(\"string\"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(w.cleanData(ye(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=[];return Re(this,arguments,function(t){var n=this.parentNode;w.inArray(this,e)<0&&(w.cleanData(ye(this)),n&&n.replaceChild(t,this))},e)}}),w.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(e,t){w.fn[e]=function(e){for(var n,r=[],i=w(e),o=i.length-1,a=0;a<=o;a++)n=a===o?this:this.clone(!0),w(i[a])[t](n),s.apply(r,n.get());return this.pushStack(r)}});var We=new RegExp(\"^(\"+re+\")(?!px)[a-z%]+$\",\"i\"),$e=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},Be=new RegExp(oe.join(\"|\"),\"i\");!function(){function t(){if(c){l.style.cssText=\"position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0\",c.style.cssText=\"position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%\",be.appendChild(l).appendChild(c);var t=e.getComputedStyle(c);i=\"1%\"!==t.top,u=12===n(t.marginLeft),c.style.right=\"60%\",s=36===n(t.right),o=36===n(t.width),c.style.position=\"absolute\",a=36===c.offsetWidth||\"absolute\",be.removeChild(l),c=null}}function n(e){return Math.round(parseFloat(e))}var i,o,a,s,u,l=r.createElement(\"div\"),c=r.createElement(\"div\");c.style&&(c.style.backgroundClip=\"content-box\",c.cloneNode(!0).style.backgroundClip=\"\",h.clearCloneStyle=\"content-box\"===c.style.backgroundClip,w.extend(h,{boxSizingReliable:function(){return t(),o},pixelBoxStyles:function(){return t(),s},pixelPosition:function(){return t(),i},reliableMarginLeft:function(){return t(),u},scrollboxSize:function(){return t(),a}}))}();function Fe(e,t,n){var r,i,o,a,s=e.style;return(n=n||$e(e))&&(\"\"!==(a=n.getPropertyValue(t)||n[t])||w.contains(e.ownerDocument,e)||(a=w.style(e,t)),!h.pixelBoxStyles()&&We.test(a)&&Be.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+\"\":a}function _e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}var ze=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ue={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Ve={letterSpacing:\"0\",fontWeight:\"400\"},Ge=[\"Webkit\",\"Moz\",\"ms\"],Ye=r.createElement(\"div\").style;function Qe(e){if(e in Ye)return e;var t=e[0].toUpperCase()+e.slice(1),n=Ge.length;while(n--)if((e=Ge[n]+t)in Ye)return e}function Je(e){var t=w.cssProps[e];return t||(t=w.cssProps[e]=Qe(e)||e),t}function Ke(e,t,n){var r=ie.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||\"px\"):t}function Ze(e,t,n,r,i,o){var a=\"width\"===t?1:0,s=0,u=0;if(n===(r?\"border\":\"content\"))return 0;for(;a<4;a+=2)\"margin\"===n&&(u+=w.css(e,n+oe[a],!0,i)),r?(\"content\"===n&&(u-=w.css(e,\"padding\"+oe[a],!0,i)),\"margin\"!==n&&(u-=w.css(e,\"border\"+oe[a]+\"Width\",!0,i))):(u+=w.css(e,\"padding\"+oe[a],!0,i),\"padding\"!==n?u+=w.css(e,\"border\"+oe[a]+\"Width\",!0,i):s+=w.css(e,\"border\"+oe[a]+\"Width\",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o=\"border-box\"===w.css(e,\"boxSizing\",!1,r),a=o;if(We.test(i)){if(!n)return i;i=\"auto\"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),(\"auto\"===i||!parseFloat(i)&&\"inline\"===w.css(e,\"display\",!1,r))&&(i=e[\"offset\"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?\"border\":\"content\"),a,r,i)+\"px\"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,\"opacity\");return\"\"===n?\"1\":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&\"get\"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];\"string\"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o=\"number\"),null!=n&&n===n&&(\"number\"===o&&(n+=i&&i[3]||(w.cssNumber[s]?\"\":\"px\")),h.clearCloneStyle||\"\"!==n||0!==t.indexOf(\"background\")||(l[t]=\"inherit\"),a&&\"set\"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&\"get\"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),\"normal\"===i&&t in Ve&&(i=Ve[t]),\"\"===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each([\"height\",\"width\"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,\"display\"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a=\"border-box\"===w.css(e,\"boxSizing\",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,\"border\",!1,o)-.5)),s&&(i=ie.exec(n))&&\"px\"!==(i[3]||\"px\")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,\"marginLeft\"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+\"px\"}),w.each({margin:\"\",padding:\"\",border:\"Width\"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o=\"string\"==typeof n?n.split(\" \"):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},\"margin\"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a<i;a++)o[t[a]]=w.css(e,t[a],!1,r);return o}return void 0!==n?w.style(e,t,n):w.css(e,t)},e,t,arguments.length>1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?\"\":\"px\")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,\"\"))&&\"auto\"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:\"swing\"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i[\"margin\"+(n=oe[r])]=i[\"padding\"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners[\"*\"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ct(e,t,n){var r,i,o,a,s,u,l,c,f=\"width\"in t||\"height\"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),y=J.get(e,\"fxshow\");n.queue||(null==(a=w._queueHooks(e,\"fx\")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,w.queue(e,\"fx\").length||a.empty.fire()})}));for(r in t)if(i=t[r],it.test(i)){if(delete t[r],o=o||\"toggle\"===i,i===(g?\"hide\":\"show\")){if(\"show\"!==i||!y||void 0===y[r])continue;g=!0}d[r]=y&&y[r]||w.style(e,r)}if((u=!w.isEmptyObject(t))||!w.isEmptyObject(d)){f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=y&&y.display)&&(l=J.get(e,\"display\")),\"none\"===(c=w.css(e,\"display\"))&&(l?c=l:(fe([e],!0),l=e.style.display||l,c=w.css(e,\"display\"),fe([e]))),(\"inline\"===c||\"inline-block\"===c&&null!=l)&&\"none\"===w.css(e,\"float\")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l=\"none\"===c?\"\":c)),h.display=\"inline-block\")),n.overflow&&(h.overflow=\"hidden\",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1;for(r in d)u||(y?\"hidden\"in y&&(g=y.hidden):y=J.access(e,\"fxshow\",{display:l}),o&&(y.hidden=!g),g&&fe([e],!0),p.done(function(){g||fe([e]),J.remove(e,\"fxshow\");for(r in d)w.style(e,r,d[r])})),u=lt(g?y[r]:0,r,p),r in y||(y[r]=u.start,g&&(u.end=u.start,u.start=0))}}function ft(e,t){var n,r,i,o,a;for(n in e)if(r=G(n),i=t[r],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=w.cssHooks[r])&&\"expand\"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function pt(e,t,n){var r,i,o=0,a=pt.prefilters.length,s=w.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=nt||st(),n=Math.max(0,l.startTime+l.duration-t),r=1-(n/l.duration||0),o=0,a=l.tweens.length;o<a;o++)l.tweens[o].run(r);return s.notifyWith(e,[l,r,n]),r<1&&a?n:(a||s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:w.extend({},t),opts:w.extend(!0,{specialEasing:{},easing:w.easing._default},n),originalProperties:t,originalOptions:n,startTime:nt||st(),duration:n.duration,tweens:[],createTween:function(t,n){var r=w.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;n<r;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(ft(c,l.opts.specialEasing);o<a;o++)if(r=pt.prefilters[o].call(l,e,c,l.opts))return g(r.stop)&&(w._queueHooks(l.elem,l.opts.queue).stop=r.stop.bind(r)),r;return w.map(c,lt,l),g(l.opts.start)&&l.opts.start.call(e,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),w.fx.timer(w.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l}w.Animation=w.extend(pt,{tweeners:{\"*\":[function(e,t){var n=this.createTween(e,t);return ue(n.elem,e,ie.exec(t),n),n}]},tweener:function(e,t){g(e)?(t=e,e=[\"*\"]):e=e.match(M);for(var n,r=0,i=e.length;r<i;r++)n=e[r],pt.tweeners[n]=pt.tweeners[n]||[],pt.tweeners[n].unshift(t)},prefilters:[ct],prefilter:function(e,t){t?pt.prefilters.unshift(e):pt.prefilters.push(e)}}),w.speed=function(e,t,n){var r=e&&\"object\"==typeof e?w.extend({},e):{complete:n||!n&&t||g(e)&&e,duration:e,easing:n&&t||t&&!g(t)&&t};return w.fx.off?r.duration=0:\"number\"!=typeof r.duration&&(r.duration in w.fx.speeds?r.duration=w.fx.speeds[r.duration]:r.duration=w.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue=\"fx\"),r.old=r.complete,r.complete=function(){g(r.old)&&r.old.call(this),r.queue&&w.dequeue(this,r.queue)},r},w.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css(\"opacity\",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=w.isEmptyObject(e),o=w.speed(t,n,r),a=function(){var t=pt(this,w.extend({},e),o);(i||J.get(this,\"finish\"))&&t.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return\"string\"!=typeof e&&(n=t,t=e,e=void 0),t&&!1!==e&&this.queue(e||\"fx\",[]),this.each(function(){var t=!0,i=null!=e&&e+\"queueHooks\",o=w.timers,a=J.get(this);if(i)a[i]&&a[i].stop&&r(a[i]);else for(i in a)a[i]&&a[i].stop&&ot.test(i)&&r(a[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));!t&&n||w.dequeue(this,e)})},finish:function(e){return!1!==e&&(e=e||\"fx\"),this.each(function(){var t,n=J.get(this),r=n[e+\"queue\"],i=n[e+\"queueHooks\"],o=w.timers,a=r?r.length:0;for(n.finish=!0,w.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;t<a;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),w.each([\"toggle\",\"show\",\"hide\"],function(e,t){var n=w.fn[t];w.fn[t]=function(e,r,i){return null==e||\"boolean\"==typeof e?n.apply(this,arguments):this.animate(ut(t,!0),e,r,i)}}),w.each({slideDown:ut(\"show\"),slideUp:ut(\"hide\"),slideToggle:ut(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(e,t){w.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),w.timers=[],w.fx.tick=function(){var e,t=0,n=w.timers;for(nt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||w.fx.stop(),nt=void 0},w.fx.timer=function(e){w.timers.push(e),w.fx.start()},w.fx.interval=13,w.fx.start=function(){rt||(rt=!0,at())},w.fx.stop=function(){rt=null},w.fx.speeds={slow:600,fast:200,_default:400},w.fn.delay=function(t,n){return t=w.fx?w.fx.speeds[t]||t:t,n=n||\"fx\",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=r.createElement(\"input\"),t=r.createElement(\"select\").appendChild(r.createElement(\"option\"));e.type=\"checkbox\",h.checkOn=\"\"!==e.value,h.optSelected=t.selected,(e=r.createElement(\"input\")).value=\"t\",e.type=\"radio\",h.radioValue=\"t\"===e.value}();var dt,ht=w.expr.attrHandle;w.fn.extend({attr:function(e,t){return z(this,w.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return\"undefined\"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+\"\"),n):i&&\"get\"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&\"radio\"===t&&N(e,\"input\")){var n=e.value;return e.setAttribute(\"type\",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&\"get\"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,\"tabindex\");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{\"for\":\"htmlFor\",\"class\":\"className\"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(\" \")}function mt(e){return e.getAttribute&&e.getAttribute(\"class\")||\"\"}function xt(e){return Array.isArray(e)?e:\"string\"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&\" \"+vt(i)+\" \"){a=0;while(o=t[a++])r.indexOf(\" \"+o+\" \")<0&&(r+=o+\" \");i!==(s=vt(r))&&n.setAttribute(\"class\",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr(\"class\",\"\");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&\" \"+vt(i)+\" \"){a=0;while(o=t[a++])while(r.indexOf(\" \"+o+\" \")>-1)r=r.replace(\" \"+o+\" \",\" \");i!==(s=vt(r))&&n.setAttribute(\"class\",s)}return this},toggleClass:function(e,t){var n=typeof e,r=\"string\"===n||Array.isArray(e);return\"boolean\"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&\"boolean\"!==n||((t=mt(this))&&J.set(this,\"__className__\",t),this.setAttribute&&this.setAttribute(\"class\",t||!1===e?\"\":J.get(this,\"__className__\")||\"\"))})},hasClass:function(e){var t,n,r=0;t=\" \"+e+\" \";while(n=this[r++])if(1===n.nodeType&&(\" \"+vt(mt(n))+\" \").indexOf(t)>-1)return!0;return!1}});var bt=/\\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i=\"\":\"number\"==typeof i?i+=\"\":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?\"\":e+\"\"})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&\"set\"in t&&void 0!==t.set(this,i,\"value\")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&\"get\"in t&&void 0!==(n=t.get(i,\"value\"))?n:\"string\"==typeof(n=i.value)?n.replace(bt,\"\"):null==n?\"\":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,\"value\");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a=\"select-one\"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!N(n.parentNode,\"optgroup\"))){if(t=w(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=w.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=w.inArray(w.valHooks.option.get(r),o)>-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each([\"radio\",\"checkbox\"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute(\"value\")?\"on\":e.value})}),h.focusin=\"onfocusin\"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,\"type\")?t.type:t,x=f.call(t,\"namespace\")?t.namespace.split(\".\"):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(\".\")>-1&&(m=(x=m.split(\".\")).shift(),x.sort()),c=m.indexOf(\":\")<0&&\"on\"+m,t=t[w.expando]?t:new w.Event(m,\"object\"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join(\".\"),t.rnamespace=t.namespace?new RegExp(\"(^|\\\\.)\"+x.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,\"events\")||{})[t.type]&&J.get(s,\"handle\"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:\"focusin\",blur:\"focusout\"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\\?/;w.parseXML=function(t){var n;if(!t||\"string\"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,\"text/xml\")}catch(e){n=void 0}return n&&!n.getElementsByTagName(\"parsererror\").length||w.error(\"Invalid XML: \"+t),n};var St=/\\[\\]$/,Dt=/\\r?\\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+\"[\"+(\"object\"==typeof i&&null!=i?t:\"\")+\"]\",i,n,r)});else if(n||\"object\"!==x(t))r(e,t);else for(i in t)jt(e+\"[\"+i+\"]\",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(null==n?\"\":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join(\"&\")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,\"elements\");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(\":disabled\")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,\"\\r\\n\")}}):{name:t.name,value:n.replace(Dt,\"\\r\\n\")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\\/\\//,It={},Wt={},$t=\"*/\".concat(\"*\"),Bt=r.createElement(\"a\");Bt.href=Ct.href;function Ft(e){return function(t,n){\"string\"!=typeof t&&(n=t,t=\"*\");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])\"+\"===r[0]?(r=r.slice(1)||\"*\",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return\"string\"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i[\"*\"]&&a(\"*\")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while(\"*\"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader(\"Content-Type\"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+\" \"+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if(\"*\"===o)o=u;else if(\"*\"!==u&&u!==o){if(!(a=l[u+\" \"+o]||l[\"* \"+o]))for(i in l)if((s=i.split(\" \"))[1]===o&&(a=l[u+\" \"+s[0]]||l[\"* \"+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e[\"throws\"])t=a(t);else try{t=a(t)}catch(e){return{state:\"parsererror\",error:a?e:\"No conversion from \"+u+\" to \"+o}}}return{state:\"success\",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:\"GET\",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":$t,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":JSON.parse,\"text xml\":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){\"object\"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks(\"once memory\"),x=h.statusCode||{},b={},T={},C=\"canceled\",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+\"\").replace(Rt,Ct.protocol+\"//\"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||\"*\").toLowerCase().match(M)||[\"\"],null==h.crossDomain){l=r.createElement(\"a\");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+\"//\"+Bt.host!=l.protocol+\"//\"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&\"string\"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger(\"ajaxStart\"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,\"\"),h.hasContent?h.data&&h.processData&&0===(h.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&(h.data=h.data.replace(qt,\"+\")):(d=h.url.slice(o.length),h.data&&(h.processData||\"string\"==typeof h.data)&&(o+=(kt.test(o)?\"&\":\"?\")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,\"$1\"),d=(kt.test(o)?\"&\":\"?\")+\"_=\"+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader(\"If-Modified-Since\",w.lastModified[o]),w.etag[o]&&E.setRequestHeader(\"If-None-Match\",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader(\"Content-Type\",h.contentType),E.setRequestHeader(\"Accept\",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+(\"*\"!==h.dataTypes[0]?\", \"+$t+\"; q=0.01\":\"\"):h.accepts[\"*\"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C=\"abort\",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger(\"ajaxSend\",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort(\"timeout\")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,\"No Transport\");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||\"\",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader(\"Last-Modified\"))&&(w.lastModified[o]=T),(T=E.getResponseHeader(\"etag\"))&&(w.etag[o]=T)),204===t||\"HEAD\"===h.type?C=\"nocontent\":304===t?C=\"notmodified\":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C=\"error\",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+\"\",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?\"ajaxSuccess\":\"ajaxError\",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger(\"ajaxComplete\",[E,h]),--w.active||w.event.trigger(\"ajaxStop\")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,\"json\")},getScript:function(e,t){return w.get(e,void 0,t,\"script\")}}),w.each([\"get\",\"post\"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,\"throws\":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not(\"body\").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&\"withCredentials\"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i[\"X-Requested-With\"]||(i[\"X-Requested-With\"]=\"XMLHttpRequest\");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,\"abort\"===e?s.abort():\"error\"===e?\"number\"!=typeof s.status?o(0,\"error\"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,\"text\"!==(s.responseType||\"text\")||\"string\"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n(\"error\"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n(\"abort\");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter(\"script\",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\")}),w.ajaxTransport(\"script\",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(\"<script>\").prop({charset:e.scriptCharset,src:e.url}).on(\"load error\",n=function(e){t.remove(),n=null,e&&o(\"error\"===e.type?404:200,e.type)}),r.head.appendChild(t[0])},abort:function(){n&&n()}}}});var Yt=[],Qt=/(=)\\?(?=&|$)|\\?\\?/;w.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var e=Yt.pop()||w.expando+\"_\"+Et++;return this[e]=!0,e}}),w.ajaxPrefilter(\"json jsonp\",function(t,n,r){var i,o,a,s=!1!==t.jsonp&&(Qt.test(t.url)?\"url\":\"string\"==typeof t.data&&0===(t.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Qt.test(t.data)&&\"data\");if(s||\"jsonp\"===t.dataTypes[0])return i=t.jsonpCallback=g(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(Qt,\"$1\"+i):!1!==t.jsonp&&(t.url+=(kt.test(t.url)?\"&\":\"?\")+t.jsonp+\"=\"+i),t.converters[\"script json\"]=function(){return a||w.error(i+\" was not called\"),a[0]},t.dataTypes[0]=\"json\",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?w(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,Yt.push(i)),a&&g(o)&&o(a[0]),a=o=void 0}),\"script\"}),h.createHTMLDocument=function(){var e=r.implementation.createHTMLDocument(\"\").body;return e.innerHTML=\"<form></form><form></form>\",2===e.childNodes.length}(),w.parseHTML=function(e,t,n){if(\"string\"!=typeof e)return[];\"boolean\"==typeof t&&(n=t,t=!1);var i,o,a;return t||(h.createHTMLDocument?((i=(t=r.implementation.createHTMLDocument(\"\")).createElement(\"base\")).href=r.location.href,t.head.appendChild(i)):t=r),o=A.exec(e),a=!n&&[],o?[t.createElement(o[1])]:(o=xe([e],t,a),a&&a.length&&w(a).remove(),w.merge([],o.childNodes))},w.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(\" \");return s>-1&&(r=vt(e.slice(s)),e=e.slice(0,s)),g(t)?(n=t,t=void 0):t&&\"object\"==typeof t&&(i=\"POST\"),a.length>0&&w.ajax({url:e,type:i||\"GET\",dataType:\"html\",data:t}).done(function(e){o=arguments,a.html(r?w(\"<div>\").append(w.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},w.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(e,t){w.fn[t]=function(e){return this.on(t,e)}}),w.expr.pseudos.animated=function(e){return w.grep(w.timers,function(t){return e===t.elem}).length},w.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=w.css(e,\"position\"),f=w(e),p={};\"static\"===c&&(e.style.position=\"relative\"),s=f.offset(),o=w.css(e,\"top\"),u=w.css(e,\"left\"),(l=(\"absolute\"===c||\"fixed\"===c)&&(o+u).indexOf(\"auto\")>-1)?(a=(r=f.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),g(t)&&(t=t.call(e,n,w.extend({},s))),null!=t.top&&(p.top=t.top-s.top+a),null!=t.left&&(p.left=t.left-s.left+i),\"using\"in t?t.using.call(e,p):f.css(p)}},w.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){w.offset.setOffset(this,e,t)});var t,n,r=this[0];if(r)return r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if(\"fixed\"===w.css(r,\"position\"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&\"static\"===w.css(e,\"position\"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=w(e).offset()).top+=w.css(e,\"borderTopWidth\",!0),i.left+=w.css(e,\"borderLeftWidth\",!0))}return{top:t.top-i.top-w.css(r,\"marginTop\",!0),left:t.left-i.left-w.css(r,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&\"static\"===w.css(e,\"position\"))e=e.offsetParent;return e||be})}}),w.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(e,t){var n=\"pageYOffset\"===t;w.fn[e]=function(r){return z(this,function(e,r,i){var o;if(y(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),w.each([\"top\",\"left\"],function(e,t){w.cssHooks[t]=_e(h.pixelPosition,function(e,n){if(n)return n=Fe(e,t),We.test(n)?w(e).position()[t]+\"px\":n})}),w.each({Height:\"height\",Width:\"width\"},function(e,t){w.each({padding:\"inner\"+e,content:t,\"\":\"outer\"+e},function(n,r){w.fn[r]=function(i,o){var a=arguments.length&&(n||\"boolean\"!=typeof i),s=n||(!0===i||!0===o?\"margin\":\"border\");return z(this,function(t,n,i){var o;return y(t)?0===r.indexOf(\"outer\")?t[\"inner\"+e]:t.document.documentElement[\"client\"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body[\"scroll\"+e],o[\"scroll\"+e],t.body[\"offset\"+e],o[\"offset\"+e],o[\"client\"+e])):void 0===i?w.css(t,n,s):w.style(t,n,i,s)},t,a?i:void 0,a)}})}),w.each(\"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu\".split(\" \"),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)}}),w.proxy=function(e,t){var n,r,i;if(\"string\"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=N,w.isFunction=g,w.isWindow=y,w.camelCase=G,w.type=x,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return(\"number\"===t||\"string\"===t)&&!isNaN(e-parseFloat(e))},\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return w});var Jt=e.jQuery,Kt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=Kt),t&&e.jQuery===w&&(e.jQuery=Jt),w},t||(e.jQuery=e.$=w),w});\n\n\n/* ---- lib/RateLimit.coffee ---- */\n\n\n(function() {\n  var call_after_interval, limits;\n\n  limits = {};\n\n  call_after_interval = {};\n\n  window.RateLimit = function(interval, fn) {\n    if (!limits[fn]) {\n      call_after_interval[fn] = false;\n      fn();\n      return limits[fn] = setTimeout((function() {\n        if (call_after_interval[fn]) {\n          fn();\n        }\n        delete limits[fn];\n        return delete call_after_interval[fn];\n      }), interval);\n    } else {\n      return call_after_interval[fn] = true;\n    }\n  };\n\n}).call(this);\n\n/* ---- lib/Translate.coffee ---- */\n\n\n(function() {\n  window._ = function(s) {\n    return s;\n  };\n\n}).call(this);\n\n/* ---- lib/ZeroWebsocket.coffee ---- */\n\n\n(function() {\n  var ZeroWebsocket,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    slice = [].slice;\n\n  ZeroWebsocket = (function() {\n    function ZeroWebsocket(url) {\n      this.onCloseWebsocket = bind(this.onCloseWebsocket, this);\n      this.onErrorWebsocket = bind(this.onErrorWebsocket, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      this.log = bind(this.log, this);\n      this.response = bind(this.response, this);\n      this.route = bind(this.route, this);\n      this.onMessage = bind(this.onMessage, this);\n      this.url = url;\n      this.next_message_id = 1;\n      this.waiting_cb = {};\n      this.init();\n    }\n\n    ZeroWebsocket.prototype.init = function() {\n      return this;\n    };\n\n    ZeroWebsocket.prototype.connect = function() {\n      this.ws = new WebSocket(this.url);\n      this.ws.onmessage = this.onMessage;\n      this.ws.onopen = this.onOpenWebsocket;\n      this.ws.onerror = this.onErrorWebsocket;\n      this.ws.onclose = this.onCloseWebsocket;\n      this.connected = false;\n      return this.message_queue = [];\n    };\n\n    ZeroWebsocket.prototype.onMessage = function(e) {\n      var cmd, message;\n      message = JSON.parse(e.data);\n      cmd = message.cmd;\n      if (cmd === \"response\") {\n        if (this.waiting_cb[message.to] != null) {\n          return this.waiting_cb[message.to](message.result);\n        } else {\n          return this.log(\"Websocket callback not found:\", message);\n        }\n      } else if (cmd === \"ping\") {\n        return this.response(message.id, \"pong\");\n      } else {\n        return this.route(cmd, message);\n      }\n    };\n\n    ZeroWebsocket.prototype.route = function(cmd, message) {\n      return this.log(\"Unknown command\", message);\n    };\n\n    ZeroWebsocket.prototype.response = function(to, result) {\n      return this.send({\n        \"cmd\": \"response\",\n        \"to\": to,\n        \"result\": result\n      });\n    };\n\n    ZeroWebsocket.prototype.cmd = function(cmd, params, cb) {\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      return this.send({\n        \"cmd\": cmd,\n        \"params\": params\n      }, cb);\n    };\n\n    ZeroWebsocket.prototype.send = function(message, cb) {\n      if (cb == null) {\n        cb = null;\n      }\n      if (message.id == null) {\n        message.id = this.next_message_id;\n        this.next_message_id += 1;\n      }\n      if (this.connected) {\n        this.ws.send(JSON.stringify(message));\n      } else {\n        this.log(\"Not connected, adding message to queue\");\n        this.message_queue.push(message);\n      }\n      if (cb) {\n        return this.waiting_cb[message.id] = cb;\n      }\n    };\n\n    ZeroWebsocket.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      return console.log.apply(console, [\"[ZeroWebsocket]\"].concat(slice.call(args)));\n    };\n\n    ZeroWebsocket.prototype.onOpenWebsocket = function(e) {\n      var i, len, message, ref;\n      this.log(\"Open\");\n      this.connected = true;\n      ref = this.message_queue;\n      for (i = 0, len = ref.length; i < len; i++) {\n        message = ref[i];\n        this.ws.send(JSON.stringify(message));\n      }\n      this.message_queue = [];\n      if (this.onOpen != null) {\n        return this.onOpen(e);\n      }\n    };\n\n    ZeroWebsocket.prototype.onErrorWebsocket = function(e) {\n      this.log(\"Error\", e);\n      if (this.onError != null) {\n        return this.onError(e);\n      }\n    };\n\n    ZeroWebsocket.prototype.onCloseWebsocket = function(e, reconnect) {\n      if (reconnect == null) {\n        reconnect = 10000;\n      }\n      this.log(\"Closed\", e);\n      this.connected = false;\n      if (e && e.code === 1000 && e.wasClean === false) {\n        this.log(\"Server error, please reload the page\", e.wasClean);\n      } else {\n        setTimeout(((function(_this) {\n          return function() {\n            _this.log(\"Reconnecting...\");\n            return _this.connect();\n          };\n        })(this)), reconnect);\n      }\n      if (this.onClose != null) {\n        return this.onClose(e);\n      }\n    };\n\n    return ZeroWebsocket;\n\n  })();\n\n  window.ZeroWebsocket = ZeroWebsocket;\n\n}).call(this);\n\n/* ---- lib/jquery.cssanim.js ---- */\n\n\njQuery.cssHooks['scale'] = {\n\tget: function(elem, computed) {\n\t\tvar match = window.getComputedStyle(elem)[transform_property].match(\"[0-9\\.]+\")\n\t\tif (match) {\n\t\t\tvar scale = parseFloat(match[0])\n\t\t\treturn scale\n\t\t} else {\n\t\t\treturn 1.0\n\t\t}\n\t},\n\tset: function(elem, val) {\n\t\t//var transforms = $(elem).css(\"transform\").match(/[0-9\\.]+/g)\n\t\tvar transforms = window.getComputedStyle(elem)[transform_property].match(/[0-9\\.]+/g)\n\t\tif (transforms) {\n\t\t\ttransforms[0] = val\n\t\t\ttransforms[3] = val\n\t\t\t//$(elem).css(\"transform\", 'matrix('+transforms.join(\", \")+\")\")\n\t\t\telem.style[transform_property] = 'matrix('+transforms.join(\", \")+')'\n\t\t} else {\n\t\t\telem.style[transform_property] = \"scale(\"+val+\")\"\n\t\t}\n\t}\n}\n\njQuery.fx.step.scale = function(fx) {\n\tjQuery.cssHooks['scale'].set(fx.elem, fx.now)\n};\n\n\nif (window.getComputedStyle(document.body).transform) {\n\ttransform_property = \"transform\"\n} else {\n\ttransform_property = \"webkitTransform\"\n}\n\n\n/* ---- lib/jquery.csslater.coffee ---- */\n\n\n(function() {\n  jQuery.fn.readdClass = function(class_name) {\n    var elem;\n    elem = this;\n    elem.removeClass(class_name);\n    setTimeout((function() {\n      return elem.addClass(class_name);\n    }), 1);\n    return this;\n  };\n\n  jQuery.fn.removeLater = function(time) {\n    var elem;\n    if (time == null) {\n      time = 500;\n    }\n    elem = this;\n    setTimeout((function() {\n      return elem.remove();\n    }), time);\n    return this;\n  };\n\n  jQuery.fn.hideLater = function(time) {\n    var elem;\n    if (time == null) {\n      time = 500;\n    }\n    elem = this;\n    setTimeout((function() {\n      if (elem.css(\"opacity\") === 0) {\n        return elem.css(\"display\", \"none\");\n      }\n    }), time);\n    return this;\n  };\n\n  jQuery.fn.addClassLater = function(class_name, time) {\n    var elem;\n    if (time == null) {\n      time = 5;\n    }\n    elem = this;\n    setTimeout((function() {\n      return elem.addClass(class_name);\n    }), time);\n    return this;\n  };\n\n  jQuery.fn.cssLater = function(name, val, time) {\n    var elem;\n    if (time == null) {\n      time = 500;\n    }\n    elem = this;\n    setTimeout((function() {\n      return elem.css(name, val);\n    }), time);\n    return this;\n  };\n\n}).call(this);\n\n/* ---- lib/jquery.easing.js ---- */\n\n\n/*\n * jQuery Easing v1.4.1 - http://gsgd.co.uk/sandbox/jquery/easing/\n * Open source under the BSD License.\n * Copyright © 2008 George McGinley Smith\n * All rights reserved.\n * https://raw.github.com/gdsmith/jquery-easing/master/LICENSE\n*/\n\n(function (factory) {\n\tif (typeof define === \"function\" && define.amd) {\n\t\tdefine(['jquery'], function ($) {\n\t\t\treturn factory($);\n\t\t});\n\t} else if (typeof module === \"object\" && typeof module.exports === \"object\") {\n\t\texports = factory(require('jquery'));\n\t} else {\n\t\tfactory(jQuery);\n\t}\n})(function($){\n\n// Preserve the original jQuery \"swing\" easing as \"jswing\"\nif (typeof $.easing !== 'undefined') {\n\t$.easing['jswing'] = $.easing['swing'];\n}\n\nvar pow = Math.pow,\n\tsqrt = Math.sqrt,\n\tsin = Math.sin,\n\tcos = Math.cos,\n\tPI = Math.PI,\n\tc1 = 1.70158,\n\tc2 = c1 * 1.525,\n\tc3 = c1 + 1,\n\tc4 = ( 2 * PI ) / 3,\n\tc5 = ( 2 * PI ) / 4.5;\n\n// x is the fraction of animation progress, in the range 0..1\nfunction bounceOut(x) {\n\tvar n1 = 7.5625,\n\t\td1 = 2.75;\n\tif ( x < 1/d1 ) {\n\t\treturn n1*x*x;\n\t} else if ( x < 2/d1 ) {\n\t\treturn n1*(x-=(1.5/d1))*x + .75;\n\t} else if ( x < 2.5/d1 ) {\n\t\treturn n1*(x-=(2.25/d1))*x + .9375;\n\t} else {\n\t\treturn n1*(x-=(2.625/d1))*x + .984375;\n\t}\n}\n\n$.extend( $.easing,\n{\n\tdef: 'easeOutQuad',\n\tswing: function (x) {\n\t\treturn $.easing[$.easing.def](x);\n\t},\n\teaseInQuad: function (x) {\n\t\treturn x * x;\n\t},\n\teaseOutQuad: function (x) {\n\t\treturn 1 - ( 1 - x ) * ( 1 - x );\n\t},\n\teaseInOutQuad: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t2 * x * x :\n\t\t\t1 - pow( -2 * x + 2, 2 ) / 2;\n\t},\n\teaseInCubic: function (x) {\n\t\treturn x * x * x;\n\t},\n\teaseOutCubic: function (x) {\n\t\treturn 1 - pow( 1 - x, 3 );\n\t},\n\teaseInOutCubic: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t4 * x * x * x :\n\t\t\t1 - pow( -2 * x + 2, 3 ) / 2;\n\t},\n\teaseInQuart: function (x) {\n\t\treturn x * x * x * x;\n\t},\n\teaseOutQuart: function (x) {\n\t\treturn 1 - pow( 1 - x, 4 );\n\t},\n\teaseInOutQuart: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t8 * x * x * x * x :\n\t\t\t1 - pow( -2 * x + 2, 4 ) / 2;\n\t},\n\teaseInQuint: function (x) {\n\t\treturn x * x * x * x * x;\n\t},\n\teaseOutQuint: function (x) {\n\t\treturn 1 - pow( 1 - x, 5 );\n\t},\n\teaseInOutQuint: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t16 * x * x * x * x * x :\n\t\t\t1 - pow( -2 * x + 2, 5 ) / 2;\n\t},\n\teaseInSine: function (x) {\n\t\treturn 1 - cos( x * PI/2 );\n\t},\n\teaseOutSine: function (x) {\n\t\treturn sin( x * PI/2 );\n\t},\n\teaseInOutSine: function (x) {\n\t\treturn -( cos( PI * x ) - 1 ) / 2;\n\t},\n\teaseInExpo: function (x) {\n\t\treturn x === 0 ? 0 : pow( 2, 10 * x - 10 );\n\t},\n\teaseOutExpo: function (x) {\n\t\treturn x === 1 ? 1 : 1 - pow( 2, -10 * x );\n\t},\n\teaseInOutExpo: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ?\n\t\t\tpow( 2, 20 * x - 10 ) / 2 :\n\t\t\t( 2 - pow( 2, -20 * x + 10 ) ) / 2;\n\t},\n\teaseInCirc: function (x) {\n\t\treturn 1 - sqrt( 1 - pow( x, 2 ) );\n\t},\n\teaseOutCirc: function (x) {\n\t\treturn sqrt( 1 - pow( x - 1, 2 ) );\n\t},\n\teaseInOutCirc: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t( 1 - sqrt( 1 - pow( 2 * x, 2 ) ) ) / 2 :\n\t\t\t( sqrt( 1 - pow( -2 * x + 2, 2 ) ) + 1 ) / 2;\n\t},\n\teaseInElastic: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 :\n\t\t\t-pow( 2, 10 * x - 10 ) * sin( ( x * 10 - 10.75 ) * c4 );\n\t},\n\teaseOutElastic: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 :\n\t\t\tpow( 2, -10 * x ) * sin( ( x * 10 - 0.75 ) * c4 ) + 1;\n\t},\n\teaseInOutElastic: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ?\n\t\t\t-( pow( 2, 20 * x - 10 ) * sin( ( 20 * x - 11.125 ) * c5 )) / 2 :\n\t\t\tpow( 2, -20 * x + 10 ) * sin( ( 20 * x - 11.125 ) * c5 ) / 2 + 1;\n\t},\n\teaseInBack: function (x) {\n\t\treturn c3 * x * x * x - c1 * x * x;\n\t},\n\teaseOutBack: function (x) {\n\t\treturn 1 + c3 * pow( x - 1, 3 ) + c1 * pow( x - 1, 2 );\n\t},\n\teaseInOutBack: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t( pow( 2 * x, 2 ) * ( ( c2 + 1 ) * 2 * x - c2 ) ) / 2 :\n\t\t\t( pow( 2 * x - 2, 2 ) *( ( c2 + 1 ) * ( x * 2 - 2 ) + c2 ) + 2 ) / 2;\n\t},\n\teaseInBounce: function (x) {\n\t\treturn 1 - bounceOut( 1 - x );\n\t},\n\teaseOutBounce: bounceOut,\n\teaseInOutBounce: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t( 1 - bounceOut( 1 - 2 * x ) ) / 2 :\n\t\t\t( 1 + bounceOut( 2 * x - 1 ) ) / 2;\n\t}\n});\n\n});\n\n\n/* ---- Fixbutton.coffee ---- */\n\n\n(function() {\n  var Fixbutton;\n\n  Fixbutton = (function() {\n    function Fixbutton() {\n      this.dragging = false;\n      $(\".fixbutton-bg\").on(\"mouseover\", function() {\n        $(\".fixbutton-bg\").stop().animate({\n          \"scale\": 0.7\n        }, 800, \"easeOutElastic\");\n        $(\".fixbutton-burger\").stop().animate({\n          \"opacity\": 1.5,\n          \"left\": 0\n        }, 800, \"easeOutElastic\");\n        return $(\".fixbutton-text\").stop().animate({\n          \"opacity\": 0,\n          \"left\": 20\n        }, 300, \"easeOutCubic\");\n      });\n      $(\".fixbutton-bg\").on(\"mouseout\", function() {\n        if ($(\".fixbutton\").hasClass(\"dragging\")) {\n          return true;\n        }\n        $(\".fixbutton-bg\").stop().animate({\n          \"scale\": 0.6\n        }, 300, \"easeOutCubic\");\n        $(\".fixbutton-burger\").stop().animate({\n          \"opacity\": 0,\n          \"left\": -20\n        }, 300, \"easeOutCubic\");\n        return $(\".fixbutton-text\").stop().animate({\n          \"opacity\": 0.9,\n          \"left\": 0\n        }, 300, \"easeOutBack\");\n      });\n\n      /*$(\".fixbutton-bg\").on \"click\", ->\n      \t\t\treturn false\n       */\n      $(\".fixbutton-bg\").on(\"mousedown\", function() {});\n      $(\".fixbutton-bg\").on(\"mouseup\", function() {});\n    }\n\n    return Fixbutton;\n\n  })();\n\n  window.Fixbutton = Fixbutton;\n\n}).call(this);\n\n/* ---- Infopanel.coffee ---- */\n\n\n(function() {\n  var Infopanel,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };\n\n  Infopanel = (function() {\n    function Infopanel(elem) {\n      this.elem = elem;\n      this.setAction = bind(this.setAction, this);\n      this.setClosedNum = bind(this.setClosedNum, this);\n      this.setTitle = bind(this.setTitle, this);\n      this.open = bind(this.open, this);\n      this.close = bind(this.close, this);\n      this.hide = bind(this.hide, this);\n      this.updateEvents = bind(this.updateEvents, this);\n      this.unfold = bind(this.unfold, this);\n      this.show = bind(this.show, this);\n      this.visible = false;\n    }\n\n    Infopanel.prototype.show = function(closed) {\n      if (closed == null) {\n        closed = false;\n      }\n      this.elem.parent().addClass(\"visible\");\n      if (closed) {\n        return this.close();\n      } else {\n        return this.open();\n      }\n    };\n\n    Infopanel.prototype.unfold = function() {\n      this.elem.toggleClass(\"unfolded\");\n      return false;\n    };\n\n    Infopanel.prototype.updateEvents = function() {\n      this.elem.off(\"click\");\n      this.elem.find(\".close\").off(\"click\");\n      this.elem.find(\".line\").off(\"click\");\n      this.elem.find(\".line\").on(\"click\", this.unfold);\n      if (this.elem.hasClass(\"closed\")) {\n        return this.elem.on(\"click\", (function(_this) {\n          return function() {\n            _this.onOpened();\n            return _this.open();\n          };\n        })(this));\n      } else {\n        return this.elem.find(\".close\").on(\"click\", (function(_this) {\n          return function() {\n            _this.onClosed();\n            return _this.close();\n          };\n        })(this));\n      }\n    };\n\n    Infopanel.prototype.hide = function() {\n      return this.elem.parent().removeClass(\"visible\");\n    };\n\n    Infopanel.prototype.close = function() {\n      this.elem.addClass(\"closed\");\n      this.updateEvents();\n      return false;\n    };\n\n    Infopanel.prototype.open = function() {\n      this.elem.removeClass(\"closed\");\n      this.updateEvents();\n      return false;\n    };\n\n    Infopanel.prototype.setTitle = function(line1, line2) {\n      this.elem.find(\".line-1\").text(line1);\n      return this.elem.find(\".line-2\").text(line2);\n    };\n\n    Infopanel.prototype.setClosedNum = function(num) {\n      return this.elem.find(\".closed-num\").text(num);\n    };\n\n    Infopanel.prototype.setAction = function(title, func) {\n      return this.elem.find(\".button\").text(title).off(\"click\").on(\"click\", func);\n    };\n\n    return Infopanel;\n\n  })();\n\n  window.Infopanel = Infopanel;\n\n}).call(this);\n\n/* ---- Loading.coffee ---- */\n\n\n(function() {\n  var Loading,\n    slice = [].slice;\n\n  Loading = (function() {\n    function Loading(wrapper) {\n      this.wrapper = wrapper;\n      if (window.show_loadingscreen) {\n        this.showScreen();\n      }\n      this.timer_hide = null;\n      this.timer_set = null;\n    }\n\n    Loading.prototype.setProgress = function(percent) {\n      if (this.timer_hide) {\n        clearInterval(this.timer_hide);\n      }\n      return this.timer_set = RateLimit(500, function() {\n        return $(\".progressbar\").css({\n          \"transform\": \"scaleX(\" + (parseInt(percent * 100) / 100) + \")\"\n        }).css(\"opacity\", \"1\").css(\"display\", \"block\");\n      });\n    };\n\n    Loading.prototype.hideProgress = function() {\n      this.log(\"hideProgress\");\n      if (this.timer_set) {\n        clearInterval(this.timer_set);\n      }\n      return this.timer_hide = setTimeout(((function(_this) {\n        return function() {\n          return $(\".progressbar\").css({\n            \"transform\": \"scaleX(1)\"\n          }).css(\"opacity\", \"0\").hideLater(1000);\n        };\n      })(this)), 300);\n    };\n\n    Loading.prototype.showScreen = function() {\n      $(\".loadingscreen\").css(\"display\", \"block\").addClassLater(\"ready\");\n      this.screen_visible = true;\n      return this.printLine(\"&nbsp;&nbsp;&nbsp;Connecting...\");\n    };\n\n    Loading.prototype.showTooLarge = function(site_info) {\n      var button, line;\n      this.log(\"Displaying large site confirmation\");\n      if ($(\".console .button-setlimit\").length === 0) {\n        line = this.printLine(\"Site size: <b>\" + (parseInt(site_info.settings.size / 1024 / 1024)) + \"MB</b> is larger than default allowed \" + (parseInt(site_info.size_limit)) + \"MB\", \"warning\");\n        button = $(\"<a href='#Set+limit' class='button button-setlimit'>\" + (\"Open site and set size limit to \" + site_info.next_size_limit + \"MB\") + \"</a>\");\n        button.on(\"click\", (function(_this) {\n          return function() {\n            button.addClass(\"loading\");\n            return _this.wrapper.setSizeLimit(site_info.next_size_limit);\n          };\n        })(this));\n        line.after(button);\n        return setTimeout(((function(_this) {\n          return function() {\n            return _this.printLine('Ready.');\n          };\n        })(this)), 100);\n      }\n    };\n\n    Loading.prototype.showTrackerTorBridge = function(server_info) {\n      var button, line;\n      if ($(\".console .button-settrackerbridge\").length === 0 && !server_info.tor_use_meek_bridges) {\n        line = this.printLine(\"Tracker connection error detected.\", \"error\");\n        button = $(\"<a href='#Enable+Tor+bridges' class='button button-settrackerbridge'>\" + \"Use Tor meek bridges for tracker connections\" + \"</a>\");\n        button.on(\"click\", (function(_this) {\n          return function() {\n            button.addClass(\"loading\");\n            _this.wrapper.ws.cmd(\"configSet\", [\"tor_use_bridges\", \"\"]);\n            _this.wrapper.ws.cmd(\"configSet\", [\"trackers_proxy\", \"tor\"]);\n            _this.wrapper.ws.cmd(\"siteUpdate\", {\n              address: _this.wrapper.site_info.address,\n              announce: true\n            });\n            _this.wrapper.reloadIframe();\n            return false;\n          };\n        })(this));\n        line.after(button);\n        if (!server_info.tor_has_meek_bridges) {\n          button.addClass(\"disabled\");\n          return this.printLine(\"No meek bridge support in your client, please <a href='https://github.com/HelloZeroNet/ZeroNet#how-to-join'>download the latest bundle</a>.\", \"warning\");\n        }\n      }\n    };\n\n    Loading.prototype.hideScreen = function() {\n      this.log(\"hideScreen\");\n      if (!$(\".loadingscreen\").hasClass(\"done\")) {\n        if (this.screen_visible) {\n          $(\".loadingscreen\").addClass(\"done\").removeLater(2000);\n        } else {\n          $(\".loadingscreen\").remove();\n        }\n      }\n      return this.screen_visible = false;\n    };\n\n    Loading.prototype.print = function(text, type) {\n      var last_line;\n      if (type == null) {\n        type = \"normal\";\n      }\n      if (!this.screen_visible) {\n        return false;\n      }\n      $(\".loadingscreen .console .cursor\").remove();\n      last_line = $(\".loadingscreen .console .console-line:last-child\");\n      if (type === \"error\") {\n        text = \"<span class='console-error'>\" + text + \"</span>\";\n      }\n      return last_line.html(last_line.html() + text);\n    };\n\n    Loading.prototype.printLine = function(text, type) {\n      var line;\n      if (type == null) {\n        type = \"normal\";\n      }\n      if (!this.screen_visible) {\n        return false;\n      }\n      $(\".loadingscreen .console .cursor\").remove();\n      if (type === \"error\") {\n        text = \"<span class='console-error'>\" + text + \"</span>\";\n      } else {\n        text = text + \"<span class='cursor'> </span>\";\n      }\n      line = $(\"<div class='console-line'>\" + text + \"</div>\").appendTo(\".loadingscreen .console\");\n      if (type === \"warning\") {\n        line.addClass(\"console-warning\");\n      }\n      return line;\n    };\n\n    Loading.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      return console.log.apply(console, [\"[Loading]\"].concat(slice.call(args)));\n    };\n\n    return Loading;\n\n  })();\n\n  window.Loading = Loading;\n\n}).call(this);\n\n/* ---- Notifications.coffee ---- */\n\n\n(function() {\n  var Notifications,\n    slice = [].slice;\n\n  Notifications = (function() {\n    function Notifications(elem1) {\n      this.elem = elem1;\n      this;\n    }\n\n    Notifications.prototype.test = function() {\n      setTimeout(((function(_this) {\n        return function() {\n          _this.add(\"connection\", \"error\", \"Connection lost to <b>UiServer</b> on <b>localhost</b>!\");\n          return _this.add(\"message-Anyone\", \"info\", \"New  from <b>Anyone</b>.\");\n        };\n      })(this)), 1000);\n      return setTimeout(((function(_this) {\n        return function() {\n          return _this.add(\"connection\", \"done\", \"<b>UiServer</b> connection recovered.\", 5000);\n        };\n      })(this)), 3000);\n    };\n\n    Notifications.prototype.add = function(id, type, body, timeout) {\n      var elem, i, len, ref, width;\n      if (timeout == null) {\n        timeout = 0;\n      }\n      id = id.replace(/[^A-Za-z0-9-]/g, \"\");\n      ref = $(\".notification-\" + id);\n      for (i = 0, len = ref.length; i < len; i++) {\n        elem = ref[i];\n        this.close($(elem));\n      }\n      elem = $(\".notification.template\", this.elem).clone().removeClass(\"template\");\n      elem.addClass(\"notification-\" + type).addClass(\"notification-\" + id);\n      if (type === \"progress\") {\n        elem.addClass(\"notification-done\");\n      }\n      if (type === \"error\") {\n        $(\".notification-icon\", elem).html(\"!\");\n      } else if (type === \"done\") {\n        $(\".notification-icon\", elem).html(\"<div class='icon-success'></div>\");\n      } else if (type === \"progress\") {\n        $(\".notification-icon\", elem).html(\"<div class='icon-success'></div>\");\n      } else if (type === \"ask\") {\n        $(\".notification-icon\", elem).html(\"?\");\n      } else {\n        $(\".notification-icon\", elem).html(\"i\");\n      }\n      if (typeof body === \"string\") {\n        $(\".body\", elem).html(\"<div class='message'><span class='multiline'>\" + body + \"</span></div>\");\n      } else {\n        $(\".body\", elem).html(\"\").append(body);\n      }\n      elem.appendTo(this.elem);\n      if (timeout) {\n        $(\".close\", elem).remove();\n        setTimeout(((function(_this) {\n          return function() {\n            return _this.close(elem);\n          };\n        })(this)), timeout);\n      }\n      width = Math.min(elem.outerWidth() + 50, 580);\n      if (!timeout) {\n        width += 20;\n      }\n      if (elem.outerHeight() > 55) {\n        elem.addClass(\"long\");\n      }\n      elem.css({\n        \"width\": \"50px\",\n        \"transform\": \"scale(0.01)\"\n      });\n      elem.animate({\n        \"scale\": 1\n      }, 800, \"easeOutElastic\");\n      elem.animate({\n        \"width\": width\n      }, 700, \"easeInOutCubic\");\n      $(\".body\", elem).css({\n        \"width\": width - 50\n      });\n      $(\".body\", elem).cssLater(\"box-shadow\", \"0px 0px 5px rgba(0,0,0,0.1)\", 1000);\n      $(\".close, .button\", elem).on(\"click\", (function(_this) {\n        return function() {\n          _this.close(elem);\n          return false;\n        };\n      })(this));\n      $(\".select\", elem).on(\"click\", (function(_this) {\n        return function() {\n          return _this.close(elem);\n        };\n      })(this));\n      $(\"input\", elem).on(\"keyup\", (function(_this) {\n        return function(e) {\n          if (e.keyCode === 13) {\n            return _this.close(elem);\n          }\n        };\n      })(this));\n      return elem;\n    };\n\n    Notifications.prototype.close = function(elem) {\n      elem.stop().animate({\n        \"width\": 0,\n        \"opacity\": 0\n      }, 700, \"easeInOutCubic\");\n      return elem.slideUp(300, (function() {\n        return elem.remove();\n      }));\n    };\n\n    Notifications.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      return console.log.apply(console, [\"[Notifications]\"].concat(slice.call(args)));\n    };\n\n    return Notifications;\n\n  })();\n\n  window.Notifications = Notifications;\n\n}).call(this);\n\n/* ---- Wrapper.coffee ---- */\n\n\n(function() {\n  var Wrapper, origin, proto, ws_url,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },\n    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },\n    slice = [].slice;\n\n  Wrapper = (function() {\n    function Wrapper(ws_url) {\n      this.reloadIframe = bind(this.reloadIframe, this);\n      this.setSizeLimit = bind(this.setSizeLimit, this);\n      this.updateModifiedPanel = bind(this.updateModifiedPanel, this);\n      this.sitePublish = bind(this.sitePublish, this);\n      this.siteSign = bind(this.siteSign, this);\n      this.onWrapperLoad = bind(this.onWrapperLoad, this);\n      this.onPageLoad = bind(this.onPageLoad, this);\n      this.onCloseWebsocket = bind(this.onCloseWebsocket, this);\n      this.onOpenWebsocket = bind(this.onOpenWebsocket, this);\n      this.handleMessage = bind(this.handleMessage, this);\n      this.cmd = bind(this.cmd, this);\n      this.onMessageInner = bind(this.onMessageInner, this);\n      this.handleMessageWebsocket = bind(this.handleMessageWebsocket, this);\n      this.onMessageWebsocket = bind(this.onMessageWebsocket, this);\n      this.verifyEvent = bind(this.verifyEvent, this);\n      this.log(\"Created!\");\n      this.loading = new Loading(this);\n      this.notifications = new Notifications($(\".notifications\"));\n      this.infopanel = new Infopanel($(\".infopanel\"));\n      this.infopanel.onClosed = (function(_this) {\n        return function() {\n          return _this.ws.cmd(\"siteSetSettingsValue\", [\"modified_files_notification\", false]);\n        };\n      })(this);\n      this.infopanel.onOpened = (function(_this) {\n        return function() {\n          return _this.ws.cmd(\"siteSetSettingsValue\", [\"modified_files_notification\", true]);\n        };\n      })(this);\n      this.fixbutton = new Fixbutton();\n      window.addEventListener(\"message\", this.onMessageInner, false);\n      this.inner = document.getElementById(\"inner-iframe\").contentWindow;\n      this.ws = new ZeroWebsocket(ws_url);\n      this.ws.next_message_id = 1000000;\n      this.ws.onOpen = this.onOpenWebsocket;\n      this.ws.onClose = this.onCloseWebsocket;\n      this.ws.onMessage = this.onMessageWebsocket;\n      this.ws.connect();\n      this.ws_error = null;\n      this.next_cmd_message_id = -1;\n      this.site_info = null;\n      this.server_info = null;\n      this.event_site_info = $.Deferred();\n      this.inner_loaded = false;\n      this.inner_ready = false;\n      this.wrapperWsInited = false;\n      this.site_error = null;\n      this.address = null;\n      this.opener_tested = false;\n      this.announcer_line = null;\n      this.web_notifications = {};\n      this.is_title_changed = false;\n      this.allowed_event_constructors = [window.MouseEvent, window.KeyboardEvent, window.PointerEvent];\n      window.onload = this.onPageLoad;\n      window.onhashchange = (function(_this) {\n        return function(e) {\n          var src;\n          _this.log(\"Hashchange\", window.location.hash);\n          if (window.location.hash) {\n            src = $(\"#inner-iframe\").attr(\"src\").replace(/#.*/, \"\") + window.location.hash;\n            return $(\"#inner-iframe\").attr(\"src\", src);\n          }\n        };\n      })(this);\n      window.onpopstate = (function(_this) {\n        return function(e) {\n          return _this.sendInner({\n            \"cmd\": \"wrapperPopState\",\n            \"params\": {\n              \"href\": document.location.href,\n              \"state\": e.state\n            }\n          });\n        };\n      })(this);\n      $(\"#inner-iframe\").focus();\n    }\n\n    Wrapper.prototype.verifyEvent = function(allowed_target, e) {\n      var ref;\n      if (!e.originalEvent.isTrusted) {\n        throw \"Event not trusted\";\n      }\n      if (ref = e.originalEvent.constructor, indexOf.call(this.allowed_event_constructors, ref) < 0) {\n        throw \"Invalid event constructor: \" + e.constructor + \" not in \" + (JSON.stringify(this.allowed_event_constructors));\n      }\n      if (e.originalEvent.currentTarget !== allowed_target[0]) {\n        throw \"Invalid event target: \" + e.originalEvent.currentTarget + \" != \" + allowed_target[0];\n      }\n    };\n\n    Wrapper.prototype.onMessageWebsocket = function(e) {\n      var message;\n      message = JSON.parse(e.data);\n      return this.handleMessageWebsocket(message);\n    };\n\n    Wrapper.prototype.handleMessageWebsocket = function(message) {\n      var cmd, id, ref, script_tag, type;\n      cmd = message.cmd;\n      if (cmd === \"response\") {\n        if (this.ws.waiting_cb[message.to] != null) {\n          return this.ws.waiting_cb[message.to](message.result);\n        } else {\n          return this.sendInner(message);\n        }\n      } else if (cmd === \"notification\") {\n        type = message.params[0];\n        id = \"notification-ws-\" + message.id;\n        if (indexOf.call(message.params[0], \"-\") >= 0) {\n          ref = message.params[0].split(\"-\"), id = ref[0], type = ref[1];\n        }\n        return this.notifications.add(id, type, message.params[1], message.params[2]);\n      } else if (cmd === \"progress\") {\n        return this.actionProgress(message);\n      } else if (cmd === \"prompt\") {\n        return this.displayPrompt(message.params[0], message.params[1], message.params[2], message.params[3], (function(_this) {\n          return function(res) {\n            return _this.ws.response(message.id, res);\n          };\n        })(this));\n      } else if (cmd === \"confirm\") {\n        return this.displayConfirm(message.params[0], message.params[1], (function(_this) {\n          return function(res) {\n            return _this.ws.response(message.id, res);\n          };\n        })(this));\n      } else if (cmd === \"setSiteInfo\") {\n        this.sendInner(message);\n        if (message.params.address === this.address) {\n          this.setSiteInfo(message.params);\n        }\n        return this.updateProgress(message.params);\n      } else if (cmd === \"setAnnouncerInfo\") {\n        this.sendInner(message);\n        if (message.params.address === this.address) {\n          this.setAnnouncerInfo(message.params);\n        }\n        return this.updateProgress(message.params);\n      } else if (cmd === \"error\") {\n        return this.notifications.add(\"notification-\" + message.id, \"error\", message.params, 0);\n      } else if (cmd === \"updating\") {\n        this.log(\"Updating: Closing websocket\");\n        this.ws.ws.close();\n        return this.ws.onCloseWebsocket(null, 4000);\n      } else if (cmd === \"redirect\") {\n        return window.top.location = message.params;\n      } else if (cmd === \"injectHtml\") {\n        return $(\"body\").append(message.params);\n      } else if (cmd === \"injectScript\") {\n        script_tag = $(\"<script>\");\n        script_tag.attr(\"nonce\", this.script_nonce);\n        script_tag.html(message.params);\n        return document.head.appendChild(script_tag[0]);\n      } else {\n        return this.sendInner(message);\n      }\n    };\n\n    Wrapper.prototype.onMessageInner = function(e) {\n      var message;\n      if (!window.postmessage_nonce_security && this.opener_tested === false) {\n        if (window.opener && window.opener !== window) {\n          this.log(\"Opener present\", window.opener);\n          this.displayOpenerDialog();\n          return false;\n        } else {\n          this.opener_tested = true;\n        }\n      }\n      message = e.data;\n      if (!message.cmd) {\n        this.log(\"Invalid message:\", message);\n        return false;\n      }\n      if (window.postmessage_nonce_security && message.wrapper_nonce !== window.wrapper_nonce) {\n        this.log(\"Message nonce error:\", message.wrapper_nonce, '!=', window.wrapper_nonce);\n        return;\n      }\n      return this.handleMessage(message);\n    };\n\n    Wrapper.prototype.cmd = function(cmd, params, cb) {\n      var message;\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      message = {};\n      message.cmd = cmd;\n      message.params = params;\n      message.id = this.next_cmd_message_id;\n      if (cb) {\n        this.ws.waiting_cb[message.id] = cb;\n      }\n      this.next_cmd_message_id -= 1;\n      return this.handleMessage(message);\n    };\n\n    Wrapper.prototype.handleMessage = function(message) {\n      var cmd, query, ref;\n      cmd = message.cmd;\n      if (cmd === \"innerReady\") {\n        this.inner_ready = true;\n        if (this.ws.ws.readyState === 1 && !this.wrapperWsInited) {\n          this.sendInner({\n            \"cmd\": \"wrapperOpenedWebsocket\"\n          });\n          return this.wrapperWsInited = true;\n        }\n      } else if (cmd === \"innerLoaded\" || cmd === \"wrapperInnerLoaded\") {\n        if (window.location.hash) {\n          $(\"#inner-iframe\")[0].src += window.location.hash;\n          return this.log(\"Added hash to location\", $(\"#inner-iframe\")[0].src);\n        }\n      } else if (cmd === \"wrapperNotification\") {\n        return this.actionNotification(message);\n      } else if (cmd === \"wrapperConfirm\") {\n        return this.actionConfirm(message);\n      } else if (cmd === \"wrapperPrompt\") {\n        return this.actionPrompt(message);\n      } else if (cmd === \"wrapperProgress\") {\n        return this.actionProgress(message);\n      } else if (cmd === \"wrapperSetViewport\") {\n        return this.actionSetViewport(message);\n      } else if (cmd === \"wrapperSetTitle\") {\n        this.log(\"wrapperSetTitle\", message.params);\n        $(\"head title\").text(message.params);\n        return this.is_title_changed = true;\n      } else if (cmd === \"wrapperReload\") {\n        return this.actionReload(message);\n      } else if (cmd === \"wrapperGetLocalStorage\") {\n        return this.actionGetLocalStorage(message);\n      } else if (cmd === \"wrapperSetLocalStorage\") {\n        return this.actionSetLocalStorage(message);\n      } else if (cmd === \"wrapperPushState\") {\n        query = this.toRelativeQuery(message.params[2]);\n        return window.history.pushState(message.params[0], message.params[1], query);\n      } else if (cmd === \"wrapperReplaceState\") {\n        query = this.toRelativeQuery(message.params[2]);\n        return window.history.replaceState(message.params[0], message.params[1], query);\n      } else if (cmd === \"wrapperGetState\") {\n        return this.sendInner({\n          \"cmd\": \"response\",\n          \"to\": message.id,\n          \"result\": window.history.state\n        });\n      } else if (cmd === \"wrapperGetAjaxKey\") {\n        return this.sendInner({\n          \"cmd\": \"response\",\n          \"to\": message.id,\n          \"result\": window.ajax_key\n        });\n      } else if (cmd === \"wrapperOpenWindow\") {\n        return this.actionOpenWindow(message.params);\n      } else if (cmd === \"wrapperPermissionAdd\") {\n        return this.actionPermissionAdd(message);\n      } else if (cmd === \"wrapperRequestFullscreen\") {\n        return this.actionRequestFullscreen();\n      } else if (cmd === \"wrapperWebNotification\") {\n        return this.actionWebNotification(message);\n      } else if (cmd === \"wrapperCloseWebNotification\") {\n        return this.actionCloseWebNotification(message);\n      } else {\n        if (message.id < 1000000) {\n          if (message.cmd === \"fileWrite\" && !this.modified_panel_updater_timer && (typeof site_info !== \"undefined\" && site_info !== null ? (ref = site_info.settings) != null ? ref.own : void 0 : void 0)) {\n            this.modified_panel_updater_timer = setTimeout(((function(_this) {\n              return function() {\n                _this.updateModifiedPanel();\n                return _this.modified_panel_updater_timer = null;\n              };\n            })(this)), 1000);\n          }\n          return this.ws.send(message);\n        } else {\n          return this.log(\"Invalid inner message id\");\n        }\n      }\n    };\n\n    Wrapper.prototype.toRelativeQuery = function(query) {\n      var back;\n      if (query == null) {\n        query = null;\n      }\n      if (query === null) {\n        query = window.location.search;\n      }\n      back = window.location.pathname;\n      if (back.match(/^\\/[^\\/]+$/)) {\n        back += \"/\";\n      }\n      if (query.startsWith(\"#\")) {\n        back = query;\n      } else if (query.replace(\"?\", \"\")) {\n        back += \"?\" + query.replace(\"?\", \"\");\n      }\n      return back;\n    };\n\n    Wrapper.prototype.displayOpenerDialog = function() {\n      var elem;\n      elem = $(\"<div class='opener-overlay'><div class='dialog'>You have opened this page by clicking on a link. Please, confirm if you want to load this site.<a href='?' target='_blank' class='button'>Open site</a></div></div>\");\n      elem.find('a').on(\"click\", function() {\n        window.open(\"?\", \"_blank\");\n        window.close();\n        return false;\n      });\n      return $(\"body\").prepend(elem);\n    };\n\n    Wrapper.prototype.actionOpenWindow = function(params) {\n      var w;\n      if (typeof params === \"string\") {\n        w = window.open();\n        w.opener = null;\n        return w.location = params;\n      } else {\n        w = window.open(null, params[1], params[2]);\n        w.opener = null;\n        return w.location = params[0];\n      }\n    };\n\n    Wrapper.prototype.actionRequestFullscreen = function() {\n      var elem, request_fullscreen;\n      elem = document.getElementById(\"inner-iframe\");\n      request_fullscreen = elem.requestFullScreen || elem.webkitRequestFullscreen || elem.mozRequestFullScreen || elem.msRequestFullScreen;\n      return request_fullscreen.call(elem);\n    };\n\n    Wrapper.prototype.actionWebNotification = function(message) {\n      return $.when(this.event_site_info).done((function(_this) {\n        return function() {\n          var res;\n          if (Notification.permission === \"granted\") {\n            return _this.displayWebNotification(message);\n          } else if (Notification.permission === \"denied\") {\n            res = {\n              \"error\": \"Web notifications are disabled by the user\"\n            };\n            return _this.sendInner({\n              \"cmd\": \"response\",\n              \"to\": message.id,\n              \"result\": res\n            });\n          } else {\n            return Notification.requestPermission().then(function(permission) {\n              if (permission === \"granted\") {\n                return _this.displayWebNotification(message);\n              }\n            });\n          }\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.actionCloseWebNotification = function(message) {\n      return $.when(this.event_site_info).done((function(_this) {\n        return function() {\n          var id;\n          id = message.params[0];\n          return _this.web_notifications[id].close();\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.displayWebNotification = function(message) {\n      var id, notification, options, title;\n      title = message.params[0];\n      id = message.params[1];\n      options = message.params[2];\n      notification = new Notification(title, options);\n      this.web_notifications[id] = notification;\n      notification.onshow = (function(_this) {\n        return function() {\n          return _this.sendInner({\n            \"cmd\": \"response\",\n            \"to\": message.id,\n            \"result\": \"ok\"\n          });\n        };\n      })(this);\n      notification.onclick = (function(_this) {\n        return function(e) {\n          if (!options.focus_tab) {\n            e.preventDefault();\n          }\n          return _this.sendInner({\n            \"cmd\": \"webNotificationClick\",\n            \"params\": {\n              \"id\": id\n            }\n          });\n        };\n      })(this);\n      return notification.onclose = (function(_this) {\n        return function() {\n          _this.sendInner({\n            \"cmd\": \"webNotificationClose\",\n            \"params\": {\n              \"id\": id\n            }\n          });\n          return delete _this.web_notifications[id];\n        };\n      })(this);\n    };\n\n    Wrapper.prototype.actionPermissionAdd = function(message) {\n      var permission;\n      permission = message.params;\n      return $.when(this.event_site_info).done((function(_this) {\n        return function() {\n          if (indexOf.call(_this.site_info.settings.permissions, permission) >= 0) {\n            return false;\n          }\n          return _this.ws.cmd(\"permissionDetails\", permission, function(permission_details) {\n            return _this.displayConfirm(\"This site requests permission:\" + (\" <b>\" + (_this.toHtmlSafe(permission)) + \"</b>\") + (\"<br><small style='color: #4F4F4F'>\" + permission_details + \"</small>\"), \"Grant\", function() {\n              return _this.ws.cmd(\"permissionAdd\", permission, function(res) {\n                return _this.sendInner({\n                  \"cmd\": \"response\",\n                  \"to\": message.id,\n                  \"result\": res\n                });\n              });\n            });\n          });\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.actionNotification = function(message) {\n      var body;\n      message.params = this.toHtmlSafe(message.params);\n      body = $(\"<span class='message'>\" + message.params[1] + \"</span>\");\n      return this.notifications.add(\"notification-\" + message.id, message.params[0], body, message.params[2]);\n    };\n\n    Wrapper.prototype.displayConfirm = function(body, captions, cb) {\n      var button, buttons, caption, fn, i, j, len;\n      body = $(\"<span class='message-outer'><span class='message'>\" + body + \"</span></span>\");\n      buttons = $(\"<span class='buttons'></span>\");\n      if (!(captions instanceof Array)) {\n        captions = [captions];\n      }\n      fn = (function(_this) {\n        return function(button) {\n          return button.on(\"click\", function(e) {\n            _this.verifyEvent(button, e);\n            cb(parseInt(e.currentTarget.dataset.value));\n            return false;\n          });\n        };\n      })(this);\n      for (i = j = 0, len = captions.length; j < len; i = ++j) {\n        caption = captions[i];\n        button = $(\"<a></a>\", {\n          href: \"#\" + caption,\n          \"class\": \"button button-confirm button-\" + caption + \" button-\" + (i + 1),\n          \"data-value\": i + 1\n        });\n        button.text(caption);\n        fn(button);\n        buttons.append(button);\n      }\n      body.append(buttons);\n      this.notifications.add(\"notification-\" + caption, \"ask\", body);\n      buttons.first().focus();\n      return $(\".notification\").scrollLeft(0);\n    };\n\n    Wrapper.prototype.actionConfirm = function(message, cb) {\n      var caption;\n      if (cb == null) {\n        cb = false;\n      }\n      message.params = this.toHtmlSafe(message.params);\n      if (message.params[1]) {\n        caption = message.params[1];\n      } else {\n        caption = \"ok\";\n      }\n      return this.displayConfirm(message.params[0], caption, (function(_this) {\n        return function(res) {\n          _this.sendInner({\n            \"cmd\": \"response\",\n            \"to\": message.id,\n            \"result\": res\n          });\n          return false;\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.displayPrompt = function(message, type, caption, placeholder, cb) {\n      var body, button, input;\n      body = $(\"<span class='message'></span>\").html(message);\n      if (placeholder == null) {\n        placeholder = \"\";\n      }\n      input = $(\"<input/>\", {\n        type: type,\n        \"class\": \"input button-\" + type,\n        placeholder: placeholder\n      });\n      input.on(\"keyup\", (function(_this) {\n        return function(e) {\n          _this.verifyEvent(input, e);\n          if (e.keyCode === 13) {\n            return cb(input.val());\n          }\n        };\n      })(this));\n      body.append(input);\n      button = $(\"<a></a>\", {\n        href: \"#\" + caption,\n        \"class\": \"button button-\" + caption\n      }).text(caption);\n      button.on(\"click\", (function(_this) {\n        return function(e) {\n          _this.verifyEvent(button, e);\n          cb(input.val());\n          return false;\n        };\n      })(this));\n      body.append(button);\n      this.notifications.add(\"notification-\" + message.id, \"ask\", body);\n      input.focus();\n      return $(\".notification\").scrollLeft(0);\n    };\n\n    Wrapper.prototype.actionPrompt = function(message) {\n      var caption, placeholder, type;\n      message.params = this.toHtmlSafe(message.params);\n      if (message.params[1]) {\n        type = message.params[1];\n      } else {\n        type = \"text\";\n      }\n      caption = message.params[2] ? message.params[2] : \"OK\";\n      if (message.params[3] != null) {\n        placeholder = message.params[3];\n      } else {\n        placeholder = \"\";\n      }\n      return this.displayPrompt(message.params[0], type, caption, placeholder, (function(_this) {\n        return function(res) {\n          return _this.sendInner({\n            \"cmd\": \"response\",\n            \"to\": message.id,\n            \"result\": res\n          });\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.displayProgress = function(type, body, percent) {\n      var circle, elem, offset, width;\n      percent = Math.min(100, percent) / 100;\n      offset = 75 - (percent * 75);\n      circle = \"<div class=\\\"circle\\\"><svg class=\\\"circle-svg\\\" width=\\\"30\\\" height=\\\"30\\\" viewport=\\\"0 0 30 30\\\" version=\\\"1.1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\">\\n  \t\t\t\t<circle r=\\\"12\\\" cx=\\\"15\\\" cy=\\\"15\\\" fill=\\\"transparent\\\" class=\\\"circle-bg\\\"></circle>\\n  \t\t\t\t<circle r=\\\"12\\\" cx=\\\"15\\\" cy=\\\"15\\\" fill=\\\"transparent\\\" class=\\\"circle-fg\\\" style=\\\"stroke-dashoffset: \" + offset + \"\\\"></circle>\\n</svg></div>\";\n      body = \"<span class='message'>\" + body + \"</span>\" + circle;\n      elem = $(\".notification-\" + type);\n      if (elem.length) {\n        width = $(\".body .message\", elem).outerWidth();\n        $(\".body .message\", elem).html(body);\n        if ($(\".body .message\", elem).css(\"width\") === \"\") {\n          $(\".body .message\", elem).css(\"width\", width);\n        }\n        $(\".body .circle-fg\", elem).css(\"stroke-dashoffset\", offset);\n      } else {\n        elem = this.notifications.add(type, \"progress\", $(body));\n      }\n      if (percent > 0) {\n        $(\".body .circle-bg\", elem).css({\n          \"animation-play-state\": \"paused\",\n          \"stroke-dasharray\": \"180px\"\n        });\n      }\n      if ($(\".notification-icon\", elem).data(\"done\")) {\n        return false;\n      } else if (percent >= 1) {\n        $(\".circle-fg\", elem).css(\"transition\", \"all 0.3s ease-in-out\");\n        setTimeout((function() {\n          $(\".notification-icon\", elem).css({\n            transform: \"scale(1)\",\n            opacity: 1\n          });\n          return $(\".notification-icon .icon-success\", elem).css({\n            transform: \"rotate(45deg) scale(1)\"\n          });\n        }), 300);\n        setTimeout(((function(_this) {\n          return function() {\n            return _this.notifications.close(elem);\n          };\n        })(this)), 3000);\n        return $(\".notification-icon\", elem).data(\"done\", true);\n      } else if (percent < 0) {\n        $(\".body .circle-fg\", elem).css(\"stroke\", \"#ec6f47\").css(\"transition\", \"transition: all 0.3s ease-in-out\");\n        setTimeout(((function(_this) {\n          return function() {\n            $(\".notification-icon\", elem).css({\n              transform: \"scale(1)\",\n              opacity: 1\n            });\n            elem.removeClass(\"notification-done\").addClass(\"notification-error\");\n            return $(\".notification-icon .icon-success\", elem).removeClass(\"icon-success\").html(\"!\");\n          };\n        })(this)), 300);\n        return $(\".notification-icon\", elem).data(\"done\", true);\n      }\n    };\n\n    Wrapper.prototype.actionProgress = function(message) {\n      message.params = this.toHtmlSafe(message.params);\n      return this.displayProgress(message.params[0], message.params[1], message.params[2]);\n    };\n\n    Wrapper.prototype.actionSetViewport = function(message) {\n      this.log(\"actionSetViewport\", message);\n      if ($(\"#viewport\").length > 0) {\n        return $(\"#viewport\").attr(\"content\", this.toHtmlSafe(message.params));\n      } else {\n        return $('<meta name=\"viewport\" id=\"viewport\">').attr(\"content\", this.toHtmlSafe(message.params)).appendTo(\"head\");\n      }\n    };\n\n    Wrapper.prototype.actionReload = function(message) {\n      return this.reload(message.params[0]);\n    };\n\n    Wrapper.prototype.reload = function(url_post) {\n      var current_url;\n      if (url_post == null) {\n        url_post = \"\";\n      }\n      this.log(\"Reload\");\n      current_url = window.location.toString().replace(/#.*/g, \"\");\n      if (url_post) {\n        if (current_url.indexOf(\"?\") > 0) {\n          return window.location = current_url + \"&\" + url_post;\n        } else {\n          return window.location = current_url + \"?\" + url_post;\n        }\n      } else {\n        return window.location.reload();\n      }\n    };\n\n    Wrapper.prototype.actionGetLocalStorage = function(message) {\n      return $.when(this.event_site_info).done((function(_this) {\n        return function() {\n          var data;\n          data = localStorage.getItem(\"site.\" + _this.site_info.address + \".\" + _this.site_info.auth_address);\n          if (!data) {\n            data = localStorage.getItem(\"site.\" + _this.site_info.address);\n            if (data) {\n              localStorage.setItem(\"site.\" + _this.site_info.address + \".\" + _this.site_info.auth_address, data);\n              localStorage.removeItem(\"site.\" + _this.site_info.address);\n              _this.log(\"Migrated LocalStorage from global to auth_address based\");\n            }\n          }\n          if (data) {\n            data = JSON.parse(data);\n          }\n          return _this.sendInner({\n            \"cmd\": \"response\",\n            \"to\": message.id,\n            \"result\": data\n          });\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.actionSetLocalStorage = function(message) {\n      return $.when(this.event_site_info).done((function(_this) {\n        return function() {\n          var back;\n          back = localStorage.setItem(\"site.\" + _this.site_info.address + \".\" + _this.site_info.auth_address, JSON.stringify(message.params));\n          return _this.sendInner({\n            \"cmd\": \"response\",\n            \"to\": message.id,\n            \"result\": back\n          });\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.onOpenWebsocket = function(e) {\n      if (window.show_loadingscreen) {\n        this.ws.cmd(\"channelJoin\", {\n          \"channels\": [\"siteChanged\", \"serverChanged\", \"announcerChanged\"]\n        });\n      } else {\n        this.ws.cmd(\"channelJoin\", {\n          \"channels\": [\"siteChanged\", \"serverChanged\"]\n        });\n      }\n      if (!this.wrapperWsInited && this.inner_ready) {\n        this.sendInner({\n          \"cmd\": \"wrapperOpenedWebsocket\"\n        });\n        this.wrapperWsInited = true;\n      }\n      if (window.show_loadingscreen) {\n        this.ws.cmd(\"serverInfo\", [], (function(_this) {\n          return function(server_info) {\n            return _this.server_info = server_info;\n          };\n        })(this));\n        this.ws.cmd(\"announcerInfo\", [], (function(_this) {\n          return function(announcer_info) {\n            return _this.setAnnouncerInfo(announcer_info);\n          };\n        })(this));\n      }\n      if (this.inner_loaded) {\n        this.reloadSiteInfo();\n      }\n      setTimeout(((function(_this) {\n        return function() {\n          if (!_this.site_info) {\n            return _this.reloadSiteInfo();\n          }\n        };\n      })(this)), 2000);\n      if (this.ws_error) {\n        this.notifications.add(\"connection\", \"done\", \"Connection with <b>UiServer Websocket</b> recovered.\", 6000);\n        return this.ws_error = null;\n      }\n    };\n\n    Wrapper.prototype.onCloseWebsocket = function(e) {\n      this.wrapperWsInited = false;\n      return setTimeout(((function(_this) {\n        return function() {\n          _this.sendInner({\n            \"cmd\": \"wrapperClosedWebsocket\"\n          });\n          if (e && e.code === 1000 && e.wasClean === false) {\n            return _this.ws_error = _this.notifications.add(\"connection\", \"error\", \"UiServer Websocket error, please reload the page.\");\n          } else if (e && e.code === 1001 && e.wasClean === true) {\n\n          } else if (!_this.ws_error) {\n            return _this.ws_error = _this.notifications.add(\"connection\", \"error\", \"Connection with <b>UiServer Websocket</b> was lost. Reconnecting...\");\n          }\n        };\n      })(this)), 1000);\n    };\n\n    Wrapper.prototype.onPageLoad = function(e) {\n      var ref;\n      this.log(\"onPageLoad\");\n      this.inner_loaded = true;\n      if (!this.inner_ready) {\n        this.sendInner({\n          \"cmd\": \"wrapperReady\"\n        });\n      }\n      if (this.ws.ws.readyState === 1 && !this.site_info) {\n        return this.reloadSiteInfo();\n      } else if (this.site_info && (((ref = this.site_info.content) != null ? ref.title : void 0) != null) && !this.is_title_changed) {\n        window.document.title = this.site_info.content.title + \" - ZeroNet\";\n        return this.log(\"Setting title to\", window.document.title);\n      }\n    };\n\n    Wrapper.prototype.onWrapperLoad = function() {\n      this.script_nonce = window.script_nonce;\n      this.wrapper_key = window.wrapper_key;\n      delete window.wrapper;\n      delete window.wrapper_key;\n      delete window.script_nonce;\n      return $(\"#script_init\").remove();\n    };\n\n    Wrapper.prototype.sendInner = function(message) {\n      return this.inner.postMessage(message, '*');\n    };\n\n    Wrapper.prototype.reloadSiteInfo = function() {\n      var params;\n      if (this.loading.screen_visible) {\n        params = {\n          \"file_status\": window.file_inner_path\n        };\n      } else {\n        params = {};\n      }\n      return this.ws.cmd(\"siteInfo\", params, (function(_this) {\n        return function(site_info) {\n          var ref;\n          _this.address = site_info.address;\n          _this.setSiteInfo(site_info);\n          if (site_info.settings.size > site_info.size_limit * 1024 * 1024 && !_this.loading.screen_visible) {\n            _this.displayConfirm(\"Site is larger than allowed: \" + ((site_info.settings.size / 1024 / 1024).toFixed(1)) + \"MB/\" + site_info.size_limit + \"MB\", \"Set limit to \" + site_info.next_size_limit + \"MB\", function() {\n              return _this.ws.cmd(\"siteSetLimit\", [site_info.next_size_limit], function(res) {\n                if (res === \"ok\") {\n                  return _this.notifications.add(\"size_limit\", \"done\", \"Site storage limit modified!\", 5000);\n                }\n              });\n            });\n          }\n          if ((((ref = site_info.content) != null ? ref.title : void 0) != null) && !_this.is_title_changed) {\n            window.document.title = site_info.content.title + \" - ZeroNet\";\n            return _this.log(\"Setting title to\", window.document.title);\n          }\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.setSiteInfo = function(site_info) {\n      var ref, ref1, ref2, ref3;\n      if (site_info.event != null) {\n        if (site_info.event[0] === \"file_added\" && site_info.bad_files) {\n          this.loading.printLine(site_info.bad_files + \" files needs to be downloaded\");\n        } else if (site_info.event[0] === \"file_done\") {\n          this.loading.printLine(site_info.event[1] + \" downloaded\");\n          if (site_info.event[1] === window.file_inner_path) {\n            this.loading.hideScreen();\n            if (!this.site_info) {\n              this.reloadSiteInfo();\n            }\n            if (site_info.content && !this.is_title_changed) {\n              window.document.title = site_info.content.title + \" - ZeroNet\";\n              this.log(\"Required file \" + window.file_inner_path + \" done, setting title to\", window.document.title);\n            }\n            if (!window.show_loadingscreen) {\n              this.notifications.add(\"modified\", \"info\", \"New version of this page has just released.<br>Reload to see the modified content.\");\n            }\n          }\n        } else if (site_info.event[0] === \"file_failed\") {\n          this.site_error = site_info.event[1];\n          if (site_info.settings.size > site_info.size_limit * 1024 * 1024) {\n            this.loading.showTooLarge(site_info);\n          } else {\n            this.loading.printLine(site_info.event[1] + \" download failed\", \"error\");\n          }\n        } else if (site_info.event[0] === \"peers_added\") {\n          this.loading.printLine(\"Peers found: \" + site_info.peers);\n        }\n      }\n      if (this.loading.screen_visible && !this.site_info) {\n        if (site_info.peers > 1) {\n          this.loading.printLine(\"Peers found: \" + site_info.peers);\n        } else {\n          this.site_error = \"No peers found\";\n          this.loading.printLine(\"No peers found\");\n        }\n      }\n      if (!this.site_info && !this.loading.screen_visible && $(\"#inner-iframe\").attr(\"src\").replace(\"?wrapper=False\", \"\").replace(/\\?wrapper_nonce=[A-Za-z0-9]+/, \"\").indexOf(\"?\") === -1) {\n        if (site_info.size_limit * 1.1 < site_info.next_size_limit) {\n          this.displayConfirm(\"Running out of size limit (\" + ((site_info.settings.size / 1024 / 1024).toFixed(1)) + \"MB/\" + site_info.size_limit + \"MB)\", \"Set limit to \" + site_info.next_size_limit + \"MB\", (function(_this) {\n            return function() {\n              _this.ws.cmd(\"siteSetLimit\", [site_info.next_size_limit], function(res) {\n                if (res === \"ok\") {\n                  return _this.notifications.add(\"size_limit\", \"done\", \"Site storage limit modified!\", 5000);\n                }\n              });\n              return false;\n            };\n          })(this));\n        }\n      }\n      if (this.loading.screen_visible && this.inner_loaded && site_info.settings.size < site_info.size_limit * 1024 * 1024 && site_info.settings.size > 0) {\n        this.log(\"Loading screen visible, but inner loaded\");\n        this.loading.hideScreen();\n      }\n      if ((site_info != null ? (ref = site_info.settings) != null ? ref.own : void 0 : void 0) && (site_info != null ? (ref1 = site_info.settings) != null ? ref1.modified : void 0 : void 0) !== ((ref2 = this.site_info) != null ? (ref3 = ref2.settings) != null ? ref3.modified : void 0 : void 0)) {\n        this.updateModifiedPanel();\n      }\n      if (this.loading.screen_visible && site_info.settings.size > site_info.size_limit * 1024 * 1024) {\n        this.log(\"Site too large\");\n        this.loading.showTooLarge(site_info);\n      }\n      this.site_info = site_info;\n      return this.event_site_info.resolve();\n    };\n\n    Wrapper.prototype.siteSign = function(inner_path, cb) {\n      if (this.site_info.privatekey) {\n        this.infopanel.elem.find(\".button\").addClass(\"loading\");\n        return this.ws.cmd(\"siteSign\", {\n          privatekey: \"stored\",\n          inner_path: inner_path,\n          update_changed_files: true\n        }, (function(_this) {\n          return function(res) {\n            if (res === \"ok\") {\n              if (typeof cb === \"function\") {\n                cb(true);\n              }\n            } else {\n              if (typeof cb === \"function\") {\n                cb(false);\n              }\n            }\n            return _this.infopanel.elem.find(\".button\").removeClass(\"loading\");\n          };\n        })(this));\n      } else {\n        return this.displayPrompt(\"Enter your private key:\", \"password\", \"Sign\", \"\", (function(_this) {\n          return function(privatekey) {\n            _this.infopanel.elem.find(\".button\").addClass(\"loading\");\n            return _this.ws.cmd(\"siteSign\", {\n              privatekey: privatekey,\n              inner_path: inner_path,\n              update_changed_files: true\n            }, function(res) {\n              if (res === \"ok\") {\n                if (typeof cb === \"function\") {\n                  cb(true);\n                }\n              } else {\n                if (typeof cb === \"function\") {\n                  cb(false);\n                }\n              }\n              return _this.infopanel.elem.find(\".button\").removeClass(\"loading\");\n            });\n          };\n        })(this));\n      }\n    };\n\n    Wrapper.prototype.sitePublish = function(inner_path) {\n      return this.ws.cmd(\"sitePublish\", {\n        \"inner_path\": inner_path,\n        \"sign\": false\n      });\n    };\n\n    Wrapper.prototype.updateModifiedPanel = function() {\n      return this.ws.cmd(\"siteListModifiedFiles\", [], (function(_this) {\n        return function(res) {\n          var closed, num, ref;\n          num = (ref = res.modified_files) != null ? ref.length : void 0;\n          if (num > 0) {\n            closed = _this.site_info.settings.modified_files_notification === false;\n            _this.infopanel.show(closed);\n          } else {\n            _this.infopanel.hide();\n          }\n          if (num > 0) {\n            _this.infopanel.setTitle(res.modified_files.length + \" modified file\" + (num > 1 ? 's' : ''), res.modified_files.join(\", \"));\n            _this.infopanel.setClosedNum(num);\n            _this.infopanel.setAction(\"Sign & Publish\", function() {\n              _this.siteSign(\"content.json\", function(res) {\n                if (res) {\n                  _this.notifications.add(\"sign\", \"done\", \"content.json Signed!\", 5000);\n                  return _this.sitePublish(\"content.json\");\n                }\n              });\n              return false;\n            });\n          }\n          return _this.log(\"siteListModifiedFiles\", num, res);\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.setAnnouncerInfo = function(announcer_info) {\n      var key, ref, status_db, status_line, val;\n      status_db = {\n        announcing: [],\n        error: [],\n        announced: []\n      };\n      ref = announcer_info.stats;\n      for (key in ref) {\n        val = ref[key];\n        if (val.status) {\n          status_db[val.status].push(val);\n        }\n      }\n      status_line = \"Trackers announcing: \" + status_db.announcing.length + \", error: \" + status_db.error.length + \", done: \" + status_db.announced.length;\n      if (this.announcer_line) {\n        this.announcer_line.text(status_line);\n      } else {\n        this.announcer_line = this.loading.printLine(status_line);\n      }\n      if (status_db.error.length > (status_db.announced.length + status_db.announcing.length) && status_db.announced.length < 3) {\n        return this.loading.showTrackerTorBridge(this.server_info);\n      }\n    };\n\n    Wrapper.prototype.updateProgress = function(site_info) {\n      if (site_info.tasks > 0 && site_info.started_task_num > 0) {\n        return this.loading.setProgress(1 - (Math.max(site_info.tasks, site_info.bad_files) / site_info.started_task_num));\n      } else {\n        return this.loading.hideProgress();\n      }\n    };\n\n    Wrapper.prototype.toHtmlSafe = function(values) {\n      var i, j, len, value;\n      if (!(values instanceof Array)) {\n        values = [values];\n      }\n      for (i = j = 0, len = values.length; j < len; i = ++j) {\n        value = values[i];\n        if (value instanceof Array) {\n          value = this.toHtmlSafe(value);\n        } else {\n          value = String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&apos;');\n          value = value.replace(/&lt;([\\/]{0,1}(br|b|u|i|small))&gt;/g, \"<$1>\");\n        }\n        values[i] = value;\n      }\n      return values;\n    };\n\n    Wrapper.prototype.setSizeLimit = function(size_limit, reload) {\n      if (reload == null) {\n        reload = true;\n      }\n      this.log(\"setSizeLimit: \" + size_limit + \", reload: \" + reload);\n      this.inner_loaded = false;\n      this.ws.cmd(\"siteSetLimit\", [size_limit], (function(_this) {\n        return function(res) {\n          if (res !== \"ok\") {\n            return false;\n          }\n          _this.loading.printLine(res);\n          _this.inner_loaded = false;\n          if (reload) {\n            return _this.reloadIframe();\n          }\n        };\n      })(this));\n      return false;\n    };\n\n    Wrapper.prototype.reloadIframe = function() {\n      var src;\n      src = $(\"iframe\").attr(\"src\");\n      return this.ws.cmd(\"serverGetWrapperNonce\", [], (function(_this) {\n        return function(wrapper_nonce) {\n          src = src.replace(/wrapper_nonce=[A-Za-z0-9]+/, \"wrapper_nonce=\" + wrapper_nonce);\n          _this.log(\"Reloading iframe using url\", src);\n          return $(\"iframe\").attr(\"src\", src);\n        };\n      })(this));\n    };\n\n    Wrapper.prototype.log = function() {\n      var args;\n      args = 1 <= arguments.length ? slice.call(arguments, 0) : [];\n      return console.log.apply(console, [\"[Wrapper]\"].concat(slice.call(args)));\n    };\n\n    return Wrapper;\n\n  })();\n\n  origin = window.server_url || window.location.href.replace(/(\\:\\/\\/.*?)\\/.*/, \"$1\");\n\n  if (origin.indexOf(\"https:\") === 0) {\n    proto = {\n      ws: 'wss',\n      http: 'https'\n    };\n  } else {\n    proto = {\n      ws: 'ws',\n      http: 'http'\n    };\n  }\n\n  ws_url = proto.ws + \":\" + origin.replace(proto.http + \":\", \"\") + \"/ZeroNet-Internal/Websocket?wrapper_key=\" + window.wrapper_key;\n\n  window.wrapper = new Wrapper(ws_url);\n\n}).call(this);\n\n\n/* ---- WrapperZeroFrame.coffee ---- */\n\n\n(function() {\n  var WrapperZeroFrame,\n    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };\n\n  WrapperZeroFrame = (function() {\n    function WrapperZeroFrame(wrapper) {\n      this.certSelectGotoSite = bind(this.certSelectGotoSite, this);\n      this.response = bind(this.response, this);\n      this.cmd = bind(this.cmd, this);\n      this.wrapperCmd = wrapper.cmd;\n      this.wrapperResponse = wrapper.ws.response;\n      console.log(\"WrapperZeroFrame\", wrapper);\n    }\n\n    WrapperZeroFrame.prototype.cmd = function(cmd, params, cb) {\n      if (params == null) {\n        params = {};\n      }\n      if (cb == null) {\n        cb = null;\n      }\n      return this.wrapperCmd(cmd, params, cb);\n    };\n\n    WrapperZeroFrame.prototype.response = function(to, result) {\n      return this.wrapperResponse(to, result);\n    };\n\n    WrapperZeroFrame.prototype.isProxyRequest = function() {\n      return window.location.pathname === \"/\";\n    };\n\n    WrapperZeroFrame.prototype.certSelectGotoSite = function(elem) {\n      var href;\n      href = $(elem).attr(\"href\");\n      if (this.isProxyRequest()) {\n        return $(elem).attr(\"href\", \"http://zero\" + href);\n      }\n    };\n\n    return WrapperZeroFrame;\n\n  })();\n\n  window.zeroframe = new WrapperZeroFrame(window.wrapper);\n\n}).call(this);\n\n/* ---- ZeroSiteTheme.coffee ---- */\n\n\n(function() {\n  var DARK, LIGHT, changeColorScheme, detectColorScheme, displayNotification, mqDark, mqLight,\n    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };\n\n  DARK = \"(prefers-color-scheme: dark)\";\n\n  LIGHT = \"(prefers-color-scheme: light)\";\n\n  mqDark = window.matchMedia(DARK);\n\n  mqLight = window.matchMedia(LIGHT);\n\n  changeColorScheme = function(theme) {\n    zeroframe.cmd(\"userGetGlobalSettings\", [], function(user_settings) {\n      if (user_settings.theme !== theme) {\n        user_settings.theme = theme;\n        zeroframe.cmd(\"userSetGlobalSettings\", [user_settings], function(status) {\n          if (status === \"ok\") {\n            location.reload();\n          }\n        });\n      }\n    });\n  };\n\n  displayNotification = function(arg) {\n    var matches, media;\n    matches = arg.matches, media = arg.media;\n    if (!matches) {\n      return;\n    }\n    zeroframe.cmd(\"siteInfo\", [], function(site_info) {\n      if (indexOf.call(site_info.settings.permissions, \"ADMIN\") >= 0) {\n        zeroframe.cmd(\"wrapperNotification\", [\"info\", \"Your system's theme has been changed.<br>Please reload site to use it.\"]);\n      } else {\n        zeroframe.cmd(\"wrapperNotification\", [\"info\", \"Your system's theme has been changed.<br>Please open ZeroHello to use it.\"]);\n      }\n    });\n  };\n\n  detectColorScheme = function() {\n    if (mqDark.matches) {\n      changeColorScheme(\"dark\");\n    } else if (mqLight.matches) {\n      changeColorScheme(\"light\");\n    }\n    mqDark.addListener(displayNotification);\n    mqLight.addListener(displayNotification);\n  };\n\n  zeroframe.cmd(\"userGetGlobalSettings\", [], function(user_settings) {\n    if (user_settings.use_system_theme === true) {\n      detectColorScheme();\n    }\n  });\n\n}).call(this);"
  },
  {
    "path": "src/Ui/media/lib/RateLimit.coffee",
    "content": "limits = {}\ncall_after_interval = {}\nwindow.RateLimit = (interval, fn) ->\n\tif not limits[fn]\n\t\tcall_after_interval[fn] = false\n\t\tfn() # First call is not delayed\n\t\tlimits[fn] = setTimeout (->\n\t\t\tif call_after_interval[fn]\n\t\t\t\tfn()\n\t\t\tdelete limits[fn]\n\t\t\tdelete call_after_interval[fn]\n\t\t), interval\n\telse # Called within iterval, delay the call\n\t\tcall_after_interval[fn] = true\n"
  },
  {
    "path": "src/Ui/media/lib/Translate.coffee",
    "content": "window._ = (s) -> return s"
  },
  {
    "path": "src/Ui/media/lib/ZeroWebsocket.coffee",
    "content": "class ZeroWebsocket\n\tconstructor: (url) ->\n\t\t@url = url\n\t\t@next_message_id = 1\n\t\t@waiting_cb = {}\n\t\t@init()\n\n\n\tinit: ->\n\t\t@\n\n\n\tconnect: ->\n\t\t@ws = new WebSocket(@url)\n\t\t@ws.onmessage = @onMessage\n\t\t@ws.onopen = @onOpenWebsocket\n\t\t@ws.onerror = @onErrorWebsocket\n\t\t@ws.onclose = @onCloseWebsocket\n\t\t@connected = false\n\t\t@message_queue = []\n\n\n\tonMessage: (e) =>\n\t\tmessage = JSON.parse(e.data)\n\t\tcmd = message.cmd\n\t\tif cmd == \"response\"\n\t\t\tif @waiting_cb[message.to]?\n\t\t\t\t@waiting_cb[message.to](message.result)\n\t\t\telse\n\t\t\t\t@log \"Websocket callback not found:\", message\n\t\telse if cmd == \"ping\"\n\t\t\t@response message.id, \"pong\"\n\t\telse\n\t\t\t@route cmd, message\n\n\troute: (cmd, message) =>\n\t\t@log \"Unknown command\", message\n\n\n\tresponse: (to, result) =>\n\t\t@send {\"cmd\": \"response\", \"to\": to, \"result\": result}\n\n\n\tcmd: (cmd, params={}, cb=null) ->\n\t\t@send {\"cmd\": cmd, \"params\": params}, cb\n\n\n\tsend: (message, cb=null) ->\n\t\tif not message.id?\n\t\t\tmessage.id = @next_message_id\n\t\t\t@next_message_id += 1\n\t\tif @connected\n\t\t\t@ws.send(JSON.stringify(message))\n\t\telse\n\t\t\t@log \"Not connected, adding message to queue\"\n\t\t\t@message_queue.push(message)\n\t\tif cb\n\t\t\t@waiting_cb[message.id] = cb\n\n\n\tlog: (args...) =>\n\t\tconsole.log \"[ZeroWebsocket]\", args...\n\n\n\tonOpenWebsocket: (e) =>\n\t\t@log \"Open\"\n\t\t@connected = true\n\n\t\t# Process messages sent before websocket opened\n\t\tfor message in @message_queue\n\t\t\t@ws.send(JSON.stringify(message))\n\t\t@message_queue = []\n\n\t\tif @onOpen? then @onOpen(e)\n\n\n\tonErrorWebsocket: (e) =>\n\t\t@log \"Error\", e\n\t\tif @onError? then @onError(e)\n\n\n\tonCloseWebsocket: (e, reconnect=10000) =>\n\t\t@log \"Closed\", e\n\t\t@connected = false\n\t\tif e and e.code == 1000 and e.wasClean == false\n\t\t\t@log \"Server error, please reload the page\", e.wasClean\n\t\telse # Connection error\n\t\t\tsetTimeout (=>\n\t\t\t\t@log \"Reconnecting...\"\n\t\t\t\t@connect()\n\t\t\t), reconnect\n\t\tif @onClose? then @onClose(e)\n\n\nwindow.ZeroWebsocket = ZeroWebsocket\n"
  },
  {
    "path": "src/Ui/media/lib/jquery.cssanim.js",
    "content": "jQuery.cssHooks['scale'] = {\n\tget: function(elem, computed) {\n\t\tvar match = window.getComputedStyle(elem)[transform_property].match(\"[0-9\\.]+\")\n\t\tif (match) {\n\t\t\tvar scale = parseFloat(match[0])\n\t\t\treturn scale\n\t\t} else {\n\t\t\treturn 1.0\n\t\t}\n\t},\n\tset: function(elem, val) {\n\t\t//var transforms = $(elem).css(\"transform\").match(/[0-9\\.]+/g)\n\t\tvar transforms = window.getComputedStyle(elem)[transform_property].match(/[0-9\\.]+/g)\n\t\tif (transforms) {\n\t\t\ttransforms[0] = val\n\t\t\ttransforms[3] = val\n\t\t\t//$(elem).css(\"transform\", 'matrix('+transforms.join(\", \")+\")\")\n\t\t\telem.style[transform_property] = 'matrix('+transforms.join(\", \")+')'\n\t\t} else {\n\t\t\telem.style[transform_property] = \"scale(\"+val+\")\"\n\t\t}\n\t}\n}\n\njQuery.fx.step.scale = function(fx) {\n\tjQuery.cssHooks['scale'].set(fx.elem, fx.now)\n};\n\n\nif (window.getComputedStyle(document.body).transform) {\n\ttransform_property = \"transform\"\n} else {\n\ttransform_property = \"webkitTransform\"\n}\n"
  },
  {
    "path": "src/Ui/media/lib/jquery.csslater.coffee",
    "content": "jQuery.fn.readdClass = (class_name) ->\n\telem = @\n\telem.removeClass class_name\n\tsetTimeout ( ->\n\t\telem.addClass class_name\n\t), 1\n\treturn @\n\njQuery.fn.removeLater = (time = 500) ->\n\telem = @\n\tsetTimeout ( ->\n\t\telem.remove()\n\t), time\n\treturn @\n\njQuery.fn.hideLater = (time = 500) ->\n\telem = @\n\tsetTimeout ( ->\n\t\tif elem.css(\"opacity\") == 0\n\t\t\telem.css(\"display\", \"none\")\n\t), time\n\treturn @\n\njQuery.fn.addClassLater = (class_name, time = 5) ->\n\telem = @\n\tsetTimeout ( ->\n\t\telem.addClass(class_name)\n\t), time\n\treturn @\n\njQuery.fn.cssLater = (name, val, time = 500) ->\n\telem = @\n\tsetTimeout ( ->\n\t\telem.css name, val\n\t), time\n\treturn @"
  },
  {
    "path": "src/Ui/media/lib/jquery.easing.js",
    "content": "/*\n * jQuery Easing v1.4.1 - http://gsgd.co.uk/sandbox/jquery/easing/\n * Open source under the BSD License.\n * Copyright © 2008 George McGinley Smith\n * All rights reserved.\n * https://raw.github.com/gdsmith/jquery-easing/master/LICENSE\n*/\n\n(function (factory) {\n\tif (typeof define === \"function\" && define.amd) {\n\t\tdefine(['jquery'], function ($) {\n\t\t\treturn factory($);\n\t\t});\n\t} else if (typeof module === \"object\" && typeof module.exports === \"object\") {\n\t\texports = factory(require('jquery'));\n\t} else {\n\t\tfactory(jQuery);\n\t}\n})(function($){\n\n// Preserve the original jQuery \"swing\" easing as \"jswing\"\nif (typeof $.easing !== 'undefined') {\n\t$.easing['jswing'] = $.easing['swing'];\n}\n\nvar pow = Math.pow,\n\tsqrt = Math.sqrt,\n\tsin = Math.sin,\n\tcos = Math.cos,\n\tPI = Math.PI,\n\tc1 = 1.70158,\n\tc2 = c1 * 1.525,\n\tc3 = c1 + 1,\n\tc4 = ( 2 * PI ) / 3,\n\tc5 = ( 2 * PI ) / 4.5;\n\n// x is the fraction of animation progress, in the range 0..1\nfunction bounceOut(x) {\n\tvar n1 = 7.5625,\n\t\td1 = 2.75;\n\tif ( x < 1/d1 ) {\n\t\treturn n1*x*x;\n\t} else if ( x < 2/d1 ) {\n\t\treturn n1*(x-=(1.5/d1))*x + .75;\n\t} else if ( x < 2.5/d1 ) {\n\t\treturn n1*(x-=(2.25/d1))*x + .9375;\n\t} else {\n\t\treturn n1*(x-=(2.625/d1))*x + .984375;\n\t}\n}\n\n$.extend( $.easing,\n{\n\tdef: 'easeOutQuad',\n\tswing: function (x) {\n\t\treturn $.easing[$.easing.def](x);\n\t},\n\teaseInQuad: function (x) {\n\t\treturn x * x;\n\t},\n\teaseOutQuad: function (x) {\n\t\treturn 1 - ( 1 - x ) * ( 1 - x );\n\t},\n\teaseInOutQuad: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t2 * x * x :\n\t\t\t1 - pow( -2 * x + 2, 2 ) / 2;\n\t},\n\teaseInCubic: function (x) {\n\t\treturn x * x * x;\n\t},\n\teaseOutCubic: function (x) {\n\t\treturn 1 - pow( 1 - x, 3 );\n\t},\n\teaseInOutCubic: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t4 * x * x * x :\n\t\t\t1 - pow( -2 * x + 2, 3 ) / 2;\n\t},\n\teaseInQuart: function (x) {\n\t\treturn x * x * x * x;\n\t},\n\teaseOutQuart: function (x) {\n\t\treturn 1 - pow( 1 - x, 4 );\n\t},\n\teaseInOutQuart: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t8 * x * x * x * x :\n\t\t\t1 - pow( -2 * x + 2, 4 ) / 2;\n\t},\n\teaseInQuint: function (x) {\n\t\treturn x * x * x * x * x;\n\t},\n\teaseOutQuint: function (x) {\n\t\treturn 1 - pow( 1 - x, 5 );\n\t},\n\teaseInOutQuint: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t16 * x * x * x * x * x :\n\t\t\t1 - pow( -2 * x + 2, 5 ) / 2;\n\t},\n\teaseInSine: function (x) {\n\t\treturn 1 - cos( x * PI/2 );\n\t},\n\teaseOutSine: function (x) {\n\t\treturn sin( x * PI/2 );\n\t},\n\teaseInOutSine: function (x) {\n\t\treturn -( cos( PI * x ) - 1 ) / 2;\n\t},\n\teaseInExpo: function (x) {\n\t\treturn x === 0 ? 0 : pow( 2, 10 * x - 10 );\n\t},\n\teaseOutExpo: function (x) {\n\t\treturn x === 1 ? 1 : 1 - pow( 2, -10 * x );\n\t},\n\teaseInOutExpo: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ?\n\t\t\tpow( 2, 20 * x - 10 ) / 2 :\n\t\t\t( 2 - pow( 2, -20 * x + 10 ) ) / 2;\n\t},\n\teaseInCirc: function (x) {\n\t\treturn 1 - sqrt( 1 - pow( x, 2 ) );\n\t},\n\teaseOutCirc: function (x) {\n\t\treturn sqrt( 1 - pow( x - 1, 2 ) );\n\t},\n\teaseInOutCirc: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t( 1 - sqrt( 1 - pow( 2 * x, 2 ) ) ) / 2 :\n\t\t\t( sqrt( 1 - pow( -2 * x + 2, 2 ) ) + 1 ) / 2;\n\t},\n\teaseInElastic: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 :\n\t\t\t-pow( 2, 10 * x - 10 ) * sin( ( x * 10 - 10.75 ) * c4 );\n\t},\n\teaseOutElastic: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 :\n\t\t\tpow( 2, -10 * x ) * sin( ( x * 10 - 0.75 ) * c4 ) + 1;\n\t},\n\teaseInOutElastic: function (x) {\n\t\treturn x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ?\n\t\t\t-( pow( 2, 20 * x - 10 ) * sin( ( 20 * x - 11.125 ) * c5 )) / 2 :\n\t\t\tpow( 2, -20 * x + 10 ) * sin( ( 20 * x - 11.125 ) * c5 ) / 2 + 1;\n\t},\n\teaseInBack: function (x) {\n\t\treturn c3 * x * x * x - c1 * x * x;\n\t},\n\teaseOutBack: function (x) {\n\t\treturn 1 + c3 * pow( x - 1, 3 ) + c1 * pow( x - 1, 2 );\n\t},\n\teaseInOutBack: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t( pow( 2 * x, 2 ) * ( ( c2 + 1 ) * 2 * x - c2 ) ) / 2 :\n\t\t\t( pow( 2 * x - 2, 2 ) *( ( c2 + 1 ) * ( x * 2 - 2 ) + c2 ) + 2 ) / 2;\n\t},\n\teaseInBounce: function (x) {\n\t\treturn 1 - bounceOut( 1 - x );\n\t},\n\teaseOutBounce: bounceOut,\n\teaseInOutBounce: function (x) {\n\t\treturn x < 0.5 ?\n\t\t\t( 1 - bounceOut( 1 - 2 * x ) ) / 2 :\n\t\t\t( 1 + bounceOut( 2 * x - 1 ) ) / 2;\n\t}\n});\n\n});\n"
  },
  {
    "path": "src/Ui/template/site_add.html",
    "content": "<html>\n<head>\n<title>Add new site</title>\n</head>\n<body>\n\n<style>\n.content { line-height: 24px; font-family: monospace; font-size: 14px; color: #636363; text-transform: uppercase; top: 38%; position: relative; text-align: center; }\n.content h1, .content h2 { font-weight: normal; letter-spacing: 1px; }\n.content h2 { font-size: 15px; margin-bottom: 50px }\n.content #details {\n    text-align: left; display: inline-block; width: 350px; background-color: white; padding: 17px 27px; border-radius: 0px;\n    box-shadow: 0px 2px 7px -1px #d8d8d8; text-transform: none; margin: 15px; transform: scale(0) rotateX(90deg); transition: all 0.6s cubic-bezier(0.785, 0.135, 0.15, 0.86);\n}\n.content #details #added { font-size: 12px; text-align: right; color: #a9a9a9; }\n\n.button {\n    padding: 8px 20px; background-color: #FFF85F; border-bottom: 2px solid #CDBD1E; border-radius: 2px;\n    text-decoration: none; transition: all 0.5s; background-position: left center; color: black;\n    border-left: 0px; border-top: 0px; border-right: 0px; font-family: monospace; font-size: 14px;\n}\n.button:hover { background-color: #FFF400; border-bottom: 2px solid #4D4D4C; transition: none; }\n.button:active { position: relative; top: 1px; }\n.button:focus { outline: none; }\n\n</style>\n\n<div class=\"content\">\n <h1>Add new site</h1>\n <h2>Please confirm before adding a new site to the client</h2>\n <form action=\"/add/\" method=\"POST\">\n  <input type=\"hidden\" name=\"add_nonce\" value=\"{add_nonce}\">\n  <input type=\"hidden\" name=\"address\" value=\"{address}\">\n  <input type=\"hidden\" name=\"url\" value=\"{url}\">\n  <input type=\"submit\" class=\"button button-submit\" id=\"button\" value=\"Load site\"/>\n </form>\n</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/Ui/template/wrapper.html",
    "content": "<!DOCTYPE html>\n\n<html>\n<head>\n <title>{title} - ZeroNet</title>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />\n <link rel=\"stylesheet\" href=\"/uimedia/all.css?rev={rev}\" />\n <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\">\n {meta_tags}\n</head>\n<body style=\"{body_style}\" class=\"{themeclass}\">\n\n<div class=\"unsupported\" id=\"unsupported\">\n <script nonce=\"{script_nonce}\">document.getElementById('unsupported').style.display = \"none\"</script>\n <h3>ZeroNet requires JavaScript support.</h3>If you use NoScript/Tor browser: Click on toolbar icon with the notification and choose \"Temp. TRUSTED\" for 127.0.0.1.\n</div>\n\n<script nonce=\"{script_nonce}\">\n// If we are inside iframe escape from it\nif (window.self !== window.top) {\n\twindow.open(window.location.toString().replace(/([&?])wrapper=False/, \"$1\").replace(/&$/, \"\").replace(/[&?]wrapper_nonce=[A-Za-z0-9]+/, \"\"), \"_top\");\n\twindow.stop();\n\tdocument.execCommand(\"Stop\", false);\n}\n\n// We are opened as a parent-window\nelse if (window.opener && window.opener.location.toString()) {\n\tdocument.write(\"Opened as child-window, stopping...\");\n\twindow.stop();\n\tdocument.execCommand(\"Stop\", false);\n}\n</script>\n\n<div class=\"progressbar\">\n <div class=\"peg\"></div>\n</div>\n\n<!-- Fixed button -->\n<div class='fixbutton'>\n <div class='fixbutton-text'><img width=30 src='/uimedia/img/logo-white.svg'/></div>\n <div class='fixbutton-burger'>&#x2261;</div>\n <a class='fixbutton-bg' href=\"{homepage}/\"></a>\n</div>\n\n<!-- Notifications -->\n<div class='notifications'>\n <div class='notification template'><span class='notification-icon'>!</span> <span class='body'>Test notification</span><a class=\"close\" href=\"#Close\">&times;</a><div style=\"clear: both\"></div></div>\n</div>\n\n<!-- Infopanel -->\n<div class='infopanel-container'>\n <div class='infopanel'>\n  <span class='closed-num'>8</span>\n  <div class=\"message\">\n   <span class='line line-1'>8 modified files</span><br><span class='line line-2'>content.json, data.json</span>\n  </div>\n  <a href=\"#Publish\" class=\"button button-submit\">Sign & Publish</a>\n  <a href=\"#Close\" class=\"close\">&times;</a>\n </div>\n</div>\n\n<!-- Loadingscreen -->\n<div class='loadingscreen'>\n <a href=\"/Config\" class=\"loading-config\">Config</a>\n <div class='loading-text console'>\n </div>\n <div class=\"flipper-container\">\n  <div class=\"flipper\"> <div class=\"front\"></div><div class=\"back\"></div> </div>\n </div>\n</div>\n\n\n<!-- Site Iframe -->\n<iframe src='about:blank' id='inner-iframe' sandbox=\"allow-forms allow-scripts allow-top-navigation allow-popups allow-modals allow-presentation allow-pointer-lock allow-popups-to-escape-sandbox {sandbox_permissions}\" allowfullscreen=\"true\" webkitallowfullscreen=\"true\" mozallowfullscreen=\"true\" oallowfullscreen=\"true\" msallowfullscreen=\"true\"></iframe>\n\n<!-- Site info -->\n<script id=\"script_init\" nonce=\"{script_nonce}\">\niframe_src = \"{file_url}{query_string}\"\nconsole.log(\"Changing url from \" + document.getElementById(\"inner-iframe\").src + \" to \" + iframe_src)\ndocument.getElementById(\"inner-iframe\").src = document.getElementById(\"inner-iframe\").src  // Workaround for Firefox back button bug\ndocument.getElementById(\"inner-iframe\").src = iframe_src\naddress = \"{address}\"\nwrapper_nonce = \"{wrapper_nonce}\"\nwrapper_key = \"{wrapper_key}\"\najax_key = \"{ajax_key}\"\npostmessage_nonce_security = {postmessage_nonce_security}\nfile_inner_path = \"{file_inner_path}\"\npermissions = {permissions}\nshow_loadingscreen = {show_loadingscreen}\nserver_url = '{server_url}'\nscript_nonce = '{script_nonce}'\n\nif (typeof WebSocket === \"undefined\") {\n\ttag = document.createElement('div');\n\ttag.innerHTML += \"<div class='unsupported'>Your browser does not support <a href='https://caniuse.com/#search=websocket'>WebSocket connections</a>.<br>Please use the latest <a href='http://outdatedbrowser.com'>Chrome or Firefox</a> browser.</div>\";\n\tdocument.body.appendChild(tag)\n}\n</script>\n<script type=\"text/javascript\" src=\"/uimedia/all.js?rev={rev}&lang={lang}\" nonce=\"{script_nonce}\"></script>\n<script nonce=\"{script_nonce}\">setTimeout(window.wrapper.onWrapperLoad, 1)</script>\n</body>\n</html>\n"
  },
  {
    "path": "src/User/User.py",
    "content": "import logging\nimport json\nimport time\nimport binascii\n\nimport gevent\n\nimport util\nfrom Crypt import CryptBitcoin\nfrom Plugin import PluginManager\nfrom Config import config\nfrom util import helper\nfrom Debug import Debug\n\n\n@PluginManager.acceptPlugins\nclass User(object):\n    def __init__(self, master_address=None, master_seed=None, data={}):\n        if master_seed:\n            self.master_seed = master_seed\n            self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)\n        elif master_address:\n            self.master_address = master_address\n            self.master_seed = data.get(\"master_seed\")\n        else:\n            self.master_seed = CryptBitcoin.newSeed()\n            self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)\n        self.sites = data.get(\"sites\", {})\n        self.certs = data.get(\"certs\", {})\n        self.settings = data.get(\"settings\", {})\n        self.delayed_save_thread = None\n\n        self.log = logging.getLogger(\"User:%s\" % self.master_address)\n\n    # Save to data/users.json\n    @util.Noparallel(queue=True, ignore_class=True)\n    def save(self):\n        s = time.time()\n        users = json.load(open(\"%s/users.json\" % config.data_dir))\n        if self.master_address not in users:\n            users[self.master_address] = {}  # Create if not exist\n        user_data = users[self.master_address]\n        if self.master_seed:\n            user_data[\"master_seed\"] = self.master_seed\n        user_data[\"sites\"] = self.sites\n        user_data[\"certs\"] = self.certs\n        user_data[\"settings\"] = self.settings\n        helper.atomicWrite(\"%s/users.json\" % config.data_dir, helper.jsonDumps(users).encode(\"utf8\"))\n        self.log.debug(\"Saved in %.3fs\" % (time.time() - s))\n        self.delayed_save_thread = None\n\n    def saveDelayed(self):\n        if not self.delayed_save_thread:\n            self.delayed_save_thread = gevent.spawn_later(5, self.save)\n\n    def getAddressAuthIndex(self, address):\n        return int(binascii.hexlify(address.encode()), 16)\n\n    @util.Noparallel()\n    def generateAuthAddress(self, address):\n        s = time.time()\n        address_id = self.getAddressAuthIndex(address)  # Convert site address to int\n        auth_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, address_id)\n        self.sites[address] = {\n            \"auth_address\": CryptBitcoin.privatekeyToAddress(auth_privatekey),\n            \"auth_privatekey\": auth_privatekey\n        }\n        self.saveDelayed()\n        self.log.debug(\"Added new site: %s in %.3fs\" % (address, time.time() - s))\n        return self.sites[address]\n\n    # Get user site data\n    # Return: {\"auth_address\": \"xxx\", \"auth_privatekey\": \"xxx\"}\n    def getSiteData(self, address, create=True):\n        if address not in self.sites:  # Generate new BIP32 child key based on site address\n            if not create:\n                return {\"auth_address\": None, \"auth_privatekey\": None}  # Dont create user yet\n            self.generateAuthAddress(address)\n        return self.sites[address]\n\n    def deleteSiteData(self, address):\n        if address in self.sites:\n            del(self.sites[address])\n            self.saveDelayed()\n            self.log.debug(\"Deleted site: %s\" % address)\n\n    def setSiteSettings(self, address, settings):\n        site_data = self.getSiteData(address)\n        site_data[\"settings\"] = settings\n        self.saveDelayed()\n        return site_data\n\n    # Get data for a new, unique site\n    # Return: [site_address, bip32_index, {\"auth_address\": \"xxx\", \"auth_privatekey\": \"xxx\", \"privatekey\": \"xxx\"}]\n    def getNewSiteData(self):\n        import random\n        bip32_index = random.randrange(2 ** 256) % 100000000\n        site_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, bip32_index)\n        site_address = CryptBitcoin.privatekeyToAddress(site_privatekey)\n        if site_address in self.sites:\n            raise Exception(\"Random error: site exist!\")\n        # Save to sites\n        self.getSiteData(site_address)\n        self.sites[site_address][\"privatekey\"] = site_privatekey\n        self.save()\n        return site_address, bip32_index, self.sites[site_address]\n\n    # Get BIP32 address from site address\n    # Return: BIP32 auth address\n    def getAuthAddress(self, address, create=True):\n        cert = self.getCert(address)\n        if cert:\n            return cert[\"auth_address\"]\n        else:\n            return self.getSiteData(address, create)[\"auth_address\"]\n\n    def getAuthPrivatekey(self, address, create=True):\n        cert = self.getCert(address)\n        if cert:\n            return cert[\"auth_privatekey\"]\n        else:\n            return self.getSiteData(address, create)[\"auth_privatekey\"]\n\n    # Add cert for the user\n    def addCert(self, auth_address, domain, auth_type, auth_user_name, cert_sign):\n        # Find privatekey by auth address\n        auth_privatekey = [site[\"auth_privatekey\"] for site in list(self.sites.values()) if site[\"auth_address\"] == auth_address][0]\n        cert_node = {\n            \"auth_address\": auth_address,\n            \"auth_privatekey\": auth_privatekey,\n            \"auth_type\": auth_type,\n            \"auth_user_name\": auth_user_name,\n            \"cert_sign\": cert_sign\n        }\n        # Check if we have already cert for that domain and its not the same\n        if self.certs.get(domain) and self.certs[domain] != cert_node:\n            return False\n        elif self.certs.get(domain) == cert_node:  # Same, not updated\n            return None\n        else:  # Not exist yet, add\n            self.certs[domain] = cert_node\n            self.save()\n            return True\n\n    # Remove cert from user\n    def deleteCert(self, domain):\n        del self.certs[domain]\n\n    # Set active cert for a site\n    def setCert(self, address, domain):\n        site_data = self.getSiteData(address)\n        if domain:\n            site_data[\"cert\"] = domain\n        else:\n            if \"cert\" in site_data:\n                del site_data[\"cert\"]\n        self.saveDelayed()\n        return site_data\n\n    # Get cert for the site address\n    # Return: { \"auth_address\":.., \"auth_privatekey\":.., \"auth_type\": \"web\", \"auth_user_name\": \"nofish\", \"cert_sign\":.. } or None\n    def getCert(self, address):\n        site_data = self.getSiteData(address, create=False)\n        if not site_data or \"cert\" not in site_data:\n            return None  # Site dont have cert\n        return self.certs.get(site_data[\"cert\"])\n\n    # Get cert user name for the site address\n    # Return: user@certprovider.bit or None\n    def getCertUserId(self, address):\n        site_data = self.getSiteData(address, create=False)\n        if not site_data or \"cert\" not in site_data:\n            return None  # Site dont have cert\n        cert = self.certs.get(site_data[\"cert\"])\n        if cert:\n            return cert[\"auth_user_name\"] + \"@\" + site_data[\"cert\"]\n"
  },
  {
    "path": "src/User/UserManager.py",
    "content": "# Included modules\nimport json\nimport logging\nimport time\n\n# ZeroNet Modules\nfrom .User import User\nfrom Plugin import PluginManager\nfrom Config import config\n\n\n@PluginManager.acceptPlugins\nclass UserManager(object):\n    def __init__(self):\n        self.users = {}\n        self.log = logging.getLogger(\"UserManager\")\n\n    # Load all user from data/users.json\n    def load(self):\n        if not self.users:\n            self.users = {}\n\n        user_found = []\n        added = 0\n        s = time.time()\n        # Load new users\n        try:\n            json_path = \"%s/users.json\" % config.data_dir\n            data = json.load(open(json_path))\n        except Exception as err:\n            raise Exception(\"Unable to load %s: %s\" % (json_path, err))\n\n        for master_address, data in list(data.items()):\n            if master_address not in self.users:\n                user = User(master_address, data=data)\n                self.users[master_address] = user\n                added += 1\n            user_found.append(master_address)\n\n        # Remove deleted adresses\n        for master_address in list(self.users.keys()):\n            if master_address not in user_found:\n                del(self.users[master_address])\n                self.log.debug(\"Removed user: %s\" % master_address)\n\n        if added:\n            self.log.debug(\"Added %s users in %.3fs\" % (added, time.time() - s))\n\n    # Create new user\n    # Return: User\n    def create(self, master_address=None, master_seed=None):\n        self.list()  # Load the users if it's not loaded yet\n        user = User(master_address, master_seed)\n        self.log.debug(\"Created user: %s\" % user.master_address)\n        if user.master_address:  # If successfully created\n            self.users[user.master_address] = user\n            user.saveDelayed()\n        return user\n\n    # List all users from data/users.json\n    # Return: {\"usermasteraddr\": User}\n    def list(self):\n        if self.users == {}:  # Not loaded yet\n            self.load()\n        return self.users\n\n    # Get user based on master_address\n    # Return: User or None\n    def get(self, master_address=None):\n        users = self.list()\n        if users:\n            return list(users.values())[0]  # Single user mode, always return the first\n        else:\n            return None\n\n\nuser_manager = UserManager()  # Singleton\n"
  },
  {
    "path": "src/User/__init__.py",
    "content": "from .User import User\n"
  },
  {
    "path": "src/Worker/Worker.py",
    "content": "import time\n\nimport gevent\nimport gevent.lock\n\nfrom Debug import Debug\nfrom Config import config\nfrom Content.ContentManager import VerifyError\n\n\nclass WorkerDownloadError(Exception):\n    pass\n\n\nclass WorkerIOError(Exception):\n    pass\n\n\nclass WorkerStop(Exception):\n    pass\n\n\nclass Worker(object):\n\n    def __init__(self, manager, peer):\n        self.manager = manager\n        self.peer = peer\n        self.task = None\n        self.key = None\n        self.running = False\n        self.thread = None\n        self.num_downloaded = 0\n        self.num_failed = 0\n\n    def __str__(self):\n        return \"Worker %s %s\" % (self.manager.site.address_short, self.key)\n\n    def __repr__(self):\n        return \"<%s>\" % self.__str__()\n\n    def waitForTask(self, task, timeout):  # Wait for other workers to finish the task\n        for sleep_i in range(1, timeout * 10):\n            time.sleep(0.1)\n            if task[\"done\"] or task[\"workers_num\"] == 0:\n                if config.verbose:\n                    self.manager.log.debug(\"%s: %s, picked task free after %ss sleep. (done: %s)\" % (\n                        self.key, task[\"inner_path\"], 0.1 * sleep_i, task[\"done\"]\n                    ))\n                break\n\n            if sleep_i % 10 == 0:\n                workers = self.manager.findWorkers(task)\n                if not workers or not workers[0].peer.connection:\n                    break\n                worker_idle = time.time() - workers[0].peer.connection.last_recv_time\n                if worker_idle > 1:\n                    if config.verbose:\n                        self.manager.log.debug(\"%s: %s, worker %s seems idle, picked up task after %ss sleep. (done: %s)\" % (\n                            self.key, task[\"inner_path\"], workers[0].key, 0.1 * sleep_i, task[\"done\"]\n                        ))\n                    break\n        return True\n\n    def pickTask(self):  # Find and select a new task for the worker\n        task = self.manager.getTask(self.peer)\n        if not task:  # No more task\n            time.sleep(0.1)  # Wait a bit for new tasks\n            task = self.manager.getTask(self.peer)\n            if not task:  # Still no task, stop it\n                stats = \"downloaded files: %s, failed: %s\" % (self.num_downloaded, self.num_failed)\n                self.manager.log.debug(\"%s: No task found, stopping (%s)\" % (self.key, stats))\n                return False\n\n        if not task[\"time_started\"]:\n            task[\"time_started\"] = time.time()  # Task started now\n\n        if task[\"workers_num\"] > 0:  # Wait a bit if someone already working on it\n            if task[\"peers\"]:  # It's an update\n                timeout = 3\n            else:\n                timeout = 1\n\n            if task[\"size\"] > 100 * 1024 * 1024:\n                timeout = timeout * 2\n\n            if config.verbose:\n                self.manager.log.debug(\"%s: Someone already working on %s (pri: %s), sleeping %s sec...\" % (\n                    self.key, task[\"inner_path\"], task[\"priority\"], timeout\n                ))\n\n            self.waitForTask(task, timeout)\n        return task\n\n    def downloadTask(self, task):\n        try:\n            buff = self.peer.getFile(task[\"site\"].address, task[\"inner_path\"], task[\"size\"])\n        except Exception as err:\n            self.manager.log.debug(\"%s: getFile error: %s\" % (self.key, err))\n            raise WorkerDownloadError(str(err))\n\n        if not buff:\n            raise WorkerDownloadError(\"No response\")\n\n        return buff\n\n    def getTaskLock(self, task):\n        if task[\"lock\"] is None:\n            task[\"lock\"] = gevent.lock.Semaphore()\n        return task[\"lock\"]\n\n    def writeTask(self, task, buff):\n        buff.seek(0)\n        try:\n            task[\"site\"].storage.write(task[\"inner_path\"], buff)\n        except Exception as err:\n            if type(err) == Debug.Notify:\n                self.manager.log.debug(\"%s: Write aborted: %s (%s: %s)\" % (self.key, task[\"inner_path\"], type(err), err))\n            else:\n                self.manager.log.error(\"%s: Error writing: %s (%s: %s)\" % (self.key, task[\"inner_path\"], type(err), err))\n            raise WorkerIOError(str(err))\n\n    def onTaskVerifyFail(self, task, error_message):\n        self.num_failed += 1\n        if self.manager.started_task_num < 50 or config.verbose:\n            self.manager.log.debug(\n                \"%s: Verify failed: %s, error: %s, failed peers: %s, workers: %s\" %\n                (self.key, task[\"inner_path\"], error_message, len(task[\"failed\"]), task[\"workers_num\"])\n            )\n        task[\"failed\"].append(self.peer)\n        self.peer.hash_failed += 1\n        if self.peer.hash_failed >= max(len(self.manager.tasks), 3) or self.peer.connection_error > 10:\n            # Broken peer: More fails than tasks number but atleast 3\n            raise WorkerStop(\n                \"Too many errors (hash failed: %s, connection error: %s)\" %\n                (self.peer.hash_failed, self.peer.connection_error)\n            )\n\n    def handleTask(self, task):\n        download_err = write_err = False\n\n        write_lock = None\n        try:\n            buff = self.downloadTask(task)\n\n            if task[\"done\"] is True:  # Task done, try to find new one\n                return None\n\n            if self.running is False:  # Worker no longer needed or got killed\n                self.manager.log.debug(\"%s: No longer needed, returning: %s\" % (self.key, task[\"inner_path\"]))\n                raise WorkerStop(\"Running got disabled\")\n\n            write_lock = self.getTaskLock(task)\n            write_lock.acquire()\n            if task[\"site\"].content_manager.verifyFile(task[\"inner_path\"], buff) is None:\n                is_same = True\n            else:\n                is_same = False\n            is_valid = True\n        except (WorkerDownloadError, VerifyError) as err:\n            download_err = err\n            is_valid = False\n            is_same = False\n\n        if is_valid and not is_same:\n            if self.manager.started_task_num < 50 or task[\"priority\"] > 10 or config.verbose:\n                self.manager.log.debug(\"%s: Verify correct: %s\" % (self.key, task[\"inner_path\"]))\n            try:\n                self.writeTask(task, buff)\n            except WorkerIOError as err:\n                write_err = err\n\n        if not task[\"done\"]:\n            if write_err:\n                self.manager.failTask(task, reason=\"Write error\")\n                self.num_failed += 1\n                self.manager.log.error(\"%s: Error writing %s: %s\" % (self.key, task[\"inner_path\"], write_err))\n            elif is_valid:\n                self.manager.doneTask(task)\n                self.num_downloaded += 1\n\n        if write_lock is not None and write_lock.locked():\n            write_lock.release()\n\n        if not is_valid:\n            self.onTaskVerifyFail(task, download_err)\n            time.sleep(1)\n            return False\n\n        return True\n\n    def downloader(self):\n        self.peer.hash_failed = 0  # Reset hash error counter\n        while self.running:\n            # Try to pickup free file download task\n            task = self.pickTask()\n\n            if not task:\n                break\n\n            if task[\"done\"]:\n                continue\n\n            self.task = task\n\n            self.manager.addTaskWorker(task, self)\n\n            try:\n                success = self.handleTask(task)\n            except WorkerStop as err:\n                self.manager.log.debug(\"%s: Worker stopped: %s\" % (self.key, err))\n                self.manager.removeTaskWorker(task, self)\n                break\n\n            self.manager.removeTaskWorker(task, self)\n\n        self.peer.onWorkerDone()\n        self.running = False\n        self.manager.removeWorker(self)\n\n    # Start the worker\n    def start(self):\n        self.running = True\n        self.thread = gevent.spawn(self.downloader)\n\n    # Skip current task\n    def skip(self, reason=\"Unknown\"):\n        self.manager.log.debug(\"%s: Force skipping (reason: %s)\" % (self.key, reason))\n        if self.thread:\n            self.thread.kill(exception=Debug.createNotifyType(\"Worker skipping (reason: %s)\" % reason))\n        self.start()\n\n    # Force stop the worker\n    def stop(self, reason=\"Unknown\"):\n        self.manager.log.debug(\"%s: Force stopping (reason: %s)\" % (self.key, reason))\n        self.running = False\n        if self.thread:\n            self.thread.kill(exception=Debug.createNotifyType(\"Worker stopped (reason: %s)\" % reason))\n        del self.thread\n        self.manager.removeWorker(self)\n"
  },
  {
    "path": "src/Worker/WorkerManager.py",
    "content": "import time\nimport logging\nimport collections\n\nimport gevent\n\nfrom .Worker import Worker\nfrom .WorkerTaskManager import WorkerTaskManager\nfrom Config import config\nfrom util import helper\nfrom Plugin import PluginManager\nfrom Debug.DebugLock import DebugLock\nimport util\n\n\n@PluginManager.acceptPlugins\nclass WorkerManager(object):\n\n    def __init__(self, site):\n        self.site = site\n        self.workers = {}  # Key: ip:port, Value: Worker.Worker\n        self.tasks = WorkerTaskManager()\n        self.next_task_id = 1\n        self.lock_add_task = DebugLock(name=\"Lock AddTask:%s\" % self.site.address_short)\n        # {\"id\": 1, \"evt\": evt, \"workers_num\": 0, \"site\": self.site, \"inner_path\": inner_path, \"done\": False, \"optional_hash_id\": None,\n        # \"time_started\": None, \"time_added\": time.time(), \"peers\": peers, \"priority\": 0, \"failed\": peer_ids, \"lock\": None or gevent.lock.RLock}\n        self.started_task_num = 0  # Last added task num\n        self.asked_peers = []\n        self.running = True\n        self.time_task_added = 0\n        self.log = logging.getLogger(\"WorkerManager:%s\" % self.site.address_short)\n        self.site.greenlet_manager.spawn(self.checkTasks)\n\n    def __str__(self):\n        return \"WorkerManager %s\" % self.site.address_short\n\n    def __repr__(self):\n        return \"<%s>\" % self.__str__()\n\n    # Check expired tasks\n    def checkTasks(self):\n        while self.running:\n            tasks = task = worker = workers = None  # Cleanup local variables\n            announced = False\n            time.sleep(15)  # Check every 15 sec\n\n            # Clean up workers\n            for worker in list(self.workers.values()):\n                if worker.task and worker.task[\"done\"]:\n                    worker.skip(reason=\"Task done\")  # Stop workers with task done\n\n            if not self.tasks:\n                continue\n\n            tasks = self.tasks[:]  # Copy it so removing elements wont cause any problem\n            num_tasks_started = len([task for task in tasks if task[\"time_started\"]])\n\n            self.log.debug(\n                \"Tasks: %s, started: %s, bad files: %s, total started: %s\" %\n                (len(tasks), num_tasks_started, len(self.site.bad_files), self.started_task_num)\n            )\n\n            for task in tasks:\n                if task[\"time_started\"] and time.time() >= task[\"time_started\"] + 60:\n                    self.log.debug(\"Timeout, Skipping: %s\" % task)  # Task taking too long time, skip it\n                    # Skip to next file workers\n                    workers = self.findWorkers(task)\n                    if workers:\n                        for worker in workers:\n                            worker.skip(reason=\"Task timeout\")\n                    else:\n                        self.failTask(task, reason=\"No workers\")\n\n                elif time.time() >= task[\"time_added\"] + 60 and not self.workers:  # No workers left\n                    self.failTask(task, reason=\"Timeout\")\n\n                elif (task[\"time_started\"] and time.time() >= task[\"time_started\"] + 15) or not self.workers:\n                    # Find more workers: Task started more than 15 sec ago or no workers\n                    workers = self.findWorkers(task)\n                    self.log.debug(\n                        \"Slow task: %s, (workers: %s, optional_hash_id: %s, peers: %s, failed: %s, asked: %s)\" %\n                        (\n                            task[\"inner_path\"], len(workers), task[\"optional_hash_id\"],\n                            len(task[\"peers\"] or []), len(task[\"failed\"]), len(self.asked_peers)\n                        )\n                    )\n                    if not announced and task[\"site\"].isAddedRecently():\n                        task[\"site\"].announce(mode=\"more\")  # Find more peers\n                        announced = True\n                    if task[\"optional_hash_id\"]:\n                        if self.workers:\n                            if not task[\"time_started\"]:\n                                ask_limit = 20\n                            else:\n                                ask_limit = max(10, time.time() - task[\"time_started\"])\n                            if len(self.asked_peers) < ask_limit and len(task[\"peers\"] or []) <= len(task[\"failed\"]) * 2:\n                                # Re-search for high priority\n                                self.startFindOptional(find_more=True)\n                        if task[\"peers\"]:\n                            peers_try = [peer for peer in task[\"peers\"] if peer not in task[\"failed\"] and peer not in workers]\n                            if peers_try:\n                                self.startWorkers(peers_try, force_num=5, reason=\"Task checker (optional, has peers)\")\n                            else:\n                                self.startFindOptional(find_more=True)\n                        else:\n                            self.startFindOptional(find_more=True)\n                    else:\n                        if task[\"peers\"]:  # Release the peer lock\n                            self.log.debug(\"Task peer lock release: %s\" % task[\"inner_path\"])\n                            task[\"peers\"] = []\n                        self.startWorkers(reason=\"Task checker\")\n\n            if len(self.tasks) > len(self.workers) * 2 and len(self.workers) < self.getMaxWorkers():\n                self.startWorkers(reason=\"Task checker (need more workers)\")\n\n        self.log.debug(\"checkTasks stopped running\")\n\n    # Returns the next free or less worked task\n    def getTask(self, peer):\n        for task in self.tasks:  # Find a task\n            if task[\"peers\"] and peer not in task[\"peers\"]:\n                continue  # This peer not allowed to pick this task\n            if peer in task[\"failed\"]:\n                continue  # Peer already tried to solve this, but failed\n            if task[\"optional_hash_id\"] and task[\"peers\"] is None:\n                continue  # No peers found yet for the optional task\n            if task[\"done\"]:\n                continue\n            return task\n\n    def removeSolvedFileTasks(self, mark_as_good=True):\n        for task in self.tasks[:]:\n            if task[\"inner_path\"] not in self.site.bad_files:\n                self.log.debug(\"No longer in bad_files, marking as %s: %s\" % (mark_as_good, task[\"inner_path\"]))\n                task[\"done\"] = True\n                task[\"evt\"].set(mark_as_good)\n                self.tasks.remove(task)\n        if not self.tasks:\n            self.started_task_num = 0\n        self.site.updateWebsocket()\n\n    # New peers added to site\n    def onPeers(self):\n        self.startWorkers(reason=\"More peers found\")\n\n    def getMaxWorkers(self):\n        if len(self.tasks) > 50:\n            return config.workers * 3\n        else:\n            return config.workers\n\n    # Add new worker\n    def addWorker(self, peer, multiplexing=False, force=False):\n        key = peer.key\n        if len(self.workers) > self.getMaxWorkers() and not force:\n            return False\n        if multiplexing:  # Add even if we already have worker for this peer\n            key = \"%s/%s\" % (key, len(self.workers))\n        if key not in self.workers:\n            # We dont have worker for that peer and workers num less than max\n            task = self.getTask(peer)\n            if task:\n                worker = Worker(self, peer)\n                self.workers[key] = worker\n                worker.key = key\n                worker.start()\n                return worker\n            else:\n                return False\n        else:  # We have worker for this peer or its over the limit\n            return False\n\n    def taskAddPeer(self, task, peer):\n        if task[\"peers\"] is None:\n            task[\"peers\"] = []\n        if peer in task[\"failed\"]:\n            return False\n\n        if peer not in task[\"peers\"]:\n            task[\"peers\"].append(peer)\n        return True\n\n    # Start workers to process tasks\n    def startWorkers(self, peers=None, force_num=0, reason=\"Unknown\"):\n        if not self.tasks:\n            return False  # No task for workers\n        max_workers = min(self.getMaxWorkers(), len(self.site.peers))\n        if len(self.workers) >= max_workers and not peers:\n            return False  # Workers number already maxed and no starting peers defined\n        self.log.debug(\n            \"Starting workers (%s), tasks: %s, peers: %s, workers: %s\" %\n            (reason, len(self.tasks), len(peers or []), len(self.workers))\n        )\n        if not peers:\n            peers = self.site.getConnectedPeers()\n            if len(peers) < max_workers:\n                peers += self.site.getRecentPeers(max_workers * 2)\n        if type(peers) is set:\n            peers = list(peers)\n\n        # Sort by ping\n        peers.sort(key=lambda peer: peer.connection.last_ping_delay if peer.connection and peer.connection.last_ping_delay and len(peer.connection.waiting_requests) == 0 and peer.connection.connected else 9999)\n\n        for peer in peers:  # One worker for every peer\n            if peers and peer not in peers:\n                continue  # If peers defined and peer not valid\n\n            if force_num:\n                worker = self.addWorker(peer, force=True)\n                force_num -= 1\n            else:\n                worker = self.addWorker(peer)\n\n            if worker:\n                self.log.debug(\"Added worker: %s (rep: %s), workers: %s/%s\" % (peer.key, peer.reputation, len(self.workers), max_workers))\n\n    # Find peers for optional hash in local hash tables and add to task peers\n    def findOptionalTasks(self, optional_tasks, reset_task=False):\n        found = collections.defaultdict(list)  # { found_hash: [peer1, peer2...], ...}\n\n        for peer in list(self.site.peers.values()):\n            if not peer.has_hashfield:\n                continue\n\n            hashfield_set = set(peer.hashfield)  # Finding in set is much faster\n            for task in optional_tasks:\n                optional_hash_id = task[\"optional_hash_id\"]\n                if optional_hash_id in hashfield_set:\n                    if reset_task and len(task[\"failed\"]) > 0:\n                        task[\"failed\"] = []\n                    if peer in task[\"failed\"]:\n                        continue\n                    if self.taskAddPeer(task, peer):\n                        found[optional_hash_id].append(peer)\n\n        return found\n\n    # Find peers for optional hash ids in local hash tables\n    def findOptionalHashIds(self, optional_hash_ids, limit=0):\n        found = collections.defaultdict(list)  # { found_hash_id: [peer1, peer2...], ...}\n\n        for peer in list(self.site.peers.values()):\n            if not peer.has_hashfield:\n                continue\n\n            hashfield_set = set(peer.hashfield)  # Finding in set is much faster\n            for optional_hash_id in optional_hash_ids:\n                if optional_hash_id in hashfield_set:\n                    found[optional_hash_id].append(peer)\n                    if limit and len(found[optional_hash_id]) >= limit:\n                        optional_hash_ids.remove(optional_hash_id)\n\n        return found\n\n    # Add peers to tasks from found result\n    def addOptionalPeers(self, found_ips):\n        found = collections.defaultdict(list)\n        for hash_id, peer_ips in found_ips.items():\n            task = [task for task in self.tasks if task[\"optional_hash_id\"] == hash_id]\n            if task:  # Found task, lets take the first\n                task = task[0]\n            else:\n                continue\n            for peer_ip in peer_ips:\n                peer = self.site.addPeer(peer_ip[0], peer_ip[1], return_peer=True, source=\"optional\")\n                if not peer:\n                    continue\n                if self.taskAddPeer(task, peer):\n                    found[hash_id].append(peer)\n                if peer.hashfield.appendHashId(hash_id):  # Peer has this file\n                    peer.time_hashfield = None  # Peer hashfield probably outdated\n\n        return found\n\n    # Start find peers for optional files\n    @util.Noparallel(blocking=False, ignore_args=True)\n    def startFindOptional(self, reset_task=False, find_more=False, high_priority=False):\n        # Wait for more file requests\n        if len(self.tasks) < 20 or high_priority:\n            time.sleep(0.01)\n        elif len(self.tasks) > 90:\n            time.sleep(5)\n        else:\n            time.sleep(0.5)\n\n        optional_tasks = [task for task in self.tasks if task[\"optional_hash_id\"]]\n        if not optional_tasks:\n            return False\n        optional_hash_ids = set([task[\"optional_hash_id\"] for task in optional_tasks])\n        time_tasks = self.time_task_added\n\n        self.log.debug(\n            \"Finding peers for optional files: %s (reset_task: %s, find_more: %s)\" %\n            (optional_hash_ids, reset_task, find_more)\n        )\n        found = self.findOptionalTasks(optional_tasks, reset_task=reset_task)\n\n        if found:\n            found_peers = set([peer for peers in list(found.values()) for peer in peers])\n            self.startWorkers(found_peers, force_num=3, reason=\"Optional found in local peers\")\n\n        if len(found) < len(optional_hash_ids) or find_more or (high_priority and any(len(peers) < 10 for peers in found.values())):\n            self.log.debug(\"No local result for optional files: %s\" % (optional_hash_ids - set(found)))\n\n            # Query hashfield from connected peers\n            threads = []\n            peers = self.site.getConnectedPeers()\n            if not peers:\n                peers = self.site.getConnectablePeers()\n            for peer in peers:\n                threads.append(self.site.greenlet_manager.spawn(peer.updateHashfield, force=find_more))\n            gevent.joinall(threads, timeout=5)\n\n            if time_tasks != self.time_task_added:  # New task added since start\n                optional_tasks = [task for task in self.tasks if task[\"optional_hash_id\"]]\n                optional_hash_ids = set([task[\"optional_hash_id\"] for task in optional_tasks])\n\n            found = self.findOptionalTasks(optional_tasks)\n            self.log.debug(\"Found optional files after query hashtable connected peers: %s/%s\" % (\n                len(found), len(optional_hash_ids)\n            ))\n\n            if found:\n                found_peers = set([peer for hash_id_peers in list(found.values()) for peer in hash_id_peers])\n                self.startWorkers(found_peers, force_num=3, reason=\"Optional found in connected peers\")\n\n        if len(found) < len(optional_hash_ids) or find_more:\n            self.log.debug(\n                \"No connected hashtable result for optional files: %s (asked: %s)\" %\n                (optional_hash_ids - set(found), len(self.asked_peers))\n            )\n            if not self.tasks:\n                self.log.debug(\"No tasks, stopping finding optional peers\")\n                return\n\n            # Try to query connected peers\n            threads = []\n            peers = [peer for peer in self.site.getConnectedPeers() if peer.key not in self.asked_peers][0:10]\n            if not peers:\n                peers = self.site.getConnectablePeers(ignore=self.asked_peers)\n\n            for peer in peers:\n                threads.append(self.site.greenlet_manager.spawn(peer.findHashIds, list(optional_hash_ids)))\n                self.asked_peers.append(peer.key)\n\n            for i in range(5):\n                time.sleep(1)\n\n                thread_values = [thread.value for thread in threads if thread.value]\n                if not thread_values:\n                    continue\n\n                found_ips = helper.mergeDicts(thread_values)\n                found = self.addOptionalPeers(found_ips)\n                self.log.debug(\"Found optional files after findhash connected peers: %s/%s (asked: %s)\" % (\n                    len(found), len(optional_hash_ids), len(threads)\n                ))\n\n                if found:\n                    found_peers = set([peer for hash_id_peers in list(found.values()) for peer in hash_id_peers])\n                    self.startWorkers(found_peers, force_num=3, reason=\"Optional found by findhash connected peers\")\n\n                if len(thread_values) == len(threads):\n                    # Got result from all started thread\n                    break\n\n        if len(found) < len(optional_hash_ids):\n            self.log.debug(\n                \"No findHash result, try random peers: %s (asked: %s)\" %\n                (optional_hash_ids - set(found), len(self.asked_peers))\n            )\n            # Try to query random peers\n\n            if time_tasks != self.time_task_added:  # New task added since start\n                optional_tasks = [task for task in self.tasks if task[\"optional_hash_id\"]]\n                optional_hash_ids = set([task[\"optional_hash_id\"] for task in optional_tasks])\n\n            threads = []\n            peers = self.site.getConnectablePeers(ignore=self.asked_peers)\n\n            for peer in peers:\n                threads.append(self.site.greenlet_manager.spawn(peer.findHashIds, list(optional_hash_ids)))\n                self.asked_peers.append(peer.key)\n\n            gevent.joinall(threads, timeout=15)\n\n            found_ips = helper.mergeDicts([thread.value for thread in threads if thread.value])\n            found = self.addOptionalPeers(found_ips)\n            self.log.debug(\"Found optional files after findhash random peers: %s/%s\" % (len(found), len(optional_hash_ids)))\n\n            if found:\n                found_peers = set([peer for hash_id_peers in list(found.values()) for peer in hash_id_peers])\n                self.startWorkers(found_peers, force_num=3, reason=\"Option found using findhash random peers\")\n\n        if len(found) < len(optional_hash_ids):\n            self.log.debug(\"No findhash result for optional files: %s\" % (optional_hash_ids - set(found)))\n\n        if time_tasks != self.time_task_added:  # New task added since start\n            self.log.debug(\"New task since start, restarting...\")\n            self.site.greenlet_manager.spawnLater(0.1, self.startFindOptional)\n        else:\n            self.log.debug(\"startFindOptional ended\")\n\n    # Stop all worker\n    def stopWorkers(self):\n        num = 0\n        for worker in list(self.workers.values()):\n            worker.stop(reason=\"Stopping all workers\")\n            num += 1\n        tasks = self.tasks[:]  # Copy\n        for task in tasks:  # Mark all current task as failed\n            self.failTask(task, reason=\"Stopping all workers\")\n        return num\n\n    # Find workers by task\n    def findWorkers(self, task):\n        workers = []\n        for worker in list(self.workers.values()):\n            if worker.task == task:\n                workers.append(worker)\n        return workers\n\n    # Ends and remove a worker\n    def removeWorker(self, worker):\n        worker.running = False\n        if worker.key in self.workers:\n            del(self.workers[worker.key])\n            self.log.debug(\"Removed worker, workers: %s/%s\" % (len(self.workers), self.getMaxWorkers()))\n        if len(self.workers) <= self.getMaxWorkers() / 3 and len(self.asked_peers) < 10:\n            optional_task = next((task for task in self.tasks if task[\"optional_hash_id\"]), None)\n            if optional_task:\n                if len(self.workers) == 0:\n                    self.startFindOptional(find_more=True)\n                else:\n                    self.startFindOptional()\n            elif self.tasks and not self.workers and worker.task and len(worker.task[\"failed\"]) < 20:\n                self.log.debug(\"Starting new workers... (tasks: %s)\" % len(self.tasks))\n                self.startWorkers(reason=\"Removed worker\")\n\n    # Tasks sorted by this\n    def getPriorityBoost(self, inner_path):\n        if inner_path == \"content.json\":\n            return 9999  # Content.json always priority\n        if inner_path == \"index.html\":\n            return 9998  # index.html also important\n        if \"-default\" in inner_path:\n            return -4  # Default files are cloning not important\n        elif inner_path.endswith(\"all.css\"):\n            return 14  # boost css files priority\n        elif inner_path.endswith(\"all.js\"):\n            return 13  # boost js files priority\n        elif inner_path.endswith(\"dbschema.json\"):\n            return 12  # boost database specification\n        elif inner_path.endswith(\"content.json\"):\n            return 1  # boost included content.json files priority a bit\n        elif inner_path.endswith(\".json\"):\n            if len(inner_path) < 50:  # Boost non-user json files\n                return 11\n            else:\n                return 2\n        return 0\n\n    def addTaskUpdate(self, task, peer, priority=0):\n        if priority > task[\"priority\"]:\n            self.tasks.updateItem(task, \"priority\", priority)\n        if peer and task[\"peers\"]:  # This peer also has new version, add it to task possible peers\n            task[\"peers\"].append(peer)\n            self.log.debug(\"Added peer %s to %s\" % (peer.key, task[\"inner_path\"]))\n            self.startWorkers([peer], reason=\"Added new task (update received by peer)\")\n        elif peer and peer in task[\"failed\"]:\n            task[\"failed\"].remove(peer)  # New update arrived, remove the peer from failed peers\n            self.log.debug(\"Removed peer %s from failed %s\" % (peer.key, task[\"inner_path\"]))\n            self.startWorkers([peer], reason=\"Added new task (peer failed before)\")\n\n    def addTaskCreate(self, inner_path, peer, priority=0, file_info=None):\n        evt = gevent.event.AsyncResult()\n        if peer:\n            peers = [peer]  # Only download from this peer\n        else:\n            peers = None\n        if not file_info:\n            file_info = self.site.content_manager.getFileInfo(inner_path)\n        if file_info and file_info[\"optional\"]:\n            optional_hash_id = helper.toHashId(file_info[\"sha512\"])\n        else:\n            optional_hash_id = None\n        if file_info:\n            size = file_info.get(\"size\", 0)\n        else:\n            size = 0\n\n        self.lock_add_task.acquire()\n\n        # Check again if we have task for this file\n        task = self.tasks.findTask(inner_path)\n        if task:\n            self.addTaskUpdate(task, peer, priority)\n            return task\n\n        priority += self.getPriorityBoost(inner_path)\n\n        if self.started_task_num == 0:  # Boost priority for first requested file\n            priority += 1\n\n        task = {\n            \"id\": self.next_task_id, \"evt\": evt, \"workers_num\": 0, \"site\": self.site, \"inner_path\": inner_path, \"done\": False,\n            \"optional_hash_id\": optional_hash_id, \"time_added\": time.time(), \"time_started\": None, \"lock\": None,\n            \"time_action\": None, \"peers\": peers, \"priority\": priority, \"failed\": [], \"size\": size\n        }\n\n        self.tasks.append(task)\n        self.lock_add_task.release()\n\n        self.next_task_id += 1\n        self.started_task_num += 1\n        if config.verbose:\n            self.log.debug(\n                \"New task: %s, peer lock: %s, priority: %s, optional_hash_id: %s, tasks started: %s\" %\n                (task[\"inner_path\"], peers, priority, optional_hash_id, self.started_task_num)\n            )\n\n        self.time_task_added = time.time()\n\n        if optional_hash_id:\n            if self.asked_peers:\n                del self.asked_peers[:]  # Reset asked peers\n            self.startFindOptional(high_priority=priority > 0)\n\n            if peers:\n                self.startWorkers(peers, reason=\"Added new optional task\")\n\n        else:\n            self.startWorkers(peers, reason=\"Added new task\")\n        return task\n\n    # Create new task and return asyncresult\n    def addTask(self, inner_path, peer=None, priority=0, file_info=None):\n        self.site.onFileStart(inner_path)  # First task, trigger site download started\n        task = self.tasks.findTask(inner_path)\n        if task:  # Already has task for that file\n            self.addTaskUpdate(task, peer, priority)\n        else:  # No task for that file yet\n            task = self.addTaskCreate(inner_path, peer, priority, file_info)\n        return task\n\n    def addTaskWorker(self, task, worker):\n        try:\n            self.tasks.updateItem(task, \"workers_num\", task[\"workers_num\"] + 1)\n        except ValueError:\n            task[\"workers_num\"] += 1\n\n    def removeTaskWorker(self, task, worker):\n        try:\n            self.tasks.updateItem(task, \"workers_num\", task[\"workers_num\"] - 1)\n        except ValueError:\n            task[\"workers_num\"] -= 1\n        if len(task[\"failed\"]) >= len(self.workers):\n            fail_reason = \"Too many fails: %s (workers: %s)\" % (len(task[\"failed\"]), len(self.workers))\n            self.failTask(task, reason=fail_reason)\n\n    # Wait for other tasks\n    def checkComplete(self):\n        time.sleep(0.1)\n        if not self.tasks:\n            self.log.debug(\"Check complete: No tasks\")\n            self.onComplete()\n\n    def onComplete(self):\n        self.started_task_num = 0\n        del self.asked_peers[:]\n        self.site.onComplete()  # No more task trigger site complete\n\n    # Mark a task done\n    def doneTask(self, task):\n        task[\"done\"] = True\n        self.tasks.remove(task)  # Remove from queue\n        if task[\"optional_hash_id\"]:\n            self.log.debug(\n                \"Downloaded optional file in %.3fs, adding to hashfield: %s\" %\n                (time.time() - task[\"time_started\"], task[\"inner_path\"])\n            )\n            self.site.content_manager.optionalDownloaded(task[\"inner_path\"], task[\"optional_hash_id\"], task[\"size\"])\n        self.site.onFileDone(task[\"inner_path\"])\n        task[\"evt\"].set(True)\n        if not self.tasks:\n            self.site.greenlet_manager.spawn(self.checkComplete)\n\n    # Mark a task failed\n    def failTask(self, task, reason=\"Unknown\"):\n        try:\n            self.tasks.remove(task)  # Remove from queue\n        except ValueError as err:\n            return False\n\n        self.log.debug(\"Task %s failed (Reason: %s)\" % (task[\"inner_path\"], reason))\n        task[\"done\"] = True\n        self.site.onFileFail(task[\"inner_path\"])\n        task[\"evt\"].set(False)\n        if not self.tasks:\n            self.site.greenlet_manager.spawn(self.checkComplete)\n"
  },
  {
    "path": "src/Worker/WorkerTaskManager.py",
    "content": "import bisect\nfrom collections.abc import MutableSequence\n\n\nclass CustomSortedList(MutableSequence):\n    def __init__(self):\n        super().__init__()\n        self.items = []  # (priority, added index, actual value)\n        self.logging = False\n\n    def __repr__(self):\n        return \"<{0} {1}>\".format(self.__class__.__name__, self.items)\n\n    def __len__(self):\n        return len(self.items)\n\n    def __getitem__(self, index):\n        if type(index) is int:\n            return self.items[index][2]\n        else:\n            return [item[2] for item in self.items[index]]\n\n    def __delitem__(self, index):\n        del self.items[index]\n\n    def __setitem__(self, index, value):\n        self.items[index] = self.valueToItem(value)\n\n    def __str__(self):\n        return str(self[:])\n\n    def insert(self, index, value):\n        self.append(value)\n\n    def append(self, value):\n        bisect.insort(self.items, self.valueToItem(value))\n\n    def updateItem(self, value, update_key=None, update_value=None):\n        self.remove(value)\n        if update_key is not None:\n            value[update_key] = update_value\n        self.append(value)\n\n    def sort(self, *args, **kwargs):\n        raise Exception(\"Sorted list can't be sorted\")\n\n    def valueToItem(self, value):\n        return (self.getPriority(value), self.getId(value), value)\n\n    def getPriority(self, value):\n        return value\n\n    def getId(self, value):\n        return id(value)\n\n    def indexSlow(self, value):\n        for pos, item in enumerate(self.items):\n            if item[2] == value:\n                return pos\n        return None\n\n    def index(self, value):\n        item = (self.getPriority(value), self.getId(value), value)\n        bisect_pos = bisect.bisect(self.items, item) - 1\n        if bisect_pos >= 0 and self.items[bisect_pos][2] == value:\n            return bisect_pos\n\n        # Item probably changed since added, switch to slow iteration\n        pos = self.indexSlow(value)\n\n        if self.logging:\n            print(\"Slow index for %s in pos %s bisect: %s\" % (item[2], pos, bisect_pos))\n\n        if pos is None:\n            raise ValueError(\"%r not in list\" % value)\n        else:\n            return pos\n\n    def __contains__(self, value):\n        try:\n            self.index(value)\n            return True\n        except ValueError:\n            return False\n\n\nclass WorkerTaskManager(CustomSortedList):\n    def __init__(self):\n        super().__init__()\n        self.inner_paths = {}\n\n    def getPriority(self, value):\n        return 0 - (value[\"priority\"] - value[\"workers_num\"] * 10)\n\n    def getId(self, value):\n        return value[\"id\"]\n\n    def __contains__(self, value):\n        return value[\"inner_path\"] in self.inner_paths\n\n    def __delitem__(self, index):\n        # Remove from inner path cache\n        del self.inner_paths[self.items[index][2][\"inner_path\"]]\n        super().__delitem__(index)\n\n    # Fast task search by inner_path\n\n    def append(self, task):\n        if task[\"inner_path\"] in self.inner_paths:\n            raise ValueError(\"File %s already has a task\" % task[\"inner_path\"])\n        super().append(task)\n        # Create inner path cache for faster lookup by filename\n        self.inner_paths[task[\"inner_path\"]] = task\n\n    def remove(self, task):\n        if task not in self:\n            raise ValueError(\"%r not in list\" % task)\n        else:\n            super().remove(task)\n\n    def findTask(self, inner_path):\n        return self.inner_paths.get(inner_path, None)\n"
  },
  {
    "path": "src/Worker/__init__.py",
    "content": "from .Worker import Worker\nfrom .WorkerManager import WorkerManager\n"
  },
  {
    "path": "src/__init__.py",
    "content": ""
  },
  {
    "path": "src/lib/__init__.py",
    "content": ""
  },
  {
    "path": "src/lib/bencode_open/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Ivan Machugovskiy\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "src/lib/bencode_open/__init__.py",
    "content": "def loads(data):\n    if not isinstance(data, bytes):\n        raise TypeError(\"Expected 'bytes' object, got {}\".format(type(data)))\n\n    offset = 0\n\n\n    def parseInteger():\n        nonlocal offset\n\n        offset += 1\n        had_digit = False\n        abs_value = 0\n\n        sign = 1\n        if data[offset] == ord(\"-\"):\n            sign = -1\n            offset += 1\n        while offset < len(data):\n            if data[offset] == ord(\"e\"):\n                # End of string\n                offset += 1\n                if not had_digit:\n                    raise ValueError(\"Integer without value\")\n                break\n            if ord(\"0\") <= data[offset] <= ord(\"9\"):\n                abs_value = abs_value * 10 + int(chr(data[offset]))\n                had_digit = True\n                offset += 1\n            else:\n                raise ValueError(\"Invalid integer\")\n        else:\n            raise ValueError(\"Unexpected EOF, expected integer\")\n\n        if not had_digit:\n            raise ValueError(\"Empty integer\")\n\n        return sign * abs_value\n\n\n    def parseString():\n        nonlocal offset\n\n        length = int(chr(data[offset]))\n        offset += 1\n\n        while offset < len(data):\n            if data[offset] == ord(\":\"):\n                offset += 1\n                break\n            if ord(\"0\") <= data[offset] <= ord(\"9\"):\n                length = length * 10 + int(chr(data[offset]))\n                offset += 1\n            else:\n                raise ValueError(\"Invalid string length\")\n        else:\n            raise ValueError(\"Unexpected EOF, expected string contents\")\n\n        if offset + length > len(data):\n            raise ValueError(\"Unexpected EOF, expected string contents\")\n        offset += length\n\n        return data[offset - length:offset]\n\n\n    def parseList():\n        nonlocal offset\n\n        offset += 1\n        values = []\n\n        while offset < len(data):\n            if data[offset] == ord(\"e\"):\n                # End of list\n                offset += 1\n                return values\n            else:\n                values.append(parse())\n\n        raise ValueError(\"Unexpected EOF, expected list contents\")\n\n\n    def parseDict():\n        nonlocal offset\n\n        offset += 1\n        items = {}\n\n        while offset < len(data):\n            if data[offset] == ord(\"e\"):\n                # End of list\n                offset += 1\n                return items\n            else:\n                key, value = parse(), parse()\n                if not isinstance(key, bytes):\n                    raise ValueError(\"A dict key must be a byte string\")\n                if key in items:\n                    raise ValueError(\"Duplicate dict key: {}\".format(key))\n                items[key] = value\n\n        raise ValueError(\"Unexpected EOF, expected dict contents\")\n\n\n    def parse():\n        nonlocal offset\n\n        if data[offset] == ord(\"i\"):\n            return parseInteger()\n        elif data[offset] == ord(\"l\"):\n            return parseList()\n        elif data[offset] == ord(\"d\"):\n            return parseDict()\n        elif ord(\"0\") <= data[offset] <= ord(\"9\"):\n            return parseString()\n\n        raise ValueError(\"Unknown type specifier: '{}'\".format(chr(data[offset])))\n\n    result = parse()\n\n    if offset != len(data):\n        raise ValueError(\"Expected EOF, got {} bytes left\".format(len(data) - offset))\n\n    return result\n\n\ndef dumps(data):\n    result = bytearray()\n\n\n    def convert(data):\n        nonlocal result\n\n        if isinstance(data, str):\n            raise ValueError(\"bencode only supports bytes, not str. Use encode\")\n\n        if isinstance(data, bytes):\n            result += str(len(data)).encode() + b\":\" + data\n        elif isinstance(data, int):\n            result += b\"i\" + str(data).encode() + b\"e\"\n        elif isinstance(data, list):\n            result += b\"l\"\n            for val in data:\n                convert(val)\n            result += b\"e\"\n        elif isinstance(data, dict):\n            result += b\"d\"\n            for key in sorted(data.keys()):\n                if not isinstance(key, bytes):\n                    raise ValueError(\"Dict key can only be bytes, not {}\".format(type(key)))\n                convert(key)\n                convert(data[key])\n            result += b\"e\"\n        else:\n            raise ValueError(\"bencode only supports bytes, int, list and dict\")\n\n\n    convert(data)\n\n    return bytes(result)\n"
  },
  {
    "path": "src/lib/cssvendor/__init__.py",
    "content": ""
  },
  {
    "path": "src/lib/cssvendor/cssvendor.py",
    "content": "import re\n\n\ndef prefix(content):\n    content = re.sub(\n        b\"@keyframes (.*? {.*?}\\s*})\", b\"@keyframes \\\\1\\n@-webkit-keyframes \\\\1\\n@-moz-keyframes \\\\1\\n\",\n        content, flags=re.DOTALL\n    )\n    content = re.sub(\n        b'([^-\\*])(border-radius|box-shadow|appearance|transition|animation|box-sizing|' +\n        b'backface-visibility|transform|filter|perspective|animation-[a-z-]+): (.*?)([;}])',\n        b'\\\\1-webkit-\\\\2: \\\\3; -moz-\\\\2: \\\\3; -o-\\\\2: \\\\3; -ms-\\\\2: \\\\3; \\\\2: \\\\3 \\\\4', content\n    )\n    content = re.sub(\n        b'(?<=[^a-zA-Z0-9-])([a-zA-Z0-9-]+): {0,1}(linear-gradient)\\((.*?)(\\)[;\\n])',\n        b'\\\\1: -webkit-\\\\2(\\\\3);' +\n        b'\\\\1: -moz-\\\\2(\\\\3);' +\n        b'\\\\1: -o-\\\\2(\\\\3);' +\n        b'\\\\1: -ms-\\\\2(\\\\3);' +\n        b'\\\\1: \\\\2(\\\\3);', content\n    )\n    return content\n\nif __name__ == \"__main__\":\n    print(prefix(b\"\"\"\n    .test {\n        border-radius: 5px;\n        background: linear-gradient(red, blue);\n    }\n\n\n    @keyframes flip {\n      0%   { transform: perspective(120px) rotateX(0deg) rotateY(0deg); }\n      50%  { transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }\n      100% { transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); }\n    }\n\n\n    \"\"\").decode(\"utf8\"))\n"
  },
  {
    "path": "src/lib/gevent_ws/__init__.py",
    "content": "from gevent.pywsgi import WSGIHandler, _InvalidClientInput\nfrom gevent.queue import Queue\nimport gevent\nimport hashlib\nimport base64\nimport struct\nimport socket\nimport time\nimport sys\n\n\nSEND_PACKET_SIZE = 1300\nOPCODE_TEXT = 1\nOPCODE_BINARY = 2\nOPCODE_CLOSE = 8\nOPCODE_PING = 9\nOPCODE_PONG = 10\nSTATUS_OK = 1000\nSTATUS_PROTOCOL_ERROR = 1002\nSTATUS_DATA_ERROR = 1007\nSTATUS_POLICY_VIOLATION = 1008\nSTATUS_TOO_LONG = 1009\n\n\nclass WebSocket:\n    def __init__(self, socket):\n        self.socket = socket\n        self.closed = False\n        self.status = None\n        self._receive_error = None\n        self._queue = Queue()\n        self.max_length = 10 * 1024 * 1024\n        gevent.spawn(self._listen)\n\n\n    def set_max_message_length(self, length):\n        self.max_length = length\n\n\n    def _listen(self):\n        try:\n            while True:\n                fin = False\n                message = bytearray()\n                is_first_message = True\n                start_opcode = None\n                while not fin:\n                    payload, opcode, fin = self._get_frame(max_length=self.max_length - len(message))\n                    # Make sure continuation frames have correct information\n                    if not is_first_message and opcode != 0:\n                        self._error(STATUS_PROTOCOL_ERROR)\n                    if is_first_message:\n                        if opcode not in (OPCODE_TEXT, OPCODE_BINARY):\n                            self._error(STATUS_PROTOCOL_ERROR)\n                        # Save opcode\n                        start_opcode = opcode\n                    message += payload\n                    is_first_message = False\n                message = bytes(message)\n                if start_opcode == OPCODE_TEXT:  # UTF-8 text\n                    try:\n                        message = message.decode()\n                    except UnicodeDecodeError:\n                        self._error(STATUS_DATA_ERROR)\n                self._queue.put(message)\n        except Exception as e:\n            self.closed = True\n            self._receive_error = e\n            self._queue.put(None)  # To make sure the error is read\n\n\n    def receive(self):\n        if not self._queue.empty():\n            return self.receive_nowait()\n        if isinstance(self._receive_error, EOFError):\n            return None\n        if self._receive_error:\n            raise self._receive_error\n        self._queue.peek()\n        return self.receive_nowait()\n\n\n    def receive_nowait(self):\n        ret = self._queue.get_nowait()\n        if self._receive_error and not isinstance(self._receive_error, EOFError):\n            raise self._receive_error\n        return ret\n\n\n    def send(self, data):\n        if self.closed:\n            raise EOFError()\n        if isinstance(data, str):\n            self._send_frame(OPCODE_TEXT, data.encode())\n        elif isinstance(data, bytes):\n            self._send_frame(OPCODE_BINARY, data)\n        else:\n            raise TypeError(\"Expected str or bytes, got \" + repr(type(data)))\n\n\n    # Reads a frame from the socket. Pings, pongs and close packets are handled\n    # automatically\n    def _get_frame(self, max_length):\n        while True:\n            payload, opcode, fin = self._read_frame(max_length=max_length)\n            if opcode == OPCODE_PING:\n                self._send_frame(OPCODE_PONG, payload)\n            elif opcode == OPCODE_PONG:\n                pass\n            elif opcode == OPCODE_CLOSE:\n                if len(payload) >= 2:\n                    self.status = struct.unpack(\"!H\", payload[:2])[0]\n                was_closed = self.closed\n                self.closed = True\n                if not was_closed:\n                    # Send a close frame in response\n                    self.close(STATUS_OK)\n                raise EOFError()\n            else:\n                return payload, opcode, fin\n\n\n    # Low-level function, use _get_frame instead\n    def _read_frame(self, max_length):\n        header = self._recv_exactly(2)\n\n        if not (header[1] & 0x80):\n            self._error(STATUS_POLICY_VIOLATION)\n\n        opcode = header[0] & 0xf\n        fin = bool(header[0] & 0x80)\n\n        payload_length = header[1] & 0x7f\n        if payload_length == 126:\n            payload_length = struct.unpack(\"!H\", self._recv_exactly(2))[0]\n        elif payload_length == 127:\n            payload_length = struct.unpack(\"!Q\", self._recv_exactly(8))[0]\n\n        # Control frames are handled in a special way\n        if opcode in (OPCODE_PING, OPCODE_PONG):\n            max_length = 125\n\n        if payload_length > max_length:\n            self._error(STATUS_TOO_LONG)\n\n        mask = self._recv_exactly(4)\n        payload = self._recv_exactly(payload_length)\n        payload = self._unmask(payload, mask)\n\n        return payload, opcode, fin\n\n\n    def _recv_exactly(self, length):\n        buf = bytearray()\n        while len(buf) < length:\n            block = self.socket.recv(min(4096, length - len(buf)))\n            if block == b\"\":\n                raise EOFError()\n            buf += block\n        return bytes(buf)\n\n\n    def _unmask(self, payload, mask):\n        def gen(c):\n            return bytes([x ^ c for x in range(256)])\n\n\n        payload = bytearray(payload)\n        payload[0::4] = payload[0::4].translate(gen(mask[0]))\n        payload[1::4] = payload[1::4].translate(gen(mask[1]))\n        payload[2::4] = payload[2::4].translate(gen(mask[2]))\n        payload[3::4] = payload[3::4].translate(gen(mask[3]))\n        return bytes(payload)\n\n\n    def _send_frame(self, opcode, data):\n        for i in range(0, len(data), SEND_PACKET_SIZE):\n            part = data[i:i + SEND_PACKET_SIZE]\n            fin = int(i == (len(data) - 1) // SEND_PACKET_SIZE * SEND_PACKET_SIZE)\n            header = bytes(\n                [\n                    (opcode if i == 0 else 0) | (fin << 7),\n                    min(len(part), 126)\n                ]\n            )\n            if len(part) >= 126:\n                header += struct.pack(\"!H\", len(part))\n            self.socket.sendall(header + part)\n\n\n    def _error(self, status):\n        self.close(status)\n        raise EOFError()\n\n\n    def close(self, status=STATUS_OK):\n        self.closed = True\n        try:\n            self._send_frame(OPCODE_CLOSE, struct.pack(\"!H\", status))\n        except (BrokenPipeError, ConnectionResetError):\n            pass\n        self.socket.close()\n\n\nclass WebSocketHandler(WSGIHandler):\n    def handle_one_response(self):\n        self.time_start = time.time()\n        self.status = None\n        self.headers_sent = False\n\n        self.result = None\n        self.response_use_chunked = False\n        self.response_length = 0\n\n\n        http_connection = [s.strip().lower() for s in self.environ.get(\"HTTP_CONNECTION\", \"\").split(\",\")]\n        if \"upgrade\" not in http_connection or self.environ.get(\"HTTP_UPGRADE\", \"\").lower() != \"websocket\":\n            # Not my problem\n            return super(WebSocketHandler, self).handle_one_response()\n\n        if \"HTTP_SEC_WEBSOCKET_KEY\" not in self.environ:\n            self.start_response(\"400 Bad Request\", [])\n            return\n\n        # Generate Sec-Websocket-Accept header\n        accept = self.environ[\"HTTP_SEC_WEBSOCKET_KEY\"].encode()\n        accept += b\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\"\n        accept = base64.b64encode(hashlib.sha1(accept).digest()).decode()\n\n        # Accept\n        self.start_response(\"101 Switching Protocols\", [\n            (\"Upgrade\", \"websocket\"),\n            (\"Connection\", \"Upgrade\"),\n            (\"Sec-Websocket-Accept\", accept)\n        ])(b\"\")\n\n        self.environ[\"wsgi.websocket\"] = WebSocket(self.socket)\n\n        # Can't call super because it sets invalid flags like \"status\"\n        try:\n            try:\n                self.run_application()\n            finally:\n                try:\n                    self.wsgi_input._discard()\n                except (socket.error, IOError):\n                    pass\n        except _InvalidClientInput:\n            self._send_error_response_if_possible(400)\n        except socket.error as ex:\n            if ex.args[0] in self.ignored_socket_errors:\n                self.close_connection = True\n            else:\n                self.handle_error(*sys.exc_info())\n        except:  # pylint:disable=bare-except\n            self.handle_error(*sys.exc_info())\n        finally:\n            self.time_finish = time.time()\n            self.log_request()\n            self.close_connection = True\n\n\n    def process_result(self):\n        if \"wsgi.websocket\" in self.environ:\n            if self.result is None:\n                return\n            # Flushing result is required for werkzeug compatibility\n            for elem in self.result:\n                pass\n        else:\n            super(WebSocketHandler, self).process_result()\n\n\n    @property\n    def version(self):\n        if not self.environ:\n            return None\n\n        return self.environ.get('HTTP_SEC_WEBSOCKET_VERSION')\n"
  },
  {
    "path": "src/lib/libsecp256k1message/__init__.py",
    "content": "from .libsecp256k1message import *"
  },
  {
    "path": "src/lib/libsecp256k1message/libsecp256k1message.py",
    "content": "import hashlib\r\nimport base64\r\nfrom coincurve import PrivateKey, PublicKey\r\nfrom base58 import b58encode_check, b58decode_check\r\nfrom hmac import compare_digest\r\nfrom util.Electrum import format as zero_format\r\n\r\nRECID_MIN = 0\r\nRECID_MAX = 3\r\nRECID_UNCOMPR = 27\r\nLEN_COMPACT_SIG = 65\r\n\r\nclass SignatureError(ValueError):\r\n    pass\r\n\r\ndef bitcoin_address():\r\n    \"\"\"Generate a public address and a secret address.\"\"\"\r\n    publickey, secretkey = key_pair()\r\n\r\n    public_address = compute_public_address(publickey)\r\n    secret_address = compute_secret_address(secretkey)\r\n\r\n    return (public_address, secret_address)\r\n\r\ndef key_pair():\r\n    \"\"\"Generate a public key and a secret key.\"\"\"\r\n    secretkey = PrivateKey()\r\n    publickey = PublicKey.from_secret(secretkey.secret)\r\n    return (publickey, secretkey)\r\n\r\ndef compute_public_address(publickey, compressed=False):\r\n    \"\"\"Convert a public key to a public Bitcoin address.\"\"\"\r\n    public_plain = b'\\x00' + public_digest(publickey, compressed=compressed)\r\n    return b58encode_check(public_plain)\r\n\r\ndef compute_secret_address(secretkey):\r\n    \"\"\"Convert a secret key to a secret Bitcoin address.\"\"\"\r\n    secret_plain = b'\\x80' + secretkey.secret\r\n    return b58encode_check(secret_plain)\r\n\r\ndef public_digest(publickey, compressed=False):\r\n    \"\"\"Convert a public key to ripemd160(sha256()) digest.\"\"\"\r\n    publickey_hex = publickey.format(compressed=compressed)\r\n    return hashlib.new('ripemd160', hashlib.sha256(publickey_hex).digest()).digest()\r\n\r\ndef address_public_digest(address):\r\n    \"\"\"Convert a public Bitcoin address to ripemd160(sha256()) digest.\"\"\"\r\n    public_plain = b58decode_check(address)\r\n    if not public_plain.startswith(b'\\x00') or len(public_plain) != 21:\r\n        raise ValueError('Invalid public key digest')\r\n    return public_plain[1:]\r\n\r\ndef _decode_bitcoin_secret(address):\r\n    secret_plain = b58decode_check(address)\r\n    if not secret_plain.startswith(b'\\x80') or len(secret_plain) != 33:\r\n        raise ValueError('Invalid secret key. Uncompressed keys only.')\r\n    return secret_plain[1:]\r\n\r\ndef recover_public_key(signature, message):\r\n    \"\"\"Recover public key from signature and message.\r\n    Recovered public key guarantees a correct signature\"\"\"\r\n    return PublicKey.from_signature_and_message(signature, message)\r\n\r\ndef decode_secret_key(address):\r\n    \"\"\"Convert a secret Bitcoin address to a secret key.\"\"\"\r\n    return PrivateKey(_decode_bitcoin_secret(address))\r\n\r\n\r\ndef coincurve_sig(electrum_signature):\r\n    # coincurve := r + s + recovery_id\r\n    # where (0 <= recovery_id <= 3)\r\n    # https://github.com/bitcoin-core/secp256k1/blob/0b7024185045a49a1a6a4c5615bf31c94f63d9c4/src/modules/recovery/main_impl.h#L35\r\n    if len(electrum_signature) != LEN_COMPACT_SIG:\r\n        raise ValueError('Not a 65-byte compact signature.')\r\n    # Compute coincurve recid\r\n    recid = (electrum_signature[0] - 27) & 3\r\n    if not (RECID_MIN <= recid <= RECID_MAX):\r\n        raise ValueError('Recovery ID %d is not supported.' % recid)\r\n    recid_byte = int.to_bytes(recid, length=1, byteorder='big')\r\n    return electrum_signature[1:] + recid_byte\r\n\r\n\r\ndef electrum_sig(coincurve_signature):\r\n    # electrum := recovery_id + r + s\r\n    # where (27 <= recovery_id <= 30)\r\n    # https://github.com/scintill/bitcoin-signature-tools/blob/ed3f5be5045af74a54c92d3648de98c329d9b4f7/key.cpp#L285\r\n    if len(coincurve_signature) != LEN_COMPACT_SIG:\r\n        raise ValueError('Not a 65-byte compact signature.')\r\n    # Compute Electrum recid\r\n    recid = coincurve_signature[-1] + RECID_UNCOMPR\r\n    if not (RECID_UNCOMPR + RECID_MIN <= recid <= RECID_UNCOMPR + RECID_MAX):\r\n        raise ValueError('Recovery ID %d is not supported.' % recid)\r\n    recid_byte = int.to_bytes(recid, length=1, byteorder='big')\r\n    return recid_byte + coincurve_signature[0:-1]\r\n\r\ndef sign_data(secretkey, byte_string):\r\n    \"\"\"Sign [byte_string] with [secretkey].\r\n    Return serialized signature compatible with Electrum (ZeroNet).\"\"\"\r\n    # encode the message\r\n    encoded = zero_format(byte_string)\r\n    # sign the message and get a coincurve signature\r\n    signature = secretkey.sign_recoverable(encoded)\r\n    # reserialize signature and return it\r\n    return electrum_sig(signature)\r\n\r\ndef verify_data(key_digest, electrum_signature, byte_string):\r\n    \"\"\"Verify if [electrum_signature] of [byte_string] is correctly signed and\r\n    is signed with the secret counterpart of [key_digest].\r\n    Raise SignatureError if the signature is forged or otherwise problematic.\"\"\"\r\n    # reserialize signature\r\n    signature = coincurve_sig(electrum_signature)\r\n    # encode the message\r\n    encoded = zero_format(byte_string)\r\n    # recover full public key from signature\r\n    # \"which guarantees a correct signature\"\r\n    publickey = recover_public_key(signature, encoded)\r\n\r\n    # verify that the message is correctly signed by the public key\r\n    # correct_sig = verify_sig(publickey, signature, encoded)\r\n\r\n    # verify that the public key is what we expect\r\n    correct_key = verify_key(publickey, key_digest)\r\n\r\n    if not correct_key:\r\n        raise SignatureError('Signature is forged!')\r\n\r\ndef verify_sig(publickey, signature, byte_string):\r\n    return publickey.verify(signature, byte_string)\r\n\r\ndef verify_key(publickey, key_digest):\r\n    return compare_digest(key_digest, public_digest(publickey))\r\n\r\ndef recover_address(data, sign):\r\n    sign_bytes = base64.b64decode(sign)\r\n    is_compressed = ((sign_bytes[0] - 27) & 4) != 0\r\n    publickey = recover_public_key(coincurve_sig(sign_bytes), zero_format(data))\r\n    return compute_public_address(publickey, compressed=is_compressed)\r\n\r\n__all__ = [\r\n    'SignatureError',\r\n    'key_pair', 'compute_public_address', 'compute_secret_address',\r\n    'public_digest', 'address_public_digest', 'recover_public_key', 'decode_secret_key',\r\n    'sign_data', 'verify_data', \"recover_address\"\r\n]\r\n\r\nif __name__ == \"__main__\":\r\n    import base64, time, multiprocessing\r\n    s = time.time()\r\n    privatekey = decode_secret_key(b\"5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk\")\r\n    threads = []\r\n    for i in range(1000):\r\n        data = bytes(\"hello\", \"utf8\")\r\n        address = recover_address(data, \"HGbib2kv9gm9IJjDt1FXbXFczZi35u0rZR3iPUIt5GglDDCeIQ7v8eYXVNIaLoJRI4URGZrhwmsYQ9aVtRTnTfQ=\")\r\n    print(\"- Verify x10000: %.3fs %s\" % (time.time() - s, address))\r\n\r\n    s = time.time()\r\n    for i in range(1000):\r\n        privatekey = decode_secret_key(b\"5JsunC55XGVqFQj5kPGK4MWgTL26jKbnPhjnmchSNPo75XXCwtk\")\r\n        sign = sign_data(privatekey, b\"hello\")\r\n        sign_b64 = base64.b64encode(sign)\r\n\r\n    print(\"- Sign x1000: %.3fs\" % (time.time() - s))\r\n"
  },
  {
    "path": "src/lib/openssl/openssl.cnf",
    "content": "[ req ]\ndefault_bits        = 2048\ndefault_keyfile     = server-key.pem\ndistinguished_name  = subject\nreq_extensions      = req_ext\nx509_extensions     = x509_ext\nstring_mask         = utf8only\n\n# The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description).\n#   Its sort of a mashup. For example, RFC 4514 does not provide emailAddress.\n[ subject ]\ncountryName         = US\nstateOrProvinceName = NY\nlocalityName        = New York\norganizationName    = Example, LLC\n\n# Use a friendly name here because its presented to the user. The server's DNS\n#   names are placed in Subject Alternate Names. Plus, DNS names here is deprecated\n#   by both IETF and CA/Browser Forums. If you place a DNS name here, then you\n#   must include the DNS name in the SAN too (otherwise, Chrome and others that\n#   strictly follow the CA/Browser Baseline Requirements will fail).\ncommonName          = Example Company\n\nemailAddress        = test@example.com\n\n# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ...\n[ x509_ext ]\n\nsubjectKeyIdentifier    = hash\nauthorityKeyIdentifier  = keyid,issuer\n\nbasicConstraints        = CA:FALSE\nkeyUsage                = digitalSignature, keyEncipherment\nextendedKeyUsage        = clientAuth, serverAuth\nsubjectAltName          = @alternate_names\n\n# RFC 5280, Section 4.2.1.12 makes EKU optional\n# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused\n# extendedKeyUsage  = serverAuth, clientAuth\n\n# Section req_ext is used when generating a certificate signing request. I.e., openssl req ...\n[ req_ext ]\n\nsubjectKeyIdentifier    = hash\n\nbasicConstraints        = CA:FALSE\nkeyUsage                = digitalSignature, keyEncipherment\nextendedKeyUsage        = clientAuth, serverAuth\nsubjectAltName          = @alternate_names\n\n# RFC 5280, Section 4.2.1.12 makes EKU optional\n# CA/Browser Baseline Requirements, Appendix (B)(3)(G) makes me confused\n# extendedKeyUsage  = serverAuth, clientAuth\n\n[ alternate_names ]\n\nDNS.1       = $ENV::CN\nDNS.2       = www.$ENV::CN"
  },
  {
    "path": "src/lib/pyaes/LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014 Richard Moore\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n"
  },
  {
    "path": "src/lib/pyaes/README.md",
    "content": "pyaes\n=====\n\nA pure-Python implementation of the AES block cipher algorithm and the common modes of operation (CBC, CFB, CTR, ECB and OFB).\n\n\nFeatures\n--------\n\n* Supports all AES key sizes\n* Supports all AES common modes\n* Pure-Python (no external dependencies)\n* BlockFeeder API allows streams to easily be encrypted and decrypted\n* Python 2.x and 3.x support (make sure you pass in bytes(), not strings for Python 3)\n\n\nAPI\n---\n\nAll keys may be 128 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32 bytes) long.\n\nTo generate a random key use:\n```python\nimport os\n\n# 128 bit, 192 bit and 256 bit keys\nkey_128 = os.urandom(16)\nkey_192 = os.urandom(24)\nkey_256 = os.urandom(32)\n```\n\nTo generate keys from simple-to-remember passwords, consider using a _password-based key-derivation function_ such as [scrypt](https://github.com/ricmoo/pyscrypt).\n\n\n### Common Modes of Operation\n\nThere are many modes of operations, each with various pros and cons. In general though, the **CBC** and **CTR** modes are recommended. The **ECB is NOT recommended.**, and is included primarily for completeness.\n\nEach of the following examples assumes the following key:\n```python\nimport pyaes\n\n# A 256 bit (32 byte) key\nkey = \"This_key_for_demo_purposes_only!\"\n\n# For some modes of operation we need a random initialization vector\n# of 16 bytes\niv = \"InitializationVe\"\n```\n\n\n#### Counter Mode of Operation (recommended)\n\n```python\naes = pyaes.AESModeOfOperationCTR(key)\nplaintext = \"Text may be any length you wish, no padding is required\"\nciphertext = aes.encrypt(plaintext)\n\n# '''\\xb6\\x99\\x10=\\xa4\\x96\\x88\\xd1\\x89\\x1co\\xe6\\x1d\\xef;\\x11\\x03\\xe3\\xee\n#    \\xa9V?wY\\xbfe\\xcdO\\xe3\\xdf\\x9dV\\x19\\xe5\\x8dk\\x9fh\\xb87>\\xdb\\xa3\\xd6\n#    \\x86\\xf4\\xbd\\xb0\\x97\\xf1\\t\\x02\\xe9 \\xed'''\nprint repr(ciphertext)\n\n# The counter mode of operation maintains state, so decryption requires\n# a new instance be created\naes = pyaes.AESModeOfOperationCTR(key)\ndecrypted = aes.decrypt(ciphertext)\n\n# True\nprint decrypted == plaintext\n\n# To use a custom initial value\ncounter = pyaes.Counter(initial_value = 100)\naes = pyaes.AESModeOfOperationCTR(key, counter = counter)\nciphertext = aes.encrypt(plaintext)\n\n# '''WZ\\x844\\x02\\xbfoY\\x1f\\x12\\xa6\\xce\\x03\\x82Ei)\\xf6\\x97mX\\x86\\xe3\\x9d\n#    _1\\xdd\\xbd\\x87\\xb5\\xccEM_4\\x01$\\xa6\\x81\\x0b\\xd5\\x04\\xd7Al\\x07\\xe5\n#    \\xb2\\x0e\\\\\\x0f\\x00\\x13,\\x07'''\nprint repr(ciphertext)\n```\n\n\n#### Cipher-Block Chaining (recommended)\n\n```python\naes = pyaes.AESModeOfOperationCBC(key, iv = iv)\nplaintext = \"TextMustBe16Byte\"\nciphertext = aes.encrypt(plaintext)\n\n# '\\xd6:\\x18\\xe6\\xb1\\xb3\\xc3\\xdc\\x87\\xdf\\xa7|\\x08{k\\xb6'\nprint repr(ciphertext)\n\n\n# The cipher-block chaining mode of operation maintains state, so\n# decryption requires a new instance be created\naes = pyaes.AESModeOfOperationCBC(key, iv = iv)\ndecrypted = aes.decrypt(ciphertext)\n\n# True\nprint decrypted == plaintext\n```\n\n\n#### Cipher Feedback\n\n```python\n# Each block into the mode of operation must be a multiple of the segment\n# size. For this example we choose 8 bytes.\naes = pyaes.AESModeOfOperationCFB(key, iv = iv, segment_size = 8)\nplaintext =  \"TextMustBeAMultipleOfSegmentSize\"\nciphertext = aes.encrypt(plaintext)\n\n# '''v\\xa9\\xc1w\"\\x8aL\\x93\\xcb\\xdf\\xa0/\\xf8Y\\x0b\\x8d\\x88i\\xcb\\x85rmp\n#    \\x85\\xfe\\xafM\\x0c)\\xd5\\xeb\\xaf'''\nprint repr(ciphertext)\n\n\n# The cipher-block chaining mode of operation maintains state, so\n# decryption requires a new instance be created\naes = pyaes.AESModeOfOperationCFB(key, iv = iv, segment_size = 8)\ndecrypted = aes.decrypt(ciphertext)\n\n# True\nprint decrypted == plaintext\n```\n\n\n#### Output Feedback Mode of Operation\n\n```python\naes = pyaes.AESModeOfOperationOFB(key, iv = iv)\nplaintext = \"Text may be any length you wish, no padding is required\"\nciphertext = aes.encrypt(plaintext)\n\n# '''v\\xa9\\xc1wO\\x92^\\x9e\\rR\\x1e\\xf7\\xb1\\xa2\\x9d\"l1\\xc7\\xe7\\x9d\\x87(\\xc26s\n#    \\xdd8\\xc8@\\xb6\\xd9!\\xf5\\x0cM\\xaa\\x9b\\xc4\\xedLD\\xe4\\xb9\\xd8\\xdf\\x9e\\xac\n#    \\xa1\\xb8\\xea\\x0f\\x8ev\\xb5'''\nprint repr(ciphertext)\n\n# The counter mode of operation maintains state, so decryption requires\n# a new instance be created\naes = pyaes.AESModeOfOperationOFB(key, iv = iv)\ndecrypted = aes.decrypt(ciphertext)\n\n# True\nprint decrypted == plaintext\n```\n\n\n#### Electronic Codebook (NOT recommended)\n\n```python\naes = pyaes.AESModeOfOperationECB(key)\nplaintext = \"TextMustBe16Byte\"\nciphertext = aes.encrypt(plaintext)\n\n# 'L6\\x95\\x85\\xe4\\xd9\\xf1\\x8a\\xfb\\xe5\\x94X\\x80|\\x19\\xc3'\nprint repr(ciphertext)\n\n# Since there is no state stored in this mode of operation, it\n# is not necessary to create a new aes object for decryption.\n#aes = pyaes.AESModeOfOperationECB(key)\ndecrypted = aes.decrypt(ciphertext)\n\n# True\nprint decrypted == plaintext\n```\n\n\n### BlockFeeder\n\nSince most of the modes of operations require data in specific block-sized or segment-sized blocks, it can be difficult when working with large arbitrary streams or strings of data.\n\nThe BlockFeeder class is meant to make life easier for you, by buffering bytes across multiple calls and returning bytes as they are available, as well as padding or stripping the output when finished, if necessary.\n\n```python\nimport pyaes\n\n# Any mode of operation can be used; for this example CBC\nkey = \"This_key_for_demo_purposes_only!\"\niv = \"InitializationVe\"\n\nciphertext = ''\n\n# We can encrypt one line at a time, regardles of length\nencrypter = pyaes.Encrypter(pyaes.AESModeOfOperationCBC(key, iv))\nfor line in file('/etc/passwd'):\n    ciphertext += encrypter.feed(line)\n\n# Make a final call to flush any remaining bytes and add paddin\nciphertext += encrypter.feed()\n\n# We can decrypt the cipher text in chunks (here we split it in half)\ndecrypter = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))\ndecrypted = decrypter.feed(ciphertext[:len(ciphertext) / 2])\ndecrypted += decrypter.feed(ciphertext[len(ciphertext) / 2:])\n\n# Again, make a final call to flush any remaining bytes and strip padding\ndecrypted += decrypter.feed()\n\nprint file('/etc/passwd').read() == decrypted\n```\n\n### Stream Feeder\n\nThis is meant to make it even easier to encrypt and decrypt streams and large files.\n\n```python\nimport pyaes\n\n# Any mode of operation can be used; for this example CTR\nkey = \"This_key_for_demo_purposes_only!\"\n\n# Create the mode of operation to encrypt with\nmode = pyaes.AESModeOfOperationCTR(key)\n\n# The input and output files\nfile_in = file('/etc/passwd')\nfile_out = file('/tmp/encrypted.bin', 'wb')\n\n# Encrypt the data as a stream, the file is read in 8kb chunks, be default\npyaes.encrypt_stream(mode, file_in, file_out)\n\n# Close the files\nfile_in.close()\nfile_out.close()\n```\n\nDecrypting is identical, except you would use `pyaes.decrypt_stream`, and the encrypted file would be the `file_in` and target for decryption the `file_out`.\n\n### AES block cipher\n\nGenerally you should use one of the modes of operation above. This may however be useful for experimenting with a custom mode of operation or dealing with encrypted blocks.\n\nThe block cipher requires exactly one block of data to encrypt or decrypt, and each block should be an array with each element an integer representation of a byte.\n\n```python\nimport pyaes\n\n# 16 byte block of plain text\nplaintext = \"Hello World!!!!!\"\nplaintext_bytes = [ ord(c) for c in plaintext ]\n\n# 32 byte key (256 bit)\nkey = \"This_key_for_demo_purposes_only!\"\n\n# Our AES instance\naes = pyaes.AES(key)\n\n# Encrypt!\nciphertext = aes.encrypt(plaintext_bytes)\n\n# [55, 250, 182, 25, 185, 208, 186, 95, 206, 115, 50, 115, 108, 58, 174, 115]\nprint repr(ciphertext)\n\n# Decrypt!\ndecrypted = aes.decrypt(ciphertext)\n\n# True\nprint decrypted == plaintext_bytes\n```\n\nWhat is a key?\n--------------\n\nThis seems to be a point of confusion for many people new to using encryption. You can think of the key as the *\"password\"*. However, these algorithms require the *\"password\"* to be a specific length.\n\nWith AES, there are three possible key lengths, 16-bytes, 24-bytes or 32-bytes. When you create an AES object, the key size is automatically detected, so it is important to pass in a key of the correct length.\n\nOften, you wish to provide a password of arbitrary length, for example, something easy to remember or write down. In these cases, you must come up with a way to transform the password into a key, of a specific length. A **Password-Based Key Derivation Function** (PBKDF) is an algorithm designed for this exact purpose.\n\nHere is an example, using the popular (possibly obsolete?) *crypt* PBKDF:\n\n```\n# See: https://www.dlitz.net/software/python-pbkdf2/\nimport pbkdf2\n\npassword = \"HelloWorld\"\n\n# The crypt PBKDF returns a 48-byte string\nkey = pbkdf2.crypt(password)\n\n# A 16-byte, 24-byte and 32-byte key, respectively\nkey_16 = key[:16]\nkey_24 = key[:24]\nkey_32 = key[:32]\n```\n\nThe [scrypt](https://github.com/ricmoo/pyscrypt) PBKDF is intentionally slow, to make it more difficult to brute-force guess a password:\n\n```\n# See: https://github.com/ricmoo/pyscrypt\nimport pyscrypt\n\npassword = \"HelloWorld\"\n\n# Salt is required, and prevents Rainbow Table attacks\nsalt = \"SeaSalt\"\n\n# N, r, and p are parameters to specify how difficult it should be to\n# generate a key; bigger numbers take longer and more memory\nN = 1024\nr = 1\np = 1\n\n# A 16-byte, 24-byte and 32-byte key, respectively; the scrypt algorithm takes\n# a 6-th parameter, indicating key length\nkey_16 = pyscrypt.hash(password, salt, N, r, p, 16)\nkey_24 = pyscrypt.hash(password, salt, N, r, p, 24)\nkey_32 = pyscrypt.hash(password, salt, N, r, p, 32)\n```\n\nAnother possibility, is to use a hashing function, such as SHA256 to hash the password, but this method may be vulnerable to [Rainbow Attacks](http://en.wikipedia.org/wiki/Rainbow_table), unless you use a [salt](http://en.wikipedia.org/wiki/Salt_(cryptography)).\n\n```python\nimport hashlib\n\npassword = \"HelloWorld\"\n\n# The SHA256 hash algorithm returns a 32-byte string\nhashed = hashlib.sha256(password).digest()\n\n# A 16-byte, 24-byte and 32-byte key, respectively\nkey_16 = hashed[:16]\nkey_24 = hashed[:24]\nkey_32 = hashed\n```\n\n\n\n\nPerformance\n-----------\n\nThere is a test case provided in _/tests/test-aes.py_ which does some basic performance testing (its primary purpose is moreso as a regression test).\n\nBased on that test, in **CPython**, this library is about 30x slower than [PyCrypto](https://www.dlitz.net/software/pycrypto/) for CBC, ECB and OFB; about 80x slower for CFB; and 300x slower for CTR.\n\nBased on that same test, in **Pypy**, this library is about 4x slower than [PyCrypto](https://www.dlitz.net/software/pycrypto/) for CBC, ECB and OFB; about 12x slower for CFB; and 19x slower for CTR.\n\nThe PyCrypto documentation makes reference to the counter call being responsible for the speed problems of the counter (CTR) mode of operation, which is why they use a specially optimized counter. I will investigate this problem further in the future.\n\n\nFAQ\n---\n\n#### Why do this?\n\nThe short answer, *why not?*\n\nThe longer answer, is for my [pyscrypt](https://github.com/ricmoo/pyscrypt) library. I required a pure-Python AES implementation that supported 256-bit keys with the counter (CTR) mode of operation. After searching, I found several implementations, but all were missing CTR or only supported 128 bit keys. After all the work of learning AES inside and out to implement the library, it was only a marginal amount of extra work to library-ify a more general solution. So, *why not?*\n\n#### How do I get a question I have added?\n\nE-mail me at pyaes@ricmoo.com with any questions, suggestions, comments, et cetera.\n\n\n#### Can I give you my money?\n\nUmm... Ok? :-)\n\n_Bitcoin_  - `18UDs4qV1shu2CgTS2tKojhCtM69kpnWg9`\n"
  },
  {
    "path": "src/lib/pyaes/__init__.py",
    "content": "# The MIT License (MIT)\n#\n# Copyright (c) 2014 Richard Moore\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n# This is a pure-Python implementation of the AES algorithm and AES common\n# modes of operation.\n\n# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard\n# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation\n\n\n# Supported key sizes:\n#   128-bit\n#   192-bit\n#   256-bit\n\n\n# Supported modes of operation:\n#   ECB - Electronic Codebook\n#   CBC - Cipher-Block Chaining\n#   CFB - Cipher Feedback\n#   OFB - Output Feedback\n#   CTR - Counter\n\n# See the README.md for API details and general information.\n\n# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:\n# https://www.dlitz.net/software/pycrypto/\n\n\nVERSION = [1, 3, 0]\n\nfrom .aes import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, AESModeOfOperationOFB, AESModesOfOperation, Counter\nfrom .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter\nfrom .blockfeeder import PADDING_NONE, PADDING_DEFAULT\n"
  },
  {
    "path": "src/lib/pyaes/aes.py",
    "content": "# The MIT License (MIT)\n#\n# Copyright (c) 2014 Richard Moore\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n# This is a pure-Python implementation of the AES algorithm and AES common\n# modes of operation.\n\n# See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard\n\n# Honestly, the best description of the modes of operations are the wonderful\n# diagrams on Wikipedia. They explain in moments what my words could never\n# achieve. Hence the inline documentation here is sparer than I'd prefer.\n# See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation\n\n# Also useful, PyCrypto, a crypto library implemented in C with Python bindings:\n# https://www.dlitz.net/software/pycrypto/\n\n\n# Supported key sizes:\n#   128-bit\n#   192-bit\n#   256-bit\n\n\n# Supported modes of operation:\n#   ECB - Electronic Codebook\n#   CBC - Cipher-Block Chaining\n#   CFB - Cipher Feedback\n#   OFB - Output Feedback\n#   CTR - Counter\n\n\n# See the README.md for API details and general information.\n\n\nimport copy\nimport struct\n\n__all__ = [\"AES\", \"AESModeOfOperationCTR\", \"AESModeOfOperationCBC\", \"AESModeOfOperationCFB\",\n           \"AESModeOfOperationECB\", \"AESModeOfOperationOFB\", \"AESModesOfOperation\", \"Counter\"]\n\n\ndef _compact_word(word):\n    return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]\n\ndef _string_to_bytes(text):\n    return list(ord(c) for c in text)\n\ndef _bytes_to_string(binary):\n    return \"\".join(chr(b) for b in binary)\n\ndef _concat_list(a, b):\n    return a + b\n\n\n# Python 3 compatibility\ntry:\n    xrange\nexcept Exception:\n    xrange = range\n\n    # Python 3 supports bytes, which is already an array of integers\n    def _string_to_bytes(text):\n        if isinstance(text, bytes):\n            return text\n        return [ord(c) for c in text]\n\n    # In Python 3, we return bytes\n    def _bytes_to_string(binary):\n        return bytes(binary)\n\n    # Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first\n    def _concat_list(a, b):\n        return a + bytes(b)\n\n\n# Based *largely* on the Rijndael implementation\n# See: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf\nclass AES(object):\n    '''Encapsulates the AES block cipher.\n\n    You generally should not need this. Use the AESModeOfOperation classes\n    below instead.'''\n\n    # Number of rounds by keysize\n    number_of_rounds = {16: 10, 24: 12, 32: 14}\n\n    # Round constant words\n    rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ]\n\n    # S-box and Inverse S-box (S is for Substitution)\n    S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ]\n    Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ] \n\n    # Transformations for encryption\n    T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ]\n    T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ]\n    T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ]\n    T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ]\n\n    # Transformations for decryption\n    T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ]\n    T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ]\n    T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ]\n    T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ]\n\n    # Transformations for decryption key expansion\n    U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ]\n    U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ]\n    U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ]\n    U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ]\n\n    def __init__(self, key):\n\n        if len(key) not in (16, 24, 32):\n            raise ValueError('Invalid key size')\n\n        rounds = self.number_of_rounds[len(key)]\n\n        # Encryption round keys\n        self._Ke = [[0] * 4 for i in xrange(rounds + 1)]\n\n        # Decryption round keys\n        self._Kd = [[0] * 4 for i in xrange(rounds + 1)]\n\n        round_key_count = (rounds + 1) * 4\n        KC = len(key) // 4\n\n        # Convert the key into ints\n        tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ]\n\n        # Copy values into round key arrays\n        for i in xrange(0, KC):\n            self._Ke[i // 4][i % 4] = tk[i]\n            self._Kd[rounds - (i // 4)][i % 4] = tk[i]\n\n        # Key expansion (fips-197 section 5.2)\n        rconpointer = 0\n        t = KC\n        while t < round_key_count:\n\n            tt = tk[KC - 1]\n            tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^\n                      (self.S[(tt >>  8) & 0xFF] << 16) ^\n                      (self.S[ tt        & 0xFF] <<  8) ^\n                       self.S[(tt >> 24) & 0xFF]        ^\n                      (self.rcon[rconpointer] << 24))\n            rconpointer += 1\n\n            if KC != 8:\n                for i in xrange(1, KC):\n                    tk[i] ^= tk[i - 1]\n\n            # Key expansion for 256-bit keys is \"slightly different\" (fips-197)\n            else:\n                for i in xrange(1, KC // 2):\n                    tk[i] ^= tk[i - 1]\n                tt = tk[KC // 2 - 1]\n\n                tk[KC // 2] ^= (self.S[ tt        & 0xFF]        ^\n                               (self.S[(tt >>  8) & 0xFF] <<  8) ^\n                               (self.S[(tt >> 16) & 0xFF] << 16) ^\n                               (self.S[(tt >> 24) & 0xFF] << 24))\n\n                for i in xrange(KC // 2 + 1, KC):\n                    tk[i] ^= tk[i - 1]\n\n            # Copy values into round key arrays\n            j = 0\n            while j < KC and t < round_key_count:\n                self._Ke[t // 4][t % 4] = tk[j]\n                self._Kd[rounds - (t // 4)][t % 4] = tk[j]\n                j += 1\n                t += 1\n\n        # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3)\n        for r in xrange(1, rounds):\n            for j in xrange(0, 4):\n                tt = self._Kd[r][j]\n                self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^\n                                  self.U2[(tt >> 16) & 0xFF] ^\n                                  self.U3[(tt >>  8) & 0xFF] ^\n                                  self.U4[ tt        & 0xFF])\n\n    def encrypt(self, plaintext):\n        'Encrypt a block of plain text using the AES block cipher.'\n\n        if len(plaintext) != 16:\n            raise ValueError('wrong block length')\n\n        rounds = len(self._Ke) - 1\n        (s1, s2, s3) = [1, 2, 3]\n        a = [0, 0, 0, 0]\n\n        # Convert plaintext to (ints ^ key)\n        t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)]\n\n        # Apply round transforms\n        for r in xrange(1, rounds):\n            for i in xrange(0, 4):\n                a[i] = (self.T1[(t[ i          ] >> 24) & 0xFF] ^\n                        self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^\n                        self.T3[(t[(i + s2) % 4] >>  8) & 0xFF] ^\n                        self.T4[ t[(i + s3) % 4]        & 0xFF] ^\n                        self._Ke[r][i])\n            t = copy.copy(a)\n\n        # The last round is special\n        result = [ ]\n        for i in xrange(0, 4):\n            tt = self._Ke[rounds][i]\n            result.append((self.S[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)\n            result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)\n            result.append((self.S[(t[(i + s2) % 4] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)\n            result.append((self.S[ t[(i + s3) % 4]        & 0xFF] ^  tt       ) & 0xFF)\n\n        return result\n\n    def decrypt(self, ciphertext):\n        'Decrypt a block of cipher text using the AES block cipher.'\n\n        if len(ciphertext) != 16:\n            raise ValueError('wrong block length')\n\n        rounds = len(self._Kd) - 1\n        (s1, s2, s3) = [3, 2, 1]\n        a = [0, 0, 0, 0]\n\n        # Convert ciphertext to (ints ^ key)\n        t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in xrange(0, 4)]\n\n        # Apply round transforms\n        for r in xrange(1, rounds):\n            for i in xrange(0, 4):\n                a[i] = (self.T5[(t[ i          ] >> 24) & 0xFF] ^\n                        self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^\n                        self.T7[(t[(i + s2) % 4] >>  8) & 0xFF] ^\n                        self.T8[ t[(i + s3) % 4]        & 0xFF] ^\n                        self._Kd[r][i])\n            t = copy.copy(a)\n\n        # The last round is special\n        result = [ ]\n        for i in xrange(0, 4):\n            tt = self._Kd[rounds][i]\n            result.append((self.Si[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)\n            result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)\n            result.append((self.Si[(t[(i + s2) % 4] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)\n            result.append((self.Si[ t[(i + s3) % 4]        & 0xFF] ^  tt       ) & 0xFF)\n\n        return result\n\n\nclass Counter(object):\n    '''A counter object for the Counter (CTR) mode of operation.\n\n       To create a custom counter, you can usually just override the\n       increment method.'''\n\n    def __init__(self, initial_value = 1):\n\n        # Convert the value into an array of bytes long\n        self._counter = [ ((initial_value >> i) % 256) for i in xrange(128 - 8, -1, -8) ]\n\n    value = property(lambda s: s._counter)\n\n    def increment(self):\n        '''Increment the counter (overflow rolls back to 0).'''\n\n        for i in xrange(len(self._counter) - 1, -1, -1):\n            self._counter[i] += 1\n\n            if self._counter[i] < 256: break\n\n            # Carry the one\n            self._counter[i] = 0\n\n        # Overflow\n        else:\n            self._counter = [ 0 ] * len(self._counter)\n\n\nclass AESBlockModeOfOperation(object):\n    '''Super-class for AES modes of operation that require blocks.'''\n    def __init__(self, key):\n        self._aes = AES(key)\n\n    def decrypt(self, ciphertext):\n        raise Exception('not implemented')\n\n    def encrypt(self, plaintext):\n        raise Exception('not implemented')\n\n\nclass AESStreamModeOfOperation(AESBlockModeOfOperation):\n    '''Super-class for AES modes of operation that are stream-ciphers.'''\n\nclass AESSegmentModeOfOperation(AESStreamModeOfOperation):\n    '''Super-class for AES modes of operation that segment data.'''\n\n    segment_bytes = 16\n\n\n\nclass AESModeOfOperationECB(AESBlockModeOfOperation):\n    '''AES Electronic Codebook Mode of Operation.\n\n       o Block-cipher, so data must be padded to 16 byte boundaries\n\n   Security Notes:\n       o This mode is not recommended\n       o Any two identical blocks produce identical encrypted values,\n         exposing data patterns. (See the image of Tux on wikipedia)\n\n   Also see:\n       o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29\n       o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.1'''\n\n\n    name = \"Electronic Codebook (ECB)\"\n\n    def encrypt(self, plaintext):\n        if len(plaintext) != 16:\n            raise ValueError('plaintext block must be 16 bytes')\n\n        plaintext = _string_to_bytes(plaintext)\n        return _bytes_to_string(self._aes.encrypt(plaintext))\n\n    def decrypt(self, ciphertext):\n        if len(ciphertext) != 16:\n            raise ValueError('ciphertext block must be 16 bytes')\n\n        ciphertext = _string_to_bytes(ciphertext)\n        return _bytes_to_string(self._aes.decrypt(ciphertext))\n\n\n\nclass AESModeOfOperationCBC(AESBlockModeOfOperation):\n    '''AES Cipher-Block Chaining Mode of Operation.\n\n       o The Initialization Vector (IV)\n       o Block-cipher, so data must be padded to 16 byte boundaries\n       o An incorrect initialization vector will only cause the first\n         block to be corrupt; all other blocks will be intact\n       o A corrupt bit in the cipher text will cause a block to be\n         corrupted, and the next block to be inverted, but all other\n         blocks will be intact.\n\n   Security Notes:\n       o This method (and CTR) ARE recommended.\n\n   Also see:\n       o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29\n       o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.2'''\n\n\n    name = \"Cipher-Block Chaining (CBC)\"\n\n    def __init__(self, key, iv = None):\n        if iv is None:\n            self._last_cipherblock = [ 0 ] * 16\n        elif len(iv) != 16:\n            raise ValueError('initialization vector must be 16 bytes')\n        else:\n            self._last_cipherblock = _string_to_bytes(iv)\n\n        AESBlockModeOfOperation.__init__(self, key)\n\n    def encrypt(self, plaintext):\n        if len(plaintext) != 16:\n            raise ValueError('plaintext block must be 16 bytes')\n\n        plaintext = _string_to_bytes(plaintext)\n        precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ]\n        self._last_cipherblock = self._aes.encrypt(precipherblock)\n\n        return _bytes_to_string(self._last_cipherblock)\n\n    def decrypt(self, ciphertext):\n        if len(ciphertext) != 16:\n            raise ValueError('ciphertext block must be 16 bytes')\n\n        cipherblock = _string_to_bytes(ciphertext)\n        plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ]\n        self._last_cipherblock = cipherblock\n\n        return _bytes_to_string(plaintext)\n\n\n\nclass AESModeOfOperationCFB(AESSegmentModeOfOperation):\n    '''AES Cipher Feedback Mode of Operation.\n\n       o A stream-cipher, so input does not need to be padded to blocks,\n         but does need to be padded to segment_size\n\n    Also see:\n       o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_.28CFB.29\n       o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.3'''\n\n\n    name = \"Cipher Feedback (CFB)\"\n\n    def __init__(self, key, iv, segment_size = 1):\n        if segment_size == 0: segment_size = 1\n\n        if iv is None:\n            self._shift_register = [ 0 ] * 16\n        elif len(iv) != 16:\n            raise ValueError('initialization vector must be 16 bytes')\n        else:\n          self._shift_register = _string_to_bytes(iv)\n\n        self._segment_bytes = segment_size\n\n        AESBlockModeOfOperation.__init__(self, key)\n\n    segment_bytes = property(lambda s: s._segment_bytes)\n\n    def encrypt(self, plaintext):\n        if len(plaintext) % self._segment_bytes != 0:\n            raise ValueError('plaintext block must be a multiple of segment_size')\n\n        plaintext = _string_to_bytes(plaintext)\n\n        # Break block into segments\n        encrypted = [ ]\n        for i in xrange(0, len(plaintext), self._segment_bytes):\n            plaintext_segment = plaintext[i: i + self._segment_bytes]\n            xor_segment = self._aes.encrypt(self._shift_register)[:len(plaintext_segment)]\n            cipher_segment = [ (p ^ x) for (p, x) in zip(plaintext_segment, xor_segment) ]\n\n            # Shift the top bits out and the ciphertext in\n            self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)\n\n            encrypted.extend(cipher_segment)\n\n        return _bytes_to_string(encrypted)\n\n    def decrypt(self, ciphertext):\n        if len(ciphertext) % self._segment_bytes != 0:\n            raise ValueError('ciphertext block must be a multiple of segment_size')\n\n        ciphertext = _string_to_bytes(ciphertext)\n\n        # Break block into segments\n        decrypted = [ ]\n        for i in xrange(0, len(ciphertext), self._segment_bytes):\n            cipher_segment = ciphertext[i: i + self._segment_bytes]\n            xor_segment = self._aes.encrypt(self._shift_register)[:len(cipher_segment)]\n            plaintext_segment = [ (p ^ x) for (p, x) in zip(cipher_segment, xor_segment) ]\n\n            # Shift the top bits out and the ciphertext in\n            self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)\n\n            decrypted.extend(plaintext_segment)\n\n        return _bytes_to_string(decrypted)\n\n\n\nclass AESModeOfOperationOFB(AESStreamModeOfOperation):\n    '''AES Output Feedback Mode of Operation.\n\n       o A stream-cipher, so input does not need to be padded to blocks,\n         allowing arbitrary length data.\n       o A bit twiddled in the cipher text, twiddles the same bit in the\n         same bit in the plain text, which can be useful for error\n         correction techniques.\n\n    Also see:\n       o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_.28OFB.29\n       o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.4'''\n\n\n    name = \"Output Feedback (OFB)\"\n\n    def __init__(self, key, iv = None):\n        if iv is None:\n            self._last_precipherblock = [ 0 ] * 16\n        elif len(iv) != 16:\n            raise ValueError('initialization vector must be 16 bytes')\n        else:\n          self._last_precipherblock = _string_to_bytes(iv)\n\n        self._remaining_block = [ ]\n\n        AESBlockModeOfOperation.__init__(self, key)\n\n    def encrypt(self, plaintext):\n        encrypted = [ ]\n        for p in _string_to_bytes(plaintext):\n            if len(self._remaining_block) == 0:\n                self._remaining_block = self._aes.encrypt(self._last_precipherblock)\n                self._last_precipherblock = [ ]\n            precipherbyte = self._remaining_block.pop(0)\n            self._last_precipherblock.append(precipherbyte)\n            cipherbyte = p ^ precipherbyte\n            encrypted.append(cipherbyte)\n\n        return _bytes_to_string(encrypted)\n\n    def decrypt(self, ciphertext):\n        # AES-OFB is symetric\n        return self.encrypt(ciphertext)\n\n\n\nclass AESModeOfOperationCTR(AESStreamModeOfOperation):\n    '''AES Counter Mode of Operation.\n\n       o A stream-cipher, so input does not need to be padded to blocks,\n         allowing arbitrary length data.\n       o The counter must be the same size as the key size (ie. len(key))\n       o Each block independant of the other, so a corrupt byte will not\n         damage future blocks.\n       o Each block has a uniue counter value associated with it, which\n         contributes to the encrypted value, so no data patterns are\n         leaked.\n       o Also known as: Counter Mode (CM), Integer Counter Mode (ICM) and\n         Segmented Integer Counter (SIC\n\n   Security Notes:\n       o This method (and CBC) ARE recommended.\n       o Each message block is associated with a counter value which must be\n         unique for ALL messages with the same key. Otherwise security may be\n         compromised.\n\n    Also see:\n\n       o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29\n       o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.5\n         and Appendix B for managing the initial counter'''\n\n\n    name = \"Counter (CTR)\"\n\n    def __init__(self, key, counter = None):\n        AESBlockModeOfOperation.__init__(self, key)\n\n        if counter is None:\n            counter = Counter()\n\n        self._counter = counter\n        self._remaining_counter = [ ]\n\n    def encrypt(self, plaintext):\n        while len(self._remaining_counter) < len(plaintext):\n            self._remaining_counter += self._aes.encrypt(self._counter.value)\n            self._counter.increment()\n\n        plaintext = _string_to_bytes(plaintext)\n\n        encrypted = [ (p ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ]\n        self._remaining_counter = self._remaining_counter[len(encrypted):]\n\n        return _bytes_to_string(encrypted)\n\n    def decrypt(self, crypttext):\n        # AES-CTR is symetric\n        return self.encrypt(crypttext)\n\n\n# Simple lookup table for each mode\nAESModesOfOperation = dict(\n    ctr = AESModeOfOperationCTR,\n    cbc = AESModeOfOperationCBC,\n    cfb = AESModeOfOperationCFB,\n    ecb = AESModeOfOperationECB,\n    ofb = AESModeOfOperationOFB,\n)\n"
  },
  {
    "path": "src/lib/pyaes/blockfeeder.py",
    "content": "# The MIT License (MIT)\n#\n# Copyright (c) 2014 Richard Moore\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n\nfrom .aes import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation\nfrom .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable\n\n\n# First we inject three functions to each of the modes of operations\n#\n#    _can_consume(size)\n#       - Given a size, determine how many bytes could be consumed in\n#         a single call to either the decrypt or encrypt method\n#\n#    _final_encrypt(data, padding = PADDING_DEFAULT)\n#       - call and return encrypt on this (last) chunk of data,\n#         padding as necessary; this will always be at least 16\n#         bytes unless the total incoming input was less than 16\n#         bytes\n#\n#    _final_decrypt(data, padding = PADDING_DEFAULT)\n#       - same as _final_encrypt except for decrypt, for\n#         stripping off padding\n#\n\nPADDING_NONE       = 'none'\nPADDING_DEFAULT    = 'default'\n\n# @TODO: Ciphertext stealing and explicit PKCS#7\n# PADDING_CIPHERTEXT_STEALING\n# PADDING_PKCS7\n\n# ECB and CBC are block-only ciphers\n\ndef _block_can_consume(self, size):\n    if size >= 16: return 16\n    return 0\n\n# After padding, we may have more than one block\ndef _block_final_encrypt(self, data, padding = PADDING_DEFAULT):\n    if padding == PADDING_DEFAULT:\n        data = append_PKCS7_padding(data)\n\n    elif padding == PADDING_NONE:\n        if len(data) != 16:\n            raise Exception('invalid data length for final block')\n    else:\n        raise Exception('invalid padding option')\n\n    if len(data) == 32:\n        return self.encrypt(data[:16]) + self.encrypt(data[16:])\n\n    return self.encrypt(data)\n\n\ndef _block_final_decrypt(self, data, padding = PADDING_DEFAULT):\n    if padding == PADDING_DEFAULT:\n        return strip_PKCS7_padding(self.decrypt(data))\n\n    if padding == PADDING_NONE:\n        if len(data) != 16:\n            raise Exception('invalid data length for final block')\n        return self.decrypt(data)\n\n    raise Exception('invalid padding option')\n\nAESBlockModeOfOperation._can_consume = _block_can_consume\nAESBlockModeOfOperation._final_encrypt = _block_final_encrypt\nAESBlockModeOfOperation._final_decrypt = _block_final_decrypt\n\n\n\n# CFB is a segment cipher\n\ndef _segment_can_consume(self, size):\n    return self.segment_bytes * int(size // self.segment_bytes)\n\n# CFB can handle a non-segment-sized block at the end using the remaining cipherblock\ndef _segment_final_encrypt(self, data, padding = PADDING_DEFAULT):\n    if padding != PADDING_DEFAULT:\n        raise Exception('invalid padding option')\n\n    faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))\n    padded = data + to_bufferable(faux_padding)\n    return self.encrypt(padded)[:len(data)]\n\n# CFB can handle a non-segment-sized block at the end using the remaining cipherblock\ndef _segment_final_decrypt(self, data, padding = PADDING_DEFAULT):\n    if padding != PADDING_DEFAULT:\n        raise Exception('invalid padding option')\n\n    faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))\n    padded = data + to_bufferable(faux_padding)\n    return self.decrypt(padded)[:len(data)]\n\nAESSegmentModeOfOperation._can_consume = _segment_can_consume\nAESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt\nAESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt\n\n\n\n# OFB and CTR are stream ciphers\n\ndef _stream_can_consume(self, size):\n    return size\n\ndef _stream_final_encrypt(self, data, padding = PADDING_DEFAULT):\n    if padding not in [PADDING_NONE, PADDING_DEFAULT]:\n        raise Exception('invalid padding option')\n\n    return self.encrypt(data)\n\ndef _stream_final_decrypt(self, data, padding = PADDING_DEFAULT):\n    if padding not in [PADDING_NONE, PADDING_DEFAULT]:\n        raise Exception('invalid padding option')\n\n    return self.decrypt(data)\n\nAESStreamModeOfOperation._can_consume = _stream_can_consume\nAESStreamModeOfOperation._final_encrypt = _stream_final_encrypt\nAESStreamModeOfOperation._final_decrypt = _stream_final_decrypt\n\n\n\nclass BlockFeeder(object):\n    '''The super-class for objects to handle chunking a stream of bytes\n       into the appropriate block size for the underlying mode of operation\n       and applying (or stripping) padding, as necessary.'''\n\n    def __init__(self, mode, feed, final, padding = PADDING_DEFAULT):\n        self._mode = mode\n        self._feed = feed\n        self._final = final\n        self._buffer = to_bufferable(\"\")\n        self._padding = padding\n\n    def feed(self, data = None):\n        '''Provide bytes to encrypt (or decrypt), returning any bytes\n           possible from this or any previous calls to feed.\n\n           Call with None or an empty string to flush the mode of\n           operation and return any final bytes; no further calls to\n           feed may be made.'''\n\n        if self._buffer is None:\n            raise ValueError('already finished feeder')\n\n        # Finalize; process the spare bytes we were keeping\n        if data is None:\n            result = self._final(self._buffer, self._padding)\n            self._buffer = None\n            return result\n\n        self._buffer += to_bufferable(data)\n\n        # We keep 16 bytes around so we can determine padding\n        result = to_bufferable('')\n        while len(self._buffer) > 16:\n            can_consume = self._mode._can_consume(len(self._buffer) - 16)\n            if can_consume == 0: break\n            result += self._feed(self._buffer[:can_consume])\n            self._buffer = self._buffer[can_consume:]\n\n        return result\n\n\nclass Encrypter(BlockFeeder):\n    'Accepts bytes of plaintext and returns encrypted ciphertext.'\n\n    def __init__(self, mode, padding = PADDING_DEFAULT):\n        BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding)\n\n\nclass Decrypter(BlockFeeder):\n    'Accepts bytes of ciphertext and returns decrypted plaintext.'\n\n    def __init__(self, mode, padding = PADDING_DEFAULT):\n        BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding)\n\n\n# 8kb blocks\nBLOCK_SIZE = (1 << 13)\n\ndef _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE):\n    'Uses feeder to read and convert from in_stream and write to out_stream.'\n\n    while True:\n        chunk = in_stream.read(block_size)\n        if not chunk:\n            break\n        converted = feeder.feed(chunk)\n        out_stream.write(converted)\n    converted = feeder.feed()\n    out_stream.write(converted)\n\n\ndef encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):\n    'Encrypts a stream of bytes from in_stream to out_stream using mode.'\n\n    encrypter = Encrypter(mode, padding = padding)\n    _feed_stream(encrypter, in_stream, out_stream, block_size)\n\n\ndef decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):\n    'Decrypts a stream of bytes from in_stream to out_stream using mode.'\n\n    decrypter = Decrypter(mode, padding = padding)\n    _feed_stream(decrypter, in_stream, out_stream, block_size)\n"
  },
  {
    "path": "src/lib/pyaes/util.py",
    "content": "# The MIT License (MIT)\n#\n# Copyright (c) 2014 Richard Moore\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n# Why to_bufferable?\n# Python 3 is very different from Python 2.x when it comes to strings of text\n# and strings of bytes; in Python 3, strings of bytes do not exist, instead to\n# represent arbitrary binary data, we must use the \"bytes\" object. This method\n# ensures the object behaves as we need it to.\n\ndef to_bufferable(binary):\n    return binary\n\ndef _get_byte(c):\n    return ord(c)\n\ntry:\n    xrange\nexcept:\n\n    def to_bufferable(binary):\n        if isinstance(binary, bytes):\n            return binary\n        return bytes(ord(b) for b in binary)\n\n    def _get_byte(c):\n        return c\n\ndef append_PKCS7_padding(data):\n    pad = 16 - (len(data) % 16)\n    return data + to_bufferable(chr(pad) * pad)\n\ndef strip_PKCS7_padding(data):\n    if len(data) % 16 != 0:\n        raise ValueError(\"invalid length\")\n\n    pad = _get_byte(data[-1])\n\n    if pad > 16:\n        raise ValueError(\"invalid padding byte\")\n\n    return data[:-pad]\n"
  },
  {
    "path": "src/lib/sslcrypto/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Ivan Machugovskiy\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\nAdditionally, the following licenses must be preserved:\n\n- ripemd implementation is licensed under BSD-3 by Markus Friedl, see `_ripemd.py`;\n- jacobian curve implementation is dual-licensed under MIT or public domain license, see `_jacobian.py`.\n"
  },
  {
    "path": "src/lib/sslcrypto/__init__.py",
    "content": "__all__ = [\"aes\", \"ecc\", \"rsa\"]\n\ntry:\n    from .openssl import aes, ecc, rsa\nexcept OSError:\n    from .fallback import aes, ecc, rsa\n"
  },
  {
    "path": "src/lib/sslcrypto/_aes.py",
    "content": "# pylint: disable=import-outside-toplevel\n\nclass AES:\n    def __init__(self, backend, fallback=None):\n        self._backend = backend\n        self._fallback = fallback\n\n\n    def get_algo_key_length(self, algo):\n        if algo.count(\"-\") != 2:\n            raise ValueError(\"Invalid algorithm name\")\n        try:\n            return int(algo.split(\"-\")[1]) // 8\n        except ValueError:\n            raise ValueError(\"Invalid algorithm name\") from None\n\n\n    def new_key(self, algo=\"aes-256-cbc\"):\n        if not self._backend.is_algo_supported(algo):\n            if self._fallback is None:\n                raise ValueError(\"This algorithm is not supported\")\n            return self._fallback.new_key(algo)\n        return self._backend.random(self.get_algo_key_length(algo))\n\n\n    def encrypt(self, data, key, algo=\"aes-256-cbc\"):\n        if not self._backend.is_algo_supported(algo):\n            if self._fallback is None:\n                raise ValueError(\"This algorithm is not supported\")\n            return self._fallback.encrypt(data, key, algo)\n\n        key_length = self.get_algo_key_length(algo)\n        if len(key) != key_length:\n            raise ValueError(\"Expected key to be {} bytes, got {} bytes\".format(key_length, len(key)))\n\n        return self._backend.encrypt(data, key, algo)\n\n\n    def decrypt(self, ciphertext, iv, key, algo=\"aes-256-cbc\"):\n        if not self._backend.is_algo_supported(algo):\n            if self._fallback is None:\n                raise ValueError(\"This algorithm is not supported\")\n            return self._fallback.decrypt(ciphertext, iv, key, algo)\n\n        key_length = self.get_algo_key_length(algo)\n        if len(key) != key_length:\n            raise ValueError(\"Expected key to be {} bytes, got {} bytes\".format(key_length, len(key)))\n\n        return self._backend.decrypt(ciphertext, iv, key, algo)\n\n\n    def get_backend(self):\n        return self._backend.get_backend()\n"
  },
  {
    "path": "src/lib/sslcrypto/_ecc.py",
    "content": "import hashlib\nimport struct\nimport hmac\nimport base58\n\n\ntry:\n    hashlib.new(\"ripemd160\")\nexcept ValueError:\n    # No native implementation\n    from . import _ripemd\n    def ripemd160(*args):\n        return _ripemd.new(*args)\nelse:\n    # Use OpenSSL\n    def ripemd160(*args):\n        return hashlib.new(\"ripemd160\", *args)\n\n\nclass ECC:\n    # pylint: disable=line-too-long\n    # name: (nid, p, n, a, b, (Gx, Gy)),\n    CURVES = {\n        \"secp112r1\": (\n            704,\n            0xDB7C2ABF62E35E668076BEAD208B,\n            0xDB7C2ABF62E35E7628DFAC6561C5,\n            0xDB7C2ABF62E35E668076BEAD2088,\n            0x659EF8BA043916EEDE8911702B22,\n            (\n                0x09487239995A5EE76B55F9C2F098,\n                0xA89CE5AF8724C0A23E0E0FF77500\n            )\n        ),\n        \"secp112r2\": (\n            705,\n            0xDB7C2ABF62E35E668076BEAD208B,\n            0x36DF0AAFD8B8D7597CA10520D04B,\n            0x6127C24C05F38A0AAAF65C0EF02C,\n            0x51DEF1815DB5ED74FCC34C85D709,\n            (\n                0x4BA30AB5E892B4E1649DD0928643,\n                0xADCD46F5882E3747DEF36E956E97\n            )\n        ),\n        \"secp128r1\": (\n            706,\n            0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF,\n            0xFFFFFFFE0000000075A30D1B9038A115,\n            0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC,\n            0xE87579C11079F43DD824993C2CEE5ED3,\n            (\n                0x161FF7528B899B2D0C28607CA52C5B86,\n                0xCF5AC8395BAFEB13C02DA292DDED7A83\n            )\n        ),\n        \"secp128r2\": (\n            707,\n            0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF,\n            0x3FFFFFFF7FFFFFFFBE0024720613B5A3,\n            0xD6031998D1B3BBFEBF59CC9BBFF9AEE1,\n            0x5EEEFCA380D02919DC2C6558BB6D8A5D,\n            (\n                0x7B6AA5D85E572983E6FB32A7CDEBC140,\n                0x27B6916A894D3AEE7106FE805FC34B44\n            )\n        ),\n        \"secp160k1\": (\n            708,\n            0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73,\n            0x0100000000000000000001B8FA16DFAB9ACA16B6B3,\n            0,\n            7,\n            (\n                0x3B4C382CE37AA192A4019E763036F4F5DD4D7EBB,\n                0x938CF935318FDCED6BC28286531733C3F03C4FEE\n            )\n        ),\n        \"secp160r1\": (\n            709,\n            0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF,\n            0x0100000000000000000001F4C8F927AED3CA752257,\n            0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC,\n            0x001C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45,\n            (\n                0x4A96B5688EF573284664698968C38BB913CBFC82,\n                0x23A628553168947D59DCC912042351377AC5FB32\n            )\n        ),\n        \"secp160r2\": (\n            710,\n            0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73,\n            0x0100000000000000000000351EE786A818F3A1A16B,\n            0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70,\n            0x00B4E134D3FB59EB8BAB57274904664D5AF50388BA,\n            (\n                0x52DCB034293A117E1F4FF11B30F7199D3144CE6D,\n                0xFEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E\n            )\n        ),\n        \"secp192k1\": (\n            711,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37,\n            0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D,\n            0,\n            3,\n            (\n                0xDB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D,\n                0x9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D\n            )\n        ),\n        \"prime192v1\": (\n            409,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF,\n            0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC,\n            0x64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1,\n            (\n                0x188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012,\n                0x07192B95FFC8DA78631011ED6B24CDD573F977A11E794811\n            )\n        ),\n        \"secp224k1\": (\n            712,\n            0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D,\n            0x010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7,\n            0,\n            5,\n            (\n                0xA1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C,\n                0x7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5\n            )\n        ),\n        \"secp224r1\": (\n            713,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE,\n            0xB4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4,\n            (\n                0xB70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21,\n                0xBD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34\n            )\n        ),\n        \"secp256k1\": (\n            714,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,\n            0,\n            7,\n            (\n                0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,\n                0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8\n            )\n        ),\n        \"prime256v1\": (\n            715,\n            0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF,\n            0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551,\n            0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC,\n            0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B,\n            (\n                0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,\n                0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5\n            )\n        ),\n        \"secp384r1\": (\n            716,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973,\n            0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC,\n            0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF,\n            (\n                0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7,\n                0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F\n            )\n        ),\n        \"secp521r1\": (\n            717,\n            0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,\n            0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409,\n            0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC,\n            0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00,\n            (\n                0x00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66,\n                0x011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650\n            )\n        )\n    }\n    # pylint: enable=line-too-long\n\n    def __init__(self, backend, aes):\n        self._backend = backend\n        self._aes = aes\n\n\n    def get_curve(self, name):\n        if name not in self.CURVES:\n            raise ValueError(\"Unknown curve {}\".format(name))\n        nid, p, n, a, b, g = self.CURVES[name]\n        return EllipticCurve(self._backend(p, n, a, b, g), self._aes, nid)\n\n\n    def get_backend(self):\n        return self._backend.get_backend()\n\n\nclass EllipticCurve:\n    def __init__(self, backend, aes, nid):\n        self._backend = backend\n        self._aes = aes\n        self.nid = nid\n\n\n    def _encode_public_key(self, x, y, is_compressed=True, raw=True):\n        if raw:\n            if is_compressed:\n                return bytes([0x02 + (y[-1] % 2)]) + x\n            else:\n                return bytes([0x04]) + x + y\n        else:\n            return struct.pack(\"!HH\", self.nid, len(x)) + x + struct.pack(\"!H\", len(y)) + y\n\n\n    def _decode_public_key(self, public_key, partial=False):\n        if not public_key:\n            raise ValueError(\"No public key\")\n\n        if public_key[0] == 0x04:\n            # Uncompressed\n            expected_length = 1 + 2 * self._backend.public_key_length\n            if partial:\n                if len(public_key) < expected_length:\n                    raise ValueError(\"Invalid uncompressed public key length\")\n            else:\n                if len(public_key) != expected_length:\n                    raise ValueError(\"Invalid uncompressed public key length\")\n            x = public_key[1:1 + self._backend.public_key_length]\n            y = public_key[1 + self._backend.public_key_length:expected_length]\n            if partial:\n                return (x, y), expected_length\n            else:\n                return x, y\n        elif public_key[0] in (0x02, 0x03):\n            # Compressed\n            expected_length = 1 + self._backend.public_key_length\n            if partial:\n                if len(public_key) < expected_length:\n                    raise ValueError(\"Invalid compressed public key length\")\n            else:\n                if len(public_key) != expected_length:\n                    raise ValueError(\"Invalid compressed public key length\")\n\n            x, y = self._backend.decompress_point(public_key[:expected_length])\n            # Sanity check\n            if x != public_key[1:expected_length]:\n                raise ValueError(\"Incorrect compressed public key\")\n            if partial:\n                return (x, y), expected_length\n            else:\n                return x, y\n        else:\n            raise ValueError(\"Invalid public key prefix\")\n\n\n    def _decode_public_key_openssl(self, public_key, partial=False):\n        if not public_key:\n            raise ValueError(\"No public key\")\n\n        i = 0\n\n        nid, = struct.unpack(\"!H\", public_key[i:i + 2])\n        i += 2\n        if nid != self.nid:\n            raise ValueError(\"Wrong curve\")\n\n        xlen, = struct.unpack(\"!H\", public_key[i:i + 2])\n        i += 2\n        if len(public_key) - i < xlen:\n            raise ValueError(\"Too short public key\")\n        x = public_key[i:i + xlen]\n        i += xlen\n\n        ylen, = struct.unpack(\"!H\", public_key[i:i + 2])\n        i += 2\n        if len(public_key) - i < ylen:\n            raise ValueError(\"Too short public key\")\n        y = public_key[i:i + ylen]\n        i += ylen\n\n        if partial:\n            return (x, y), i\n        else:\n            if i < len(public_key):\n                raise ValueError(\"Too long public key\")\n            return x, y\n\n\n    def new_private_key(self, is_compressed=False):\n        return self._backend.new_private_key() + (b\"\\x01\" if is_compressed else b\"\")\n\n\n    def private_to_public(self, private_key):\n        if len(private_key) == self._backend.public_key_length:\n            is_compressed = False\n        elif len(private_key) == self._backend.public_key_length + 1 and private_key[-1] == 1:\n            is_compressed = True\n            private_key = private_key[:-1]\n        else:\n            raise ValueError(\"Private key has invalid length\")\n        x, y = self._backend.private_to_public(private_key)\n        return self._encode_public_key(x, y, is_compressed=is_compressed)\n\n\n    def private_to_wif(self, private_key):\n        return base58.b58encode_check(b\"\\x80\" + private_key)\n\n\n    def wif_to_private(self, wif):\n        dec = base58.b58decode_check(wif)\n        if dec[0] != 0x80:\n            raise ValueError(\"Invalid network (expected mainnet)\")\n        return dec[1:]\n\n\n    def public_to_address(self, public_key):\n        h = hashlib.sha256(public_key).digest()\n        hash160 = ripemd160(h).digest()\n        return base58.b58encode_check(b\"\\x00\" + hash160)\n\n\n    def private_to_address(self, private_key):\n        # Kinda useless but left for quick migration from pybitcointools\n        return self.public_to_address(self.private_to_public(private_key))\n\n\n    def derive(self, private_key, public_key):\n        if len(private_key) == self._backend.public_key_length + 1 and private_key[-1] == 1:\n            private_key = private_key[:-1]\n        if len(private_key) != self._backend.public_key_length:\n            raise ValueError(\"Private key has invalid length\")\n        if not isinstance(public_key, tuple):\n            public_key = self._decode_public_key(public_key)\n        return self._backend.ecdh(private_key, public_key)\n\n\n    def _digest(self, data, hash):\n        if hash is None:\n            return data\n        elif callable(hash):\n            return hash(data)\n        elif hash == \"sha1\":\n            return hashlib.sha1(data).digest()\n        elif hash == \"sha256\":\n            return hashlib.sha256(data).digest()\n        elif hash == \"sha512\":\n            return hashlib.sha512(data).digest()\n        else:\n            raise ValueError(\"Unknown hash/derivation method\")\n\n\n    # High-level functions\n    def encrypt(self, data, public_key, algo=\"aes-256-cbc\", derivation=\"sha256\", mac=\"hmac-sha256\", return_aes_key=False):\n        # Generate ephemeral private key\n        private_key = self.new_private_key()\n\n        # Derive key\n        ecdh = self.derive(private_key, public_key)\n        key = self._digest(ecdh, derivation)\n        k_enc_len = self._aes.get_algo_key_length(algo)\n        if len(key) < k_enc_len:\n            raise ValueError(\"Too short digest\")\n        k_enc, k_mac = key[:k_enc_len], key[k_enc_len:]\n\n        # Encrypt\n        ciphertext, iv = self._aes.encrypt(data, k_enc, algo=algo)\n        ephem_public_key = self.private_to_public(private_key)\n        ephem_public_key = self._decode_public_key(ephem_public_key)\n        ephem_public_key = self._encode_public_key(*ephem_public_key, raw=False)\n        ciphertext = iv + ephem_public_key + ciphertext\n\n        # Add MAC tag\n        if callable(mac):\n            tag = mac(k_mac, ciphertext)\n        elif mac == \"hmac-sha256\":\n            h = hmac.new(k_mac, digestmod=\"sha256\")\n            h.update(ciphertext)\n            tag = h.digest()\n        elif mac == \"hmac-sha512\":\n            h = hmac.new(k_mac, digestmod=\"sha512\")\n            h.update(ciphertext)\n            tag = h.digest()\n        elif mac is None:\n            tag = b\"\"\n        else:\n            raise ValueError(\"Unsupported MAC\")\n\n        if return_aes_key:\n            return ciphertext + tag, k_enc\n        else:\n            return ciphertext + tag\n\n\n    def decrypt(self, ciphertext, private_key, algo=\"aes-256-cbc\", derivation=\"sha256\", mac=\"hmac-sha256\"):\n        # Get MAC tag\n        if callable(mac):\n            tag_length = mac.digest_size\n        elif mac == \"hmac-sha256\":\n            tag_length = hmac.new(b\"\", digestmod=\"sha256\").digest_size\n        elif mac == \"hmac-sha512\":\n            tag_length = hmac.new(b\"\", digestmod=\"sha512\").digest_size\n        elif mac is None:\n            tag_length = 0\n        else:\n            raise ValueError(\"Unsupported MAC\")\n\n        if len(ciphertext) < tag_length:\n            raise ValueError(\"Ciphertext is too small to contain MAC tag\")\n        if tag_length == 0:\n            tag = b\"\"\n        else:\n            ciphertext, tag = ciphertext[:-tag_length], ciphertext[-tag_length:]\n\n        orig_ciphertext = ciphertext\n\n        if len(ciphertext) < 16:\n            raise ValueError(\"Ciphertext is too small to contain IV\")\n        iv, ciphertext = ciphertext[:16], ciphertext[16:]\n\n        public_key, pos = self._decode_public_key_openssl(ciphertext, partial=True)\n        ciphertext = ciphertext[pos:]\n\n        # Derive key\n        ecdh = self.derive(private_key, public_key)\n        key = self._digest(ecdh, derivation)\n        k_enc_len = self._aes.get_algo_key_length(algo)\n        if len(key) < k_enc_len:\n            raise ValueError(\"Too short digest\")\n        k_enc, k_mac = key[:k_enc_len], key[k_enc_len:]\n\n        # Verify MAC tag\n        if callable(mac):\n            expected_tag = mac(k_mac, orig_ciphertext)\n        elif mac == \"hmac-sha256\":\n            h = hmac.new(k_mac, digestmod=\"sha256\")\n            h.update(orig_ciphertext)\n            expected_tag = h.digest()\n        elif mac == \"hmac-sha512\":\n            h = hmac.new(k_mac, digestmod=\"sha512\")\n            h.update(orig_ciphertext)\n            expected_tag = h.digest()\n        elif mac is None:\n            expected_tag = b\"\"\n\n        if not hmac.compare_digest(tag, expected_tag):\n            raise ValueError(\"Invalid MAC tag\")\n\n        return self._aes.decrypt(ciphertext, iv, k_enc, algo=algo)\n\n\n    def sign(self, data, private_key, hash=\"sha256\", recoverable=False, entropy=None):\n        if len(private_key) == self._backend.public_key_length:\n            is_compressed = False\n        elif len(private_key) == self._backend.public_key_length + 1 and private_key[-1] == 1:\n            is_compressed = True\n            private_key = private_key[:-1]\n        else:\n            raise ValueError(\"Private key has invalid length\")\n\n        data = self._digest(data, hash)\n        if not entropy:\n            v = b\"\\x01\" * len(data)\n            k = b\"\\x00\" * len(data)\n            k = hmac.new(k, v + b\"\\x00\" + private_key + data, \"sha256\").digest()\n            v = hmac.new(k, v, \"sha256\").digest()\n            k = hmac.new(k, v + b\"\\x01\" + private_key + data, \"sha256\").digest()\n            v = hmac.new(k, v, \"sha256\").digest()\n            entropy = hmac.new(k, v, \"sha256\").digest()\n        return self._backend.sign(data, private_key, recoverable, is_compressed, entropy=entropy)\n\n\n    def recover(self, signature, data, hash=\"sha256\"):\n        # Sanity check: is this signature recoverable?\n        if len(signature) != 1 + 2 * self._backend.public_key_length:\n            raise ValueError(\"Cannot recover an unrecoverable signature\")\n        x, y = self._backend.recover(signature, self._digest(data, hash))\n        is_compressed = signature[0] >= 31\n        return self._encode_public_key(x, y, is_compressed=is_compressed)\n\n\n    def verify(self, signature, data, public_key, hash=\"sha256\"):\n        if len(signature) == 1 + 2 * self._backend.public_key_length:\n            # Recoverable signature\n            signature = signature[1:]\n        if len(signature) != 2 * self._backend.public_key_length:\n            raise ValueError(\"Invalid signature format\")\n        if not isinstance(public_key, tuple):\n            public_key = self._decode_public_key(public_key)\n        return self._backend.verify(signature, self._digest(data, hash), public_key)\n\n\n    def derive_child(self, seed, child):\n        # Based on BIP32\n        if not 0 <= child < 2 ** 31:\n            raise ValueError(\"Invalid child index\")\n        return self._backend.derive_child(seed, child)\n"
  },
  {
    "path": "src/lib/sslcrypto/_ripemd.py",
    "content": "# Copyright (c) 2001 Markus Friedl.  All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions\n# are met:\n# 1. Redistributions of source code must retain the above copyright\n#    notice, this list of conditions and the following disclaimer.\n# 2. Redistributions in binary form must reproduce the above copyright\n#    notice, this list of conditions and the following disclaimer in the\n#    documentation and/or other materials provided with the distribution.\n#\n# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n# pylint: skip-file\n\nimport sys\n\ndigest_size = 20\ndigestsize = 20\n\nclass RIPEMD160:\n    \"\"\"\n    Return a new RIPEMD160 object. An optional string argument\n    may be provided; if present, this string will be automatically\n    hashed.\n    \"\"\"\n    \n    def __init__(self, arg=None):\n        self.ctx = RMDContext()\n        if arg:\n            self.update(arg)\n        self.dig = None\n        \n    def update(self, arg):\n        RMD160Update(self.ctx, arg, len(arg))\n        self.dig = None\n        \n    def digest(self):\n        if self.dig:\n            return self.dig\n        ctx = self.ctx.copy()\n        self.dig = RMD160Final(self.ctx)\n        self.ctx = ctx\n        return self.dig\n    \n    def hexdigest(self):\n        dig = self.digest()\n        hex_digest = \"\"\n        for d in dig:\n            hex_digest += \"%02x\" % d\n        return hex_digest\n    \n    def copy(self):\n        import copy\n        return copy.deepcopy(self)\n\n\n\ndef new(arg=None):\n    \"\"\"\n    Return a new RIPEMD160 object. An optional string argument\n    may be provided; if present, this string will be automatically\n    hashed.\n    \"\"\"\n    return RIPEMD160(arg)\n\n\n\n#\n# Private.\n#\n\nclass RMDContext:\n    def __init__(self):\n        self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE,\n                      0x10325476, 0xC3D2E1F0] # uint32\n        self.count = 0 # uint64\n        self.buffer = [0] * 64 # uchar\n    def copy(self):\n        ctx = RMDContext()\n        ctx.state = self.state[:]\n        ctx.count = self.count\n        ctx.buffer = self.buffer[:]\n        return ctx\n\nK0 = 0x00000000\nK1 = 0x5A827999\nK2 = 0x6ED9EBA1\nK3 = 0x8F1BBCDC\nK4 = 0xA953FD4E\n\nKK0 = 0x50A28BE6\nKK1 = 0x5C4DD124\nKK2 = 0x6D703EF3\nKK3 = 0x7A6D76E9\nKK4 = 0x00000000\n\ndef ROL(n, x):\n    return ((x << n) & 0xffffffff) | (x >> (32 - n))\n\ndef F0(x, y, z):\n    return x ^ y ^ z\n\ndef F1(x, y, z):\n    return (x & y) | (((~x) % 0x100000000) & z)\n\ndef F2(x, y, z):\n    return (x | ((~y) % 0x100000000)) ^ z\n\ndef F3(x, y, z):\n    return (x & z) | (((~z) % 0x100000000) & y)\n\ndef F4(x, y, z):\n    return x ^ (y | ((~z) % 0x100000000))\n\ndef R(a, b, c, d, e, Fj, Kj, sj, rj, X):\n    a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e\n    c = ROL(10, c)\n    return a % 0x100000000, c\n\nPADDING = [0x80] + [0] * 63\n\nimport sys\nimport struct\n\ndef RMD160Transform(state, block): # uint32 state[5], uchar block[64]\n    x = [0] * 16\n    if sys.byteorder == \"little\":\n        x = struct.unpack(\"<16L\", bytes(block[0:64]))\n    else:\n        raise ValueError(\"Big-endian platforms are not supported\")\n    a = state[0]\n    b = state[1]\n    c = state[2]\n    d = state[3]\n    e = state[4]\n\n    # Round 1\n    a, c = R(a, b, c, d, e, F0, K0, 11,  0, x)\n    e, b = R(e, a, b, c, d, F0, K0, 14,  1, x)\n    d, a = R(d, e, a, b, c, F0, K0, 15,  2, x)\n    c, e = R(c, d, e, a, b, F0, K0, 12,  3, x)\n    b, d = R(b, c, d, e, a, F0, K0,  5,  4, x)\n    a, c = R(a, b, c, d, e, F0, K0,  8,  5, x)\n    e, b = R(e, a, b, c, d, F0, K0,  7,  6, x)\n    d, a = R(d, e, a, b, c, F0, K0,  9,  7, x)\n    c, e = R(c, d, e, a, b, F0, K0, 11,  8, x)\n    b, d = R(b, c, d, e, a, F0, K0, 13,  9, x)\n    a, c = R(a, b, c, d, e, F0, K0, 14, 10, x)\n    e, b = R(e, a, b, c, d, F0, K0, 15, 11, x)\n    d, a = R(d, e, a, b, c, F0, K0,  6, 12, x)\n    c, e = R(c, d, e, a, b, F0, K0,  7, 13, x)\n    b, d = R(b, c, d, e, a, F0, K0,  9, 14, x)\n    a, c = R(a, b, c, d, e, F0, K0,  8, 15, x) # #15\n    # Round 2\n    e, b = R(e, a, b, c, d, F1, K1,  7,  7, x)\n    d, a = R(d, e, a, b, c, F1, K1,  6,  4, x)\n    c, e = R(c, d, e, a, b, F1, K1,  8, 13, x)\n    b, d = R(b, c, d, e, a, F1, K1, 13,  1, x)\n    a, c = R(a, b, c, d, e, F1, K1, 11, 10, x)\n    e, b = R(e, a, b, c, d, F1, K1,  9,  6, x)\n    d, a = R(d, e, a, b, c, F1, K1,  7, 15, x)\n    c, e = R(c, d, e, a, b, F1, K1, 15,  3, x)\n    b, d = R(b, c, d, e, a, F1, K1,  7, 12, x)\n    a, c = R(a, b, c, d, e, F1, K1, 12,  0, x)\n    e, b = R(e, a, b, c, d, F1, K1, 15,  9, x)\n    d, a = R(d, e, a, b, c, F1, K1,  9,  5, x)\n    c, e = R(c, d, e, a, b, F1, K1, 11,  2, x)\n    b, d = R(b, c, d, e, a, F1, K1,  7, 14, x)\n    a, c = R(a, b, c, d, e, F1, K1, 13, 11, x)\n    e, b = R(e, a, b, c, d, F1, K1, 12,  8, x) # #31\n    # Round 3\n    d, a = R(d, e, a, b, c, F2, K2, 11,  3, x)\n    c, e = R(c, d, e, a, b, F2, K2, 13, 10, x)\n    b, d = R(b, c, d, e, a, F2, K2,  6, 14, x)\n    a, c = R(a, b, c, d, e, F2, K2,  7,  4, x)\n    e, b = R(e, a, b, c, d, F2, K2, 14,  9, x)\n    d, a = R(d, e, a, b, c, F2, K2,  9, 15, x)\n    c, e = R(c, d, e, a, b, F2, K2, 13,  8, x)\n    b, d = R(b, c, d, e, a, F2, K2, 15,  1, x)\n    a, c = R(a, b, c, d, e, F2, K2, 14,  2, x)\n    e, b = R(e, a, b, c, d, F2, K2,  8,  7, x)\n    d, a = R(d, e, a, b, c, F2, K2, 13,  0, x)\n    c, e = R(c, d, e, a, b, F2, K2,  6,  6, x)\n    b, d = R(b, c, d, e, a, F2, K2,  5, 13, x)\n    a, c = R(a, b, c, d, e, F2, K2, 12, 11, x)\n    e, b = R(e, a, b, c, d, F2, K2,  7,  5, x)\n    d, a = R(d, e, a, b, c, F2, K2,  5, 12, x) # #47\n    # Round 4\n    c, e = R(c, d, e, a, b, F3, K3, 11,  1, x)\n    b, d = R(b, c, d, e, a, F3, K3, 12,  9, x)\n    a, c = R(a, b, c, d, e, F3, K3, 14, 11, x)\n    e, b = R(e, a, b, c, d, F3, K3, 15, 10, x)\n    d, a = R(d, e, a, b, c, F3, K3, 14,  0, x)\n    c, e = R(c, d, e, a, b, F3, K3, 15,  8, x)\n    b, d = R(b, c, d, e, a, F3, K3,  9, 12, x)\n    a, c = R(a, b, c, d, e, F3, K3,  8,  4, x)\n    e, b = R(e, a, b, c, d, F3, K3,  9, 13, x)\n    d, a = R(d, e, a, b, c, F3, K3, 14,  3, x)\n    c, e = R(c, d, e, a, b, F3, K3,  5,  7, x)\n    b, d = R(b, c, d, e, a, F3, K3,  6, 15, x)\n    a, c = R(a, b, c, d, e, F3, K3,  8, 14, x)\n    e, b = R(e, a, b, c, d, F3, K3,  6,  5, x)\n    d, a = R(d, e, a, b, c, F3, K3,  5,  6, x)\n    c, e = R(c, d, e, a, b, F3, K3, 12,  2, x) # #63\n    # Round 5\n    b, d = R(b, c, d, e, a, F4, K4,  9,  4, x)\n    a, c = R(a, b, c, d, e, F4, K4, 15,  0, x)\n    e, b = R(e, a, b, c, d, F4, K4,  5,  5, x)\n    d, a = R(d, e, a, b, c, F4, K4, 11,  9, x)\n    c, e = R(c, d, e, a, b, F4, K4,  6,  7, x)\n    b, d = R(b, c, d, e, a, F4, K4,  8, 12, x)\n    a, c = R(a, b, c, d, e, F4, K4, 13,  2, x)\n    e, b = R(e, a, b, c, d, F4, K4, 12, 10, x)\n    d, a = R(d, e, a, b, c, F4, K4,  5, 14, x)\n    c, e = R(c, d, e, a, b, F4, K4, 12,  1, x)\n    b, d = R(b, c, d, e, a, F4, K4, 13,  3, x)\n    a, c = R(a, b, c, d, e, F4, K4, 14,  8, x)\n    e, b = R(e, a, b, c, d, F4, K4, 11, 11, x)\n    d, a = R(d, e, a, b, c, F4, K4,  8,  6, x)\n    c, e = R(c, d, e, a, b, F4, K4,  5, 15, x)\n    b, d = R(b, c, d, e, a, F4, K4,  6, 13, x) # #79\n\n    aa = a\n    bb = b\n    cc = c\n    dd = d\n    ee = e\n\n    a = state[0]\n    b = state[1]\n    c = state[2]\n    d = state[3]\n    e = state[4]    \n\n    # Parallel round 1\n    a, c = R(a, b, c, d, e, F4, KK0,  8,  5, x)\n    e, b = R(e, a, b, c, d, F4, KK0,  9, 14, x)\n    d, a = R(d, e, a, b, c, F4, KK0,  9,  7, x)\n    c, e = R(c, d, e, a, b, F4, KK0, 11,  0, x)\n    b, d = R(b, c, d, e, a, F4, KK0, 13,  9, x)\n    a, c = R(a, b, c, d, e, F4, KK0, 15,  2, x)\n    e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x)\n    d, a = R(d, e, a, b, c, F4, KK0,  5,  4, x)\n    c, e = R(c, d, e, a, b, F4, KK0,  7, 13, x)\n    b, d = R(b, c, d, e, a, F4, KK0,  7,  6, x)\n    a, c = R(a, b, c, d, e, F4, KK0,  8, 15, x)\n    e, b = R(e, a, b, c, d, F4, KK0, 11,  8, x)\n    d, a = R(d, e, a, b, c, F4, KK0, 14,  1, x)\n    c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x)\n    b, d = R(b, c, d, e, a, F4, KK0, 12,  3, x)\n    a, c = R(a, b, c, d, e, F4, KK0,  6, 12, x) # #15\n    # Parallel round 2\n    e, b = R(e, a, b, c, d, F3, KK1,  9,  6, x)\n    d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x)\n    c, e = R(c, d, e, a, b, F3, KK1, 15,  3, x)\n    b, d = R(b, c, d, e, a, F3, KK1,  7,  7, x)\n    a, c = R(a, b, c, d, e, F3, KK1, 12,  0, x)\n    e, b = R(e, a, b, c, d, F3, KK1,  8, 13, x)\n    d, a = R(d, e, a, b, c, F3, KK1,  9,  5, x)\n    c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x)\n    b, d = R(b, c, d, e, a, F3, KK1,  7, 14, x)\n    a, c = R(a, b, c, d, e, F3, KK1,  7, 15, x)\n    e, b = R(e, a, b, c, d, F3, KK1, 12,  8, x)\n    d, a = R(d, e, a, b, c, F3, KK1,  7, 12, x)\n    c, e = R(c, d, e, a, b, F3, KK1,  6,  4, x)\n    b, d = R(b, c, d, e, a, F3, KK1, 15,  9, x)\n    a, c = R(a, b, c, d, e, F3, KK1, 13,  1, x)\n    e, b = R(e, a, b, c, d, F3, KK1, 11,  2, x) # #31\n    # Parallel round 3\n    d, a = R(d, e, a, b, c, F2, KK2,  9, 15, x)\n    c, e = R(c, d, e, a, b, F2, KK2,  7,  5, x)\n    b, d = R(b, c, d, e, a, F2, KK2, 15,  1, x)\n    a, c = R(a, b, c, d, e, F2, KK2, 11,  3, x)\n    e, b = R(e, a, b, c, d, F2, KK2,  8,  7, x)\n    d, a = R(d, e, a, b, c, F2, KK2,  6, 14, x)\n    c, e = R(c, d, e, a, b, F2, KK2,  6,  6, x)\n    b, d = R(b, c, d, e, a, F2, KK2, 14,  9, x)\n    a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x)\n    e, b = R(e, a, b, c, d, F2, KK2, 13,  8, x)\n    d, a = R(d, e, a, b, c, F2, KK2,  5, 12, x)\n    c, e = R(c, d, e, a, b, F2, KK2, 14,  2, x)\n    b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x)\n    a, c = R(a, b, c, d, e, F2, KK2, 13,  0, x)\n    e, b = R(e, a, b, c, d, F2, KK2,  7,  4, x)\n    d, a = R(d, e, a, b, c, F2, KK2,  5, 13, x) # #47\n    # Parallel round 4\n    c, e = R(c, d, e, a, b, F1, KK3, 15,  8, x)\n    b, d = R(b, c, d, e, a, F1, KK3,  5,  6, x)\n    a, c = R(a, b, c, d, e, F1, KK3,  8,  4, x)\n    e, b = R(e, a, b, c, d, F1, KK3, 11,  1, x)\n    d, a = R(d, e, a, b, c, F1, KK3, 14,  3, x)\n    c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x)\n    b, d = R(b, c, d, e, a, F1, KK3,  6, 15, x)\n    a, c = R(a, b, c, d, e, F1, KK3, 14,  0, x)\n    e, b = R(e, a, b, c, d, F1, KK3,  6,  5, x)\n    d, a = R(d, e, a, b, c, F1, KK3,  9, 12, x)\n    c, e = R(c, d, e, a, b, F1, KK3, 12,  2, x)\n    b, d = R(b, c, d, e, a, F1, KK3,  9, 13, x)\n    a, c = R(a, b, c, d, e, F1, KK3, 12,  9, x)\n    e, b = R(e, a, b, c, d, F1, KK3,  5,  7, x)\n    d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x)\n    c, e = R(c, d, e, a, b, F1, KK3,  8, 14, x) # #63\n    # Parallel round 5\n    b, d = R(b, c, d, e, a, F0, KK4,  8, 12, x)\n    a, c = R(a, b, c, d, e, F0, KK4,  5, 15, x)\n    e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x)\n    d, a = R(d, e, a, b, c, F0, KK4,  9,  4, x)\n    c, e = R(c, d, e, a, b, F0, KK4, 12,  1, x)\n    b, d = R(b, c, d, e, a, F0, KK4,  5,  5, x)\n    a, c = R(a, b, c, d, e, F0, KK4, 14,  8, x)\n    e, b = R(e, a, b, c, d, F0, KK4,  6,  7, x)\n    d, a = R(d, e, a, b, c, F0, KK4,  8,  6, x)\n    c, e = R(c, d, e, a, b, F0, KK4, 13,  2, x)\n    b, d = R(b, c, d, e, a, F0, KK4,  6, 13, x)\n    a, c = R(a, b, c, d, e, F0, KK4,  5, 14, x)\n    e, b = R(e, a, b, c, d, F0, KK4, 15,  0, x)\n    d, a = R(d, e, a, b, c, F0, KK4, 13,  3, x)\n    c, e = R(c, d, e, a, b, F0, KK4, 11,  9, x)\n    b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) # #79\n\n    t = (state[1] + cc + d) % 0x100000000\n    state[1] = (state[2] + dd + e) % 0x100000000\n    state[2] = (state[3] + ee + a) % 0x100000000\n    state[3] = (state[4] + aa + b) % 0x100000000\n    state[4] = (state[0] + bb + c) % 0x100000000\n    state[0] = t % 0x100000000\n\n\ndef RMD160Update(ctx, inp, inplen):\n    if type(inp) == str:\n        inp = [ord(i)&0xff for i in inp]\n    \n    have = int((ctx.count // 8) % 64)\n    inplen = int(inplen)\n    need = 64 - have\n    ctx.count += 8 * inplen\n    off = 0\n    if inplen >= need:\n        if have:\n            for i in range(need):\n                ctx.buffer[have + i] = inp[i]\n            RMD160Transform(ctx.state, ctx.buffer)\n            off = need\n            have = 0\n        while off + 64 <= inplen:\n            RMD160Transform(ctx.state, inp[off:]) #<---\n            off += 64\n    if off < inplen:\n        # memcpy(ctx->buffer + have, input+off, len-off)\n        for i in range(inplen - off):\n            ctx.buffer[have + i] = inp[off + i]\n\ndef RMD160Final(ctx):\n    size = struct.pack(\"<Q\", ctx.count)\n    padlen = 64 - ((ctx.count // 8) % 64)\n    if padlen < 1 + 8:\n        padlen += 64\n    RMD160Update(ctx, PADDING, padlen - 8)\n    RMD160Update(ctx, size, 8)\n    return struct.pack(\"<5L\", *ctx.state)\n\n\nassert \"37f332f68db77bd9d7edd4969571ad671cf9dd3b\" == new(\"The quick brown fox jumps over the lazy dog\").hexdigest()\nassert \"132072df690933835eb8b6ad0b77e7b6f14acad7\" == new(\"The quick brown fox jumps over the lazy cog\").hexdigest()\nassert \"9c1185a5c5e9fc54612808977ee8f548b2258d31\" == new(\"\").hexdigest()\n"
  },
  {
    "path": "src/lib/sslcrypto/fallback/__init__.py",
    "content": "from .aes import aes\nfrom .ecc import ecc\nfrom .rsa import rsa\n"
  },
  {
    "path": "src/lib/sslcrypto/fallback/_jacobian.py",
    "content": "\"\"\"\nThis code is public domain. Everyone has the right to do whatever they want\nwith it for any purpose.\n\nIn case your jurisdiction does not consider the above disclaimer valid or\nenforceable, here's an MIT license for you:\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 Vitalik Buterin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\"\"\"\n\n\nfrom ._util import inverse\n\n\nclass JacobianCurve:\n    def __init__(self, p, n, a, b, g):\n        self.p = p\n        self.n = n\n        self.a = a\n        self.b = b\n        self.g = g\n        self.n_length = len(bin(self.n).replace(\"0b\", \"\"))\n\n\n    def isinf(self, p):\n        return p[0] == 0 and p[1] == 0\n\n\n    def to_jacobian(self, p):\n        return p[0], p[1], 1\n\n\n    def jacobian_double(self, p):\n        if not p[1]:\n            return 0, 0, 0\n        ysq = (p[1] ** 2) % self.p\n        s = (4 * p[0] * ysq) % self.p\n        m = (3 * p[0] ** 2 + self.a * p[2] ** 4) % self.p\n        nx = (m ** 2 - 2 * s) % self.p\n        ny = (m * (s - nx) - 8 * ysq ** 2) % self.p\n        nz = (2 * p[1] * p[2]) % self.p\n        return nx, ny, nz\n\n\n    def jacobian_add(self, p, q):\n        if not p[1]:\n            return q\n        if not q[1]:\n            return p\n        u1 = (p[0] * q[2] ** 2) % self.p\n        u2 = (q[0] * p[2] ** 2) % self.p\n        s1 = (p[1] * q[2] ** 3) % self.p\n        s2 = (q[1] * p[2] ** 3) % self.p\n        if u1 == u2:\n            if s1 != s2:\n                return (0, 0, 1)\n            return self.jacobian_double(p)\n        h = u2 - u1\n        r = s2 - s1\n        h2 = (h * h) % self.p\n        h3 = (h * h2) % self.p\n        u1h2 = (u1 * h2) % self.p\n        nx = (r ** 2 - h3 - 2 * u1h2) % self.p\n        ny = (r * (u1h2 - nx) - s1 * h3) % self.p\n        nz = (h * p[2] * q[2]) % self.p\n        return (nx, ny, nz)\n\n\n    def from_jacobian(self, p):\n        z = inverse(p[2], self.p)\n        return (p[0] * z ** 2) % self.p, (p[1] * z ** 3) % self.p\n\n\n    def jacobian_multiply(self, a, n, secret=False):\n        if a[1] == 0 or n == 0:\n            return 0, 0, 1\n        if n == 1:\n            return a\n        if n < 0 or n >= self.n:\n            return self.jacobian_multiply(a, n % self.n, secret)\n        half = self.jacobian_multiply(a, n // 2, secret)\n        half_sq = self.jacobian_double(half)\n        if secret:\n            # A constant-time implementation\n            half_sq_a = self.jacobian_add(half_sq, a)\n            if n % 2 == 0:\n                result = half_sq\n            if n % 2 == 1:\n                result = half_sq_a\n            return result\n        else:\n            if n % 2 == 0:\n                return half_sq\n            return self.jacobian_add(half_sq, a)\n\n\n    def jacobian_shamir(self, a, n, b, m):\n        ab = self.jacobian_add(a, b)\n        if n < 0 or n >= self.n:\n            n %= self.n\n        if m < 0 or m >= self.n:\n            m %= self.n\n        res = 0, 0, 1  # point on infinity\n        for i in range(self.n_length - 1, -1, -1):\n            res = self.jacobian_double(res)\n            has_n = n & (1 << i)\n            has_m = m & (1 << i)\n            if has_n:\n                if has_m == 0:\n                    res = self.jacobian_add(res, a)\n                if has_m != 0:\n                    res = self.jacobian_add(res, ab)\n            else:\n                if has_m == 0:\n                    res = self.jacobian_add(res, (0, 0, 1))  # Try not to leak\n                if has_m != 0:\n                    res = self.jacobian_add(res, b)\n        return res\n\n\n    def fast_multiply(self, a, n, secret=False):\n        return self.from_jacobian(self.jacobian_multiply(self.to_jacobian(a), n, secret))\n\n\n    def fast_add(self, a, b):\n        return self.from_jacobian(self.jacobian_add(self.to_jacobian(a), self.to_jacobian(b)))\n\n\n    def fast_shamir(self, a, n, b, m):\n        return self.from_jacobian(self.jacobian_shamir(self.to_jacobian(a), n, self.to_jacobian(b), m))\n\n\n    def is_on_curve(self, a):\n        x, y = a\n        # Simple arithmetic check\n        if (pow(x, 3, self.p) + self.a * x + self.b) % self.p != y * y % self.p:\n            return False\n        # nP = point-at-infinity\n        return self.isinf(self.jacobian_multiply(self.to_jacobian(a), self.n))\n"
  },
  {
    "path": "src/lib/sslcrypto/fallback/_util.py",
    "content": "def int_to_bytes(raw, length):\n    data = []\n    for _ in range(length):\n        data.append(raw % 256)\n        raw //= 256\n    return bytes(data[::-1])\n\n\ndef bytes_to_int(data):\n    raw = 0\n    for byte in data:\n        raw = raw * 256 + byte\n    return raw\n\n\ndef legendre(a, p):\n    res = pow(a, (p - 1) // 2, p)\n    if res == p - 1:\n        return -1\n    else:\n        return res\n\n\ndef inverse(a, n):\n    if a == 0:\n        return 0\n    lm, hm = 1, 0\n    low, high = a % n, n\n    while low > 1:\n        r = high // low\n        nm, new = hm - lm * r, high - low * r\n        lm, low, hm, high = nm, new, lm, low\n    return lm % n\n\n\ndef square_root_mod_prime(n, p):\n    if n == 0:\n        return 0\n    if p == 2:\n        return n  # We should never get here but it might be useful\n    if legendre(n, p) != 1:\n        raise ValueError(\"No square root\")\n    # Optimizations\n    if p % 4 == 3:\n        return pow(n, (p + 1) // 4, p)\n    # 1. By factoring out powers of 2, find Q and S such that p - 1 =\n    # Q * 2 ** S with Q odd\n    q = p - 1\n    s = 0\n    while q % 2 == 0:\n        q //= 2\n        s += 1\n    # 2. Search for z in Z/pZ which is a quadratic non-residue\n    z = 1\n    while legendre(z, p) != -1:\n        z += 1\n    m, c, t, r = s, pow(z, q, p), pow(n, q, p), pow(n, (q + 1) // 2, p)\n    while True:\n        if t == 0:\n            return 0\n        elif t == 1:\n            return r\n        # Use repeated squaring to find the least i, 0 < i < M, such\n        # that t ** (2 ** i) = 1\n        t_sq = t\n        i = 0\n        for i in range(1, m):\n            t_sq = t_sq * t_sq % p\n            if t_sq == 1:\n                break\n        else:\n            raise ValueError(\"Should never get here\")\n        # Let b = c ** (2 ** (m - i - 1))\n        b = pow(c, 2 ** (m - i - 1), p)\n        m = i\n        c = b * b % p\n        t = t * b * b % p\n        r = r * b % p\n    return r\n"
  },
  {
    "path": "src/lib/sslcrypto/fallback/aes.py",
    "content": "import os\nimport pyaes\nfrom .._aes import AES\n\n\n__all__ = [\"aes\"]\n\nclass AESBackend:\n    def _get_algo_cipher_type(self, algo):\n        if not algo.startswith(\"aes-\") or algo.count(\"-\") != 2:\n            raise ValueError(\"Unknown cipher algorithm {}\".format(algo))\n        key_length, cipher_type = algo[4:].split(\"-\")\n        if key_length not in (\"128\", \"192\", \"256\"):\n            raise ValueError(\"Unknown cipher algorithm {}\".format(algo))\n        if cipher_type not in (\"cbc\", \"ctr\", \"cfb\", \"ofb\"):\n            raise ValueError(\"Unknown cipher algorithm {}\".format(algo))\n        return cipher_type\n\n\n    def is_algo_supported(self, algo):\n        try:\n            self._get_algo_cipher_type(algo)\n            return True\n        except ValueError:\n            return False\n\n\n    def random(self, length):\n        return os.urandom(length)\n\n\n    def encrypt(self, data, key, algo=\"aes-256-cbc\"):\n        cipher_type = self._get_algo_cipher_type(algo)\n\n        # Generate random IV\n        iv = os.urandom(16)\n\n        if cipher_type == \"cbc\":\n            cipher = pyaes.AESModeOfOperationCBC(key, iv=iv)\n        elif cipher_type == \"ctr\":\n            # The IV is actually a counter, not an IV but it does almost the\n            # same. Notice: pyaes always uses 1 as initial counter! Make sure\n            # not to call pyaes directly.\n\n            # We kinda do two conversions here: from byte array to int here, and\n            # from int to byte array in pyaes internals. It's possible to fix that\n            # but I didn't notice any performance changes so I'm keeping clean code.\n            iv_int = 0\n            for byte in iv:\n                iv_int = (iv_int * 256) + byte\n            counter = pyaes.Counter(iv_int)\n            cipher = pyaes.AESModeOfOperationCTR(key, counter=counter)\n        elif cipher_type == \"cfb\":\n            # Change segment size from default 8 bytes to 16 bytes for OpenSSL\n            # compatibility\n            cipher = pyaes.AESModeOfOperationCFB(key, iv, segment_size=16)\n        elif cipher_type == \"ofb\":\n            cipher = pyaes.AESModeOfOperationOFB(key, iv)\n\n        encrypter = pyaes.Encrypter(cipher)\n        ciphertext = encrypter.feed(data)\n        ciphertext += encrypter.feed()\n        return ciphertext, iv\n\n\n    def decrypt(self, ciphertext, iv, key, algo=\"aes-256-cbc\"):\n        cipher_type = self._get_algo_cipher_type(algo)\n\n        if cipher_type == \"cbc\":\n            cipher = pyaes.AESModeOfOperationCBC(key, iv=iv)\n        elif cipher_type == \"ctr\":\n            # The IV is actually a counter, not an IV but it does almost the\n            # same. Notice: pyaes always uses 1 as initial counter! Make sure\n            # not to call pyaes directly.\n\n            # We kinda do two conversions here: from byte array to int here, and\n            # from int to byte array in pyaes internals. It's possible to fix that\n            # but I didn't notice any performance changes so I'm keeping clean code.\n            iv_int = 0\n            for byte in iv:\n                iv_int = (iv_int * 256) + byte\n            counter = pyaes.Counter(iv_int)\n            cipher = pyaes.AESModeOfOperationCTR(key, counter=counter)\n        elif cipher_type == \"cfb\":\n            # Change segment size from default 8 bytes to 16 bytes for OpenSSL\n            # compatibility\n            cipher = pyaes.AESModeOfOperationCFB(key, iv, segment_size=16)\n        elif cipher_type == \"ofb\":\n            cipher = pyaes.AESModeOfOperationOFB(key, iv)\n\n        decrypter = pyaes.Decrypter(cipher)\n        data = decrypter.feed(ciphertext)\n        data += decrypter.feed()\n        return data\n\n\n    def get_backend(self):\n        return \"fallback\"\n\n\naes = AES(AESBackend())\n"
  },
  {
    "path": "src/lib/sslcrypto/fallback/ecc.py",
    "content": "import hmac\nimport os\nfrom ._jacobian import JacobianCurve\nfrom .._ecc import ECC\nfrom .aes import aes\nfrom ._util import int_to_bytes, bytes_to_int, inverse, square_root_mod_prime\n\n\nclass EllipticCurveBackend:\n    def __init__(self, p, n, a, b, g):\n        self.p, self.n, self.a, self.b, self.g = p, n, a, b, g\n        self.jacobian = JacobianCurve(p, n, a, b, g)\n\n        self.public_key_length = (len(bin(p).replace(\"0b\", \"\")) + 7) // 8\n        self.order_bitlength = len(bin(n).replace(\"0b\", \"\"))\n\n\n    def _int_to_bytes(self, raw, len=None):\n        return int_to_bytes(raw, len or self.public_key_length)\n\n\n    def decompress_point(self, public_key):\n        # Parse & load data\n        x = bytes_to_int(public_key[1:])\n        # Calculate Y\n        y_square = (pow(x, 3, self.p) + self.a * x + self.b) % self.p\n        try:\n            y = square_root_mod_prime(y_square, self.p)\n        except Exception:\n            raise ValueError(\"Invalid public key\") from None\n        if y % 2 != public_key[0] - 0x02:\n            y = self.p - y\n        return self._int_to_bytes(x), self._int_to_bytes(y)\n\n\n    def new_private_key(self):\n        while True:\n            private_key = os.urandom(self.public_key_length)\n            if bytes_to_int(private_key) >= self.n:\n                continue\n            return private_key\n\n\n    def private_to_public(self, private_key):\n        raw = bytes_to_int(private_key)\n        x, y = self.jacobian.fast_multiply(self.g, raw)\n        return self._int_to_bytes(x), self._int_to_bytes(y)\n\n\n    def ecdh(self, private_key, public_key):\n        x, y = public_key\n        x, y = bytes_to_int(x), bytes_to_int(y)\n        private_key = bytes_to_int(private_key)\n        x, _ = self.jacobian.fast_multiply((x, y), private_key, secret=True)\n        return self._int_to_bytes(x)\n\n\n    def _subject_to_int(self, subject):\n        return bytes_to_int(subject[:(self.order_bitlength + 7) // 8])\n\n\n    def sign(self, subject, raw_private_key, recoverable, is_compressed, entropy):\n        z = self._subject_to_int(subject)\n        private_key = bytes_to_int(raw_private_key)\n        k = bytes_to_int(entropy)\n\n        # Fix k length to prevent Minerva. Increasing multiplier by a\n        # multiple of order doesn't break anything. This fix was ported\n        # from python-ecdsa\n        ks = k + self.n\n        kt = ks + self.n\n        ks_len = len(bin(ks).replace(\"0b\", \"\")) // 8\n        kt_len = len(bin(kt).replace(\"0b\", \"\")) // 8\n        if ks_len == kt_len:\n            k = kt\n        else:\n            k = ks\n        px, py = self.jacobian.fast_multiply(self.g, k, secret=True)\n\n        r = px % self.n\n        if r == 0:\n            # Invalid k\n            raise ValueError(\"Invalid k\")\n\n        s = (inverse(k, self.n) * (z + (private_key * r))) % self.n\n        if s == 0:\n            # Invalid k\n            raise ValueError(\"Invalid k\")\n\n        inverted = False\n        if s * 2 >= self.n:\n            s = self.n - s\n            inverted = True\n        rs_buf = self._int_to_bytes(r) + self._int_to_bytes(s)\n\n        if recoverable:\n            recid = (py % 2) ^ inverted\n            recid += 2 * int(px // self.n)\n            if is_compressed:\n                return bytes([31 + recid]) + rs_buf\n            else:\n                if recid >= 4:\n                    raise ValueError(\"Too big recovery ID, use compressed address instead\")\n                return bytes([27 + recid]) + rs_buf\n        else:\n            return rs_buf\n\n\n    def recover(self, signature, subject):\n        z = self._subject_to_int(subject)\n\n        recid = signature[0] - 27 if signature[0] < 31 else signature[0] - 31\n        r = bytes_to_int(signature[1:self.public_key_length + 1])\n        s = bytes_to_int(signature[self.public_key_length + 1:])\n\n        # Verify bounds\n        if not 0 <= recid < 2 * (self.p // self.n + 1):\n            raise ValueError(\"Invalid recovery ID\")\n        if r >= self.n:\n            raise ValueError(\"r is out of bounds\")\n        if s >= self.n:\n            raise ValueError(\"s is out of bounds\")\n\n        rinv = inverse(r, self.n)\n        u1 = (-z * rinv) % self.n\n        u2 = (s * rinv) % self.n\n\n        # Recover R\n        rx = r + (recid // 2) * self.n\n        if rx >= self.p:\n            raise ValueError(\"Rx is out of bounds\")\n\n        # Almost copied from decompress_point\n        ry_square = (pow(rx, 3, self.p) + self.a * rx + self.b) % self.p\n        try:\n            ry = square_root_mod_prime(ry_square, self.p)\n        except Exception:\n            raise ValueError(\"Invalid recovered public key\") from None\n\n        # Ensure the point is correct\n        if ry % 2 != recid % 2:\n            # Fix Ry sign\n            ry = self.p - ry\n\n        x, y = self.jacobian.fast_shamir(self.g, u1, (rx, ry), u2)\n        return self._int_to_bytes(x), self._int_to_bytes(y)\n\n\n    def verify(self, signature, subject, public_key):\n        z = self._subject_to_int(subject)\n\n        r = bytes_to_int(signature[:self.public_key_length])\n        s = bytes_to_int(signature[self.public_key_length:])\n\n        # Verify bounds\n        if r >= self.n:\n            raise ValueError(\"r is out of bounds\")\n        if s >= self.n:\n            raise ValueError(\"s is out of bounds\")\n\n        public_key = [bytes_to_int(c) for c in public_key]\n\n        # Ensure that the public key is correct\n        if not self.jacobian.is_on_curve(public_key):\n            raise ValueError(\"Public key is not on curve\")\n\n        sinv = inverse(s, self.n)\n        u1 = (z * sinv) % self.n\n        u2 = (r * sinv) % self.n\n\n        x1, _ = self.jacobian.fast_shamir(self.g, u1, public_key, u2)\n        if r != x1 % self.n:\n            raise ValueError(\"Invalid signature\")\n\n        return True\n\n\n    def derive_child(self, seed, child):\n        # Round 1\n        h = hmac.new(key=b\"Bitcoin seed\", msg=seed, digestmod=\"sha512\").digest()\n        private_key1 = h[:32]\n        x, y = self.private_to_public(private_key1)\n        public_key1 = bytes([0x02 + (y[-1] % 2)]) + x\n        private_key1 = bytes_to_int(private_key1)\n\n        # Round 2\n        msg = public_key1 + self._int_to_bytes(child, 4)\n        h = hmac.new(key=h[32:], msg=msg, digestmod=\"sha512\").digest()\n        private_key2 = bytes_to_int(h[:32])\n\n        return self._int_to_bytes((private_key1 + private_key2) % self.n)\n\n\n    @classmethod\n    def get_backend(cls):\n        return \"fallback\"\n\n\necc = ECC(EllipticCurveBackend, aes)\n"
  },
  {
    "path": "src/lib/sslcrypto/fallback/rsa.py",
    "content": "# pylint: disable=too-few-public-methods\n\nclass RSA:\n    def get_backend(self):\n        return \"fallback\"\n\n\nrsa = RSA()\n"
  },
  {
    "path": "src/lib/sslcrypto/openssl/__init__.py",
    "content": "from .aes import aes\nfrom .ecc import ecc\nfrom .rsa import rsa\n"
  },
  {
    "path": "src/lib/sslcrypto/openssl/aes.py",
    "content": "import ctypes\nimport threading\nfrom .._aes import AES\nfrom ..fallback.aes import aes as fallback_aes\nfrom .library import lib, openssl_backend\n\n\n# Initialize functions\ntry:\n    lib.EVP_CIPHER_CTX_new.restype = ctypes.POINTER(ctypes.c_char)\nexcept AttributeError:\n    pass\nlib.EVP_get_cipherbyname.restype = ctypes.POINTER(ctypes.c_char)\n\n\nthread_local = threading.local()\n\n\nclass Context:\n    def __init__(self, ptr, do_free):\n        self.lib = lib\n        self.ptr = ptr\n        self.do_free = do_free\n\n\n    def __del__(self):\n        if self.do_free:\n            self.lib.EVP_CIPHER_CTX_free(self.ptr)\n\n\nclass AESBackend:\n    ALGOS = (\n        \"aes-128-cbc\", \"aes-192-cbc\", \"aes-256-cbc\",\n        \"aes-128-ctr\", \"aes-192-ctr\", \"aes-256-ctr\",\n        \"aes-128-cfb\", \"aes-192-cfb\", \"aes-256-cfb\",\n        \"aes-128-ofb\", \"aes-192-ofb\", \"aes-256-ofb\"\n    )\n\n    def __init__(self):\n        self.is_supported_ctx_new = hasattr(lib, \"EVP_CIPHER_CTX_new\")\n        self.is_supported_ctx_reset = hasattr(lib, \"EVP_CIPHER_CTX_reset\")\n\n\n    def _get_ctx(self):\n        if not hasattr(thread_local, \"ctx\"):\n            if self.is_supported_ctx_new:\n                thread_local.ctx = Context(lib.EVP_CIPHER_CTX_new(), True)\n            else:\n                # 1 KiB ought to be enough for everybody. We don't know the real\n                # size of the context buffer because we are unsure about padding and\n                # pointer size\n                thread_local.ctx = Context(ctypes.create_string_buffer(1024), False)\n        return thread_local.ctx.ptr\n\n\n    def get_backend(self):\n        return openssl_backend\n\n\n    def _get_cipher(self, algo):\n        if algo not in self.ALGOS:\n            raise ValueError(\"Unknown cipher algorithm {}\".format(algo))\n        cipher = lib.EVP_get_cipherbyname(algo.encode())\n        if not cipher:\n            raise ValueError(\"Unknown cipher algorithm {}\".format(algo))\n        return cipher\n\n\n    def is_algo_supported(self, algo):\n        try:\n            self._get_cipher(algo)\n            return True\n        except ValueError:\n            return False\n\n\n    def random(self, length):\n        entropy = ctypes.create_string_buffer(length)\n        lib.RAND_bytes(entropy, length)\n        return bytes(entropy)\n\n\n    def encrypt(self, data, key, algo=\"aes-256-cbc\"):\n        # Initialize context\n        ctx = self._get_ctx()\n        if not self.is_supported_ctx_new:\n            lib.EVP_CIPHER_CTX_init(ctx)\n        try:\n            lib.EVP_EncryptInit_ex(ctx, self._get_cipher(algo), None, None, None)\n\n            # Generate random IV\n            iv_length = 16\n            iv = self.random(iv_length)\n\n            # Set key and IV\n            lib.EVP_EncryptInit_ex(ctx, None, None, key, iv)\n\n            # Actually encrypt\n            block_size = 16\n            output = ctypes.create_string_buffer((len(data) // block_size + 1) * block_size)\n            output_len = ctypes.c_int()\n\n            if not lib.EVP_CipherUpdate(ctx, output, ctypes.byref(output_len), data, len(data)):\n                raise ValueError(\"Could not feed cipher with data\")\n\n            new_output = ctypes.byref(output, output_len.value)\n            output_len2 = ctypes.c_int()\n            if not lib.EVP_CipherFinal_ex(ctx, new_output, ctypes.byref(output_len2)):\n                raise ValueError(\"Could not finalize cipher\")\n\n            ciphertext = output[:output_len.value + output_len2.value]\n            return ciphertext, iv\n        finally:\n            if self.is_supported_ctx_reset:\n                lib.EVP_CIPHER_CTX_reset(ctx)\n            else:\n                lib.EVP_CIPHER_CTX_cleanup(ctx)\n\n\n    def decrypt(self, ciphertext, iv, key, algo=\"aes-256-cbc\"):\n        # Initialize context\n        ctx = self._get_ctx()\n        if not self.is_supported_ctx_new:\n            lib.EVP_CIPHER_CTX_init(ctx)\n        try:\n            lib.EVP_DecryptInit_ex(ctx, self._get_cipher(algo), None, None, None)\n\n            # Make sure IV length is correct\n            iv_length = 16\n            if len(iv) != iv_length:\n                raise ValueError(\"Expected IV to be {} bytes, got {} bytes\".format(iv_length, len(iv)))\n\n            # Set key and IV\n            lib.EVP_DecryptInit_ex(ctx, None, None, key, iv)\n\n            # Actually decrypt\n            output = ctypes.create_string_buffer(len(ciphertext))\n            output_len = ctypes.c_int()\n\n            if not lib.EVP_DecryptUpdate(ctx, output, ctypes.byref(output_len), ciphertext, len(ciphertext)):\n                raise ValueError(\"Could not feed decipher with ciphertext\")\n\n            new_output = ctypes.byref(output, output_len.value)\n            output_len2 = ctypes.c_int()\n            if not lib.EVP_DecryptFinal_ex(ctx, new_output, ctypes.byref(output_len2)):\n                raise ValueError(\"Could not finalize decipher\")\n\n            return output[:output_len.value + output_len2.value]\n        finally:\n            if self.is_supported_ctx_reset:\n                lib.EVP_CIPHER_CTX_reset(ctx)\n            else:\n                lib.EVP_CIPHER_CTX_cleanup(ctx)\n\n\naes = AES(AESBackend(), fallback_aes)\n"
  },
  {
    "path": "src/lib/sslcrypto/openssl/discovery.py",
    "content": "# Can be redefined by user\ndef discover():\n\tpass"
  },
  {
    "path": "src/lib/sslcrypto/openssl/ecc.py",
    "content": "import ctypes\nimport hmac\nimport threading\nfrom .._ecc import ECC\nfrom .aes import aes\nfrom .library import lib, openssl_backend\n\n\n# Initialize functions\nlib.BN_new.restype = ctypes.POINTER(ctypes.c_char)\nlib.BN_bin2bn.restype = ctypes.POINTER(ctypes.c_char)\nlib.BN_CTX_new.restype = ctypes.POINTER(ctypes.c_char)\nlib.EC_GROUP_new_curve_GFp.restype = ctypes.POINTER(ctypes.c_char)\nlib.EC_KEY_new.restype = ctypes.POINTER(ctypes.c_char)\nlib.EC_POINT_new.restype = ctypes.POINTER(ctypes.c_char)\nlib.EC_KEY_get0_private_key.restype = ctypes.POINTER(ctypes.c_char)\nlib.EVP_PKEY_new.restype = ctypes.POINTER(ctypes.c_char)\ntry:\n    lib.EVP_PKEY_CTX_new.restype = ctypes.POINTER(ctypes.c_char)\nexcept AttributeError:\n    pass\n\n\nthread_local = threading.local()\n\n\n# This lock is required to keep ECC thread-safe. Old OpenSSL versions (before\n# 1.1.0) use global objects so they aren't thread safe. Fortunately we can check\n# the code to find out which functions are thread safe.\n#\n# For example, EC_GROUP_new_curve_GFp checks global error code to initialize\n# the group, so if two errors happen at once or two threads read the error code,\n# or the codes are read in the wrong order, the group is initialized in a wrong\n# way.\n#\n# EC_KEY_new_by_curve_name calls EC_GROUP_new_curve_GFp so it's not thread\n# safe. We can't use the lock because it would be too slow; instead, we use\n# EC_KEY_new and then EC_KEY_set_group which calls EC_GROUP_copy instead which\n# is thread safe.\nlock = threading.Lock()\n\n\nclass BN:\n    # BN_CTX\n    class Context:\n        def __init__(self):\n            self.ptr = lib.BN_CTX_new()\n            self.lib = lib  # For finalizer\n\n\n        def __del__(self):\n            self.lib.BN_CTX_free(self.ptr)\n\n\n        @classmethod\n        def get(cls):\n            # Get thread-safe contexf\n            if not hasattr(thread_local, \"bn_ctx\"):\n                thread_local.bn_ctx = cls()\n            return thread_local.bn_ctx.ptr\n\n\n    def __init__(self, value=None, link_only=False):\n        if link_only:\n            self.bn = value\n            self._free = False\n        else:\n            if value is None:\n                self.bn = lib.BN_new()\n                self._free = True\n            elif isinstance(value, int) and value < 256:\n                self.bn = lib.BN_new()\n                lib.BN_clear(self.bn)\n                lib.BN_add_word(self.bn, value)\n                self._free = True\n            else:\n                if isinstance(value, int):\n                    value = value.to_bytes(128, \"big\")\n                self.bn = lib.BN_bin2bn(value, len(value), None)\n                self._free = True\n\n\n    def __del__(self):\n        if self._free:\n            lib.BN_free(self.bn)\n\n\n    def bytes(self, length=None):\n        buf = ctypes.create_string_buffer((len(self) + 7) // 8)\n        lib.BN_bn2bin(self.bn, buf)\n        buf = bytes(buf)\n        if length is None:\n            return buf\n        else:\n            if length < len(buf):\n                raise ValueError(\"Too little space for BN\")\n            return b\"\\x00\" * (length - len(buf)) + buf\n\n    def __int__(self):\n        value = 0\n        for byte in self.bytes():\n            value = value * 256 + byte\n        return value\n\n    def __len__(self):\n        return lib.BN_num_bits(self.bn)\n\n\n    def inverse(self, modulo):\n        result = BN()\n        if not lib.BN_mod_inverse(result.bn, self.bn, modulo.bn, BN.Context.get()):\n            raise ValueError(\"Could not compute inverse\")\n        return result\n\n\n    def __floordiv__(self, other):\n        if not isinstance(other, BN):\n            raise TypeError(\"Can only divide BN by BN, not {}\".format(other))\n        result = BN()\n        if not lib.BN_div(result.bn, None, self.bn, other.bn, BN.Context.get()):\n            raise ZeroDivisionError(\"Division by zero\")\n        return result\n\n    def __mod__(self, other):\n        if not isinstance(other, BN):\n            raise TypeError(\"Can only divide BN by BN, not {}\".format(other))\n        result = BN()\n        if not lib.BN_div(None, result.bn, self.bn, other.bn, BN.Context.get()):\n            raise ZeroDivisionError(\"Division by zero\")\n        return result\n\n    def __add__(self, other):\n        if not isinstance(other, BN):\n            raise TypeError(\"Can only sum BN's, not BN and {}\".format(other))\n        result = BN()\n        if not lib.BN_add(result.bn, self.bn, other.bn):\n            raise ValueError(\"Could not sum two BN's\")\n        return result\n\n    def __sub__(self, other):\n        if not isinstance(other, BN):\n            raise TypeError(\"Can only subtract BN's, not BN and {}\".format(other))\n        result = BN()\n        if not lib.BN_sub(result.bn, self.bn, other.bn):\n            raise ValueError(\"Could not subtract BN from BN\")\n        return result\n\n    def __mul__(self, other):\n        if not isinstance(other, BN):\n            raise TypeError(\"Can only multiply BN by BN, not {}\".format(other))\n        result = BN()\n        if not lib.BN_mul(result.bn, self.bn, other.bn, BN.Context.get()):\n            raise ValueError(\"Could not multiply two BN's\")\n        return result\n\n    def __neg__(self):\n        return BN(0) - self\n\n\n    # A dirty but nice way to update current BN and free old BN at the same time\n    def __imod__(self, other):\n        res = self % other\n        self.bn, res.bn = res.bn, self.bn\n        return self\n    def __iadd__(self, other):\n        res = self + other\n        self.bn, res.bn = res.bn, self.bn\n        return self\n    def __isub__(self, other):\n        res = self - other\n        self.bn, res.bn = res.bn, self.bn\n        return self\n    def __imul__(self, other):\n        res = self * other\n        self.bn, res.bn = res.bn, self.bn\n        return self\n\n\n    def cmp(self, other):\n        if not isinstance(other, BN):\n            raise TypeError(\"Can only compare BN with BN, not {}\".format(other))\n        return lib.BN_cmp(self.bn, other.bn)\n\n    def __eq__(self, other):\n        return self.cmp(other) == 0\n    def __lt__(self, other):\n        return self.cmp(other) < 0\n    def __gt__(self, other):\n        return self.cmp(other) > 0\n    def __ne__(self, other):\n        return self.cmp(other) != 0\n    def __le__(self, other):\n        return self.cmp(other) <= 0\n    def __ge__(self, other):\n        return self.cmp(other) >= 0\n\n\n    def __repr__(self):\n        return \"<BN {}>\".format(int(self))\n\n    def __str__(self):\n        return str(int(self))\n\n\nclass EllipticCurveBackend:\n    def __init__(self, p, n, a, b, g):\n        bn_ctx = BN.Context.get()\n\n        self.lib = lib  # For finalizer\n\n        self.p = BN(p)\n        self.order = BN(n)\n        self.a = BN(a)\n        self.b = BN(b)\n        self.h = BN((p + n // 2) // n)\n\n        with lock:\n            # Thread-safety\n            self.group = lib.EC_GROUP_new_curve_GFp(self.p.bn, self.a.bn, self.b.bn, bn_ctx)\n            if not self.group:\n                raise ValueError(\"Could not create group object\")\n            generator = self._public_key_to_point(g)\n            lib.EC_GROUP_set_generator(self.group, generator, self.order.bn, self.h.bn)\n        if not self.group:\n            raise ValueError(\"The curve is not supported by OpenSSL\")\n\n        self.public_key_length = (len(self.p) + 7) // 8\n\n        self.is_supported_evp_pkey_ctx = hasattr(lib, \"EVP_PKEY_CTX_new\")\n\n\n    def __del__(self):\n        self.lib.EC_GROUP_free(self.group)\n\n\n    def _private_key_to_ec_key(self, private_key):\n        # Thread-safety\n        eckey = lib.EC_KEY_new()\n        lib.EC_KEY_set_group(eckey, self.group)\n        if not eckey:\n            raise ValueError(\"Failed to allocate EC_KEY\")\n        private_key = BN(private_key)\n        if not lib.EC_KEY_set_private_key(eckey, private_key.bn):\n            lib.EC_KEY_free(eckey)\n            raise ValueError(\"Invalid private key\")\n        return eckey, private_key\n\n\n    def _public_key_to_point(self, public_key):\n        x = BN(public_key[0])\n        y = BN(public_key[1])\n        # EC_KEY_set_public_key_affine_coordinates is not supported by\n        # OpenSSL 1.0.0 so we can't use it\n        point = lib.EC_POINT_new(self.group)\n        if not lib.EC_POINT_set_affine_coordinates_GFp(self.group, point, x.bn, y.bn, BN.Context.get()):\n            raise ValueError(\"Could not set public key affine coordinates\")\n        return point\n\n\n    def _public_key_to_ec_key(self, public_key):\n        # Thread-safety\n        eckey = lib.EC_KEY_new()\n        lib.EC_KEY_set_group(eckey, self.group)\n        if not eckey:\n            raise ValueError(\"Failed to allocate EC_KEY\")\n        try:\n            # EC_KEY_set_public_key_affine_coordinates is not supported by\n            # OpenSSL 1.0.0 so we can't use it\n            point = self._public_key_to_point(public_key)\n            if not lib.EC_KEY_set_public_key(eckey, point):\n                raise ValueError(\"Could not set point\")\n            lib.EC_POINT_free(point)\n            return eckey\n        except Exception as e:\n            lib.EC_KEY_free(eckey)\n            raise e from None\n\n\n    def _point_to_affine(self, point):\n        # Convert to affine coordinates\n        x = BN()\n        y = BN()\n        if lib.EC_POINT_get_affine_coordinates_GFp(self.group, point, x.bn, y.bn, BN.Context.get()) != 1:\n            raise ValueError(\"Failed to convert public key to affine coordinates\")\n        # Convert to binary\n        if (len(x) + 7) // 8 > self.public_key_length:\n            raise ValueError(\"Public key X coordinate is too large\")\n        if (len(y) + 7) // 8 > self.public_key_length:\n            raise ValueError(\"Public key Y coordinate is too large\")\n        return x.bytes(self.public_key_length), y.bytes(self.public_key_length)\n\n\n    def decompress_point(self, public_key):\n        point = lib.EC_POINT_new(self.group)\n        if not point:\n            raise ValueError(\"Could not create point\")\n        try:\n            if not lib.EC_POINT_oct2point(self.group, point, public_key, len(public_key), BN.Context.get()):\n                raise ValueError(\"Invalid compressed public key\")\n            return self._point_to_affine(point)\n        finally:\n            lib.EC_POINT_free(point)\n\n\n    def new_private_key(self):\n        # Create random key\n        # Thread-safety\n        eckey = lib.EC_KEY_new()\n        lib.EC_KEY_set_group(eckey, self.group)\n        lib.EC_KEY_generate_key(eckey)\n        # To big integer\n        private_key = BN(lib.EC_KEY_get0_private_key(eckey), link_only=True)\n        # To binary\n        private_key_buf = private_key.bytes(self.public_key_length)\n        # Cleanup\n        lib.EC_KEY_free(eckey)\n        return private_key_buf\n\n\n    def private_to_public(self, private_key):\n        eckey, private_key = self._private_key_to_ec_key(private_key)\n        try:\n            # Derive public key\n            point = lib.EC_POINT_new(self.group)\n            try:\n                if not lib.EC_POINT_mul(self.group, point, private_key.bn, None, None, BN.Context.get()):\n                    raise ValueError(\"Failed to derive public key\")\n                return self._point_to_affine(point)\n            finally:\n                lib.EC_POINT_free(point)\n        finally:\n            lib.EC_KEY_free(eckey)\n\n\n    def ecdh(self, private_key, public_key):\n        if not self.is_supported_evp_pkey_ctx:\n            # Use ECDH_compute_key instead\n            # Create EC_KEY from private key\n            eckey, _ = self._private_key_to_ec_key(private_key)\n            try:\n                # Create EC_POINT from public key\n                point = self._public_key_to_point(public_key)\n                try:\n                    key = ctypes.create_string_buffer(self.public_key_length)\n                    if lib.ECDH_compute_key(key, self.public_key_length, point, eckey, None) == -1:\n                        raise ValueError(\"Could not compute shared secret\")\n                    return bytes(key)\n                finally:\n                    lib.EC_POINT_free(point)\n            finally:\n                lib.EC_KEY_free(eckey)\n\n        # Private key:\n        # Create EC_KEY\n        eckey, _ = self._private_key_to_ec_key(private_key)\n        try:\n            # Convert to EVP_PKEY\n            pkey = lib.EVP_PKEY_new()\n            if not pkey:\n                raise ValueError(\"Could not create private key object\")\n            try:\n                lib.EVP_PKEY_set1_EC_KEY(pkey, eckey)\n\n                # Public key:\n                # Create EC_KEY\n                peer_eckey = self._public_key_to_ec_key(public_key)\n                try:\n                    # Convert to EVP_PKEY\n                    peer_pkey = lib.EVP_PKEY_new()\n                    if not peer_pkey:\n                        raise ValueError(\"Could not create public key object\")\n                    try:\n                        lib.EVP_PKEY_set1_EC_KEY(peer_pkey, peer_eckey)\n\n                        # Create context\n                        ctx = lib.EVP_PKEY_CTX_new(pkey, None)\n                        if not ctx:\n                            raise ValueError(\"Could not create EVP context\")\n                        try:\n                            if lib.EVP_PKEY_derive_init(ctx) != 1:\n                                raise ValueError(\"Could not initialize key derivation\")\n                            if not lib.EVP_PKEY_derive_set_peer(ctx, peer_pkey):\n                                raise ValueError(\"Could not set peer\")\n\n                            # Actually derive\n                            key_len = ctypes.c_int(0)\n                            lib.EVP_PKEY_derive(ctx, None, ctypes.byref(key_len))\n                            key = ctypes.create_string_buffer(key_len.value)\n                            lib.EVP_PKEY_derive(ctx, key, ctypes.byref(key_len))\n\n                            return bytes(key)\n                        finally:\n                            lib.EVP_PKEY_CTX_free(ctx)\n                    finally:\n                        lib.EVP_PKEY_free(peer_pkey)\n                finally:\n                    lib.EC_KEY_free(peer_eckey)\n            finally:\n                lib.EVP_PKEY_free(pkey)\n        finally:\n            lib.EC_KEY_free(eckey)\n\n\n    def _subject_to_bn(self, subject):\n        return BN(subject[:(len(self.order) + 7) // 8])\n\n\n    def sign(self, subject, private_key, recoverable, is_compressed, entropy):\n        z = self._subject_to_bn(subject)\n        private_key = BN(private_key)\n        k = BN(entropy)\n\n        rp = lib.EC_POINT_new(self.group)\n        bn_ctx = BN.Context.get()\n        try:\n            # Fix Minerva\n            k1 = k + self.order\n            k2 = k1 + self.order\n            if len(k1) == len(k2):\n                k = k2\n            else:\n                k = k1\n            if not lib.EC_POINT_mul(self.group, rp, k.bn, None, None, bn_ctx):\n                raise ValueError(\"Could not generate R\")\n            # Convert to affine coordinates\n            rx = BN()\n            ry = BN()\n            if lib.EC_POINT_get_affine_coordinates_GFp(self.group, rp, rx.bn, ry.bn, bn_ctx) != 1:\n                raise ValueError(\"Failed to convert R to affine coordinates\")\n            r = rx % self.order\n            if r == BN(0):\n                raise ValueError(\"Invalid k\")\n            # Calculate s = k^-1 * (z + r * private_key) mod n\n            s = (k.inverse(self.order) * (z + r * private_key)) % self.order\n            if s == BN(0):\n                raise ValueError(\"Invalid k\")\n\n            inverted = False\n            if s * BN(2) >= self.order:\n                s = self.order - s\n                inverted = True\n\n            r_buf = r.bytes(self.public_key_length)\n            s_buf = s.bytes(self.public_key_length)\n            if recoverable:\n                # Generate recid\n                recid = int(ry % BN(2)) ^ inverted\n                # The line below is highly unlikely to matter in case of\n                # secp256k1 but might make sense for other curves\n                recid += 2 * int(rx // self.order)\n                if is_compressed:\n                    return bytes([31 + recid]) + r_buf + s_buf\n                else:\n                    if recid >= 4:\n                        raise ValueError(\"Too big recovery ID, use compressed address instead\")\n                    return bytes([27 + recid]) + r_buf + s_buf\n            else:\n                return r_buf + s_buf\n        finally:\n            lib.EC_POINT_free(rp)\n\n\n    def recover(self, signature, subject):\n        recid = signature[0] - 27 if signature[0] < 31 else signature[0] - 31\n        r = BN(signature[1:self.public_key_length + 1])\n        s = BN(signature[self.public_key_length + 1:])\n\n        # Verify bounds\n        if r >= self.order:\n            raise ValueError(\"r is out of bounds\")\n        if s >= self.order:\n            raise ValueError(\"s is out of bounds\")\n\n        bn_ctx = BN.Context.get()\n\n        z = self._subject_to_bn(subject)\n\n        rinv = r.inverse(self.order)\n        u1 = (-z * rinv) % self.order\n        u2 = (s * rinv) % self.order\n\n        # Recover R\n        rx = r + BN(recid // 2) * self.order\n        if rx >= self.p:\n            raise ValueError(\"Rx is out of bounds\")\n        rp = lib.EC_POINT_new(self.group)\n        if not rp:\n            raise ValueError(\"Could not create R\")\n        try:\n            init_buf = b\"\\x02\" + rx.bytes(self.public_key_length)\n            if not lib.EC_POINT_oct2point(self.group, rp, init_buf, len(init_buf), bn_ctx):\n                raise ValueError(\"Could not use Rx to initialize point\")\n            ry = BN()\n            if lib.EC_POINT_get_affine_coordinates_GFp(self.group, rp, None, ry.bn, bn_ctx) != 1:\n                raise ValueError(\"Failed to convert R to affine coordinates\")\n            if int(ry % BN(2)) != recid % 2:\n                # Fix Ry sign\n                ry = self.p - ry\n                if lib.EC_POINT_set_affine_coordinates_GFp(self.group, rp, rx.bn, ry.bn, bn_ctx) != 1:\n                    raise ValueError(\"Failed to update R coordinates\")\n\n            # Recover public key\n            result = lib.EC_POINT_new(self.group)\n            if not result:\n                raise ValueError(\"Could not create point\")\n            try:\n                if not lib.EC_POINT_mul(self.group, result, u1.bn, rp, u2.bn, bn_ctx):\n                    raise ValueError(\"Could not recover public key\")\n                return self._point_to_affine(result)\n            finally:\n                lib.EC_POINT_free(result)\n        finally:\n            lib.EC_POINT_free(rp)\n\n\n    def verify(self, signature, subject, public_key):\n        r_raw = signature[:self.public_key_length]\n        r = BN(r_raw)\n        s = BN(signature[self.public_key_length:])\n        if r >= self.order:\n            raise ValueError(\"r is out of bounds\")\n        if s >= self.order:\n            raise ValueError(\"s is out of bounds\")\n\n        bn_ctx = BN.Context.get()\n\n        z = self._subject_to_bn(subject)\n\n        pub_p = lib.EC_POINT_new(self.group)\n        if not pub_p:\n            raise ValueError(\"Could not create public key point\")\n        try:\n            init_buf = b\"\\x04\" + public_key[0] + public_key[1]\n            if not lib.EC_POINT_oct2point(self.group, pub_p, init_buf, len(init_buf), bn_ctx):\n                raise ValueError(\"Could initialize point\")\n\n            sinv = s.inverse(self.order)\n            u1 = (z * sinv) % self.order\n            u2 = (r * sinv) % self.order\n\n            # Recover public key\n            result = lib.EC_POINT_new(self.group)\n            if not result:\n                raise ValueError(\"Could not create point\")\n            try:\n                if not lib.EC_POINT_mul(self.group, result, u1.bn, pub_p, u2.bn, bn_ctx):\n                    raise ValueError(\"Could not recover public key\")\n                if BN(self._point_to_affine(result)[0]) % self.order != r:\n                    raise ValueError(\"Invalid signature\")\n                return True\n            finally:\n                lib.EC_POINT_free(result)\n        finally:\n            lib.EC_POINT_free(pub_p)\n\n\n    def derive_child(self, seed, child):\n        # Round 1\n        h = hmac.new(key=b\"Bitcoin seed\", msg=seed, digestmod=\"sha512\").digest()\n        private_key1 = h[:32]\n        x, y = self.private_to_public(private_key1)\n        public_key1 = bytes([0x02 + (y[-1] % 2)]) + x\n        private_key1 = BN(private_key1)\n\n        # Round 2\n        child_bytes = []\n        for _ in range(4):\n            child_bytes.append(child & 255)\n            child >>= 8\n        child_bytes = bytes(child_bytes[::-1])\n        msg = public_key1 + child_bytes\n        h = hmac.new(key=h[32:], msg=msg, digestmod=\"sha512\").digest()\n        private_key2 = BN(h[:32])\n\n        return ((private_key1 + private_key2) % self.order).bytes(self.public_key_length)\n\n\n    @classmethod\n    def get_backend(cls):\n        return openssl_backend\n\n\necc = ECC(EllipticCurveBackend, aes)\n"
  },
  {
    "path": "src/lib/sslcrypto/openssl/library.py",
    "content": "import os\nimport sys\nimport ctypes\nimport ctypes.util\nfrom .discovery import discover as user_discover\n\n\n# Disable false-positive _MEIPASS\n# pylint: disable=no-member,protected-access\n\n# Discover OpenSSL library\ndef discover_paths():\n    # Search local files first\n    if \"win\" in sys.platform:\n        # Windows\n        names = [\n            \"libeay32.dll\"\n        ]\n        openssl_paths = [os.path.abspath(path) for path in names]\n        if hasattr(sys, \"_MEIPASS\"):\n            openssl_paths += [os.path.join(sys._MEIPASS, path) for path in openssl_paths]\n        openssl_paths.append(ctypes.util.find_library(\"libeay32\"))\n    elif \"darwin\" in sys.platform:\n        # Mac OS\n        names = [\n            \"libcrypto.dylib\",\n            \"libcrypto.1.1.0.dylib\",\n            \"libcrypto.1.0.2.dylib\",\n            \"libcrypto.1.0.1.dylib\",\n            \"libcrypto.1.0.0.dylib\",\n            \"libcrypto.0.9.8.dylib\"\n        ]\n        openssl_paths = [os.path.abspath(path) for path in names]\n        openssl_paths += names\n        openssl_paths += [\n            \"/usr/local/opt/openssl/lib/libcrypto.dylib\"\n        ]\n        if hasattr(sys, \"_MEIPASS\") and \"RESOURCEPATH\" in os.environ:\n            openssl_paths += [\n                os.path.join(os.environ[\"RESOURCEPATH\"], \"..\", \"Frameworks\", name)\n                for name in names\n            ]\n        openssl_paths.append(ctypes.util.find_library(\"ssl\"))\n    else:\n        # Linux, BSD and such\n        names = [\n            \"libcrypto.so\",\n            \"libssl.so\",\n            \"libcrypto.so.1.1.0\",\n            \"libssl.so.1.1.0\",\n            \"libcrypto.so.1.0.2\",\n            \"libssl.so.1.0.2\",\n            \"libcrypto.so.1.0.1\",\n            \"libssl.so.1.0.1\",\n            \"libcrypto.so.1.0.0\",\n            \"libssl.so.1.0.0\",\n            \"libcrypto.so.0.9.8\",\n            \"libssl.so.0.9.8\"\n        ]\n        openssl_paths = [os.path.abspath(path) for path in names]\n        openssl_paths += names\n        if hasattr(sys, \"_MEIPASS\"):\n            openssl_paths += [os.path.join(sys._MEIPASS, path) for path in names]\n        openssl_paths.append(ctypes.util.find_library(\"ssl\"))\n    lst = user_discover()\n    if isinstance(lst, str):\n        lst = [lst]\n    elif not lst:\n        lst = []\n    return lst + openssl_paths\n\n\ndef discover_library():\n    for path in discover_paths():\n        if path:\n            try:\n                return ctypes.CDLL(path)\n            except OSError:\n                pass\n    raise OSError(\"OpenSSL is unavailable\")\n\n\nlib = discover_library()\n\n# Initialize internal state\ntry:\n    lib.OPENSSL_add_all_algorithms_conf()\nexcept AttributeError:\n    pass\n\ntry:\n    lib.OpenSSL_version.restype = ctypes.c_char_p\n    openssl_backend = lib.OpenSSL_version(0).decode()\nexcept AttributeError:\n    lib.SSLeay_version.restype = ctypes.c_char_p\n    openssl_backend = lib.SSLeay_version(0).decode()\n\nopenssl_backend += \" at \" + lib._name\n"
  },
  {
    "path": "src/lib/sslcrypto/openssl/rsa.py",
    "content": "# pylint: disable=too-few-public-methods\n\nfrom .library import openssl_backend\n\n\nclass RSA:\n    def get_backend(self):\n        return openssl_backend\n\n\nrsa = RSA()\n"
  },
  {
    "path": "src/lib/subtl/LICENCE",
    "content": "Copyright (c) 2012, Packetloop. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of Packetloop nor the names of its contributors may be\n      used to endorse or promote products derived from this software without\n      specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "src/lib/subtl/README.md",
    "content": "# subtl\n\n## Overview\n\nSUBTL is a **s**imple **U**DP **B**itTorrent **t**racker **l**ibrary for Python, licenced under the modified BSD license.\n\n## Example\n\nThis short example will list a few IP Addresses from a certain hash:\n\n    from subtl import UdpTrackerClient\n    utc = UdpTrackerClient('tracker.openbittorrent.com', 80)\n    utc.connect()\n    if not utc.poll_once():\n        raise Exception('Could not connect')\n    print('Success!')\n\n    utc.announce(info_hash='089184ED52AA37F71801391C451C5D5ADD0D9501')\n    data = utc.poll_once()\n    if not data:\n        raise Exception('Could not announce')\n    for a in data['response']['peers']:\n        print(a)\n\n## Caveats\n\n * There is no automatic retrying of sending packets yet.\n * This library won't download torrent files--it is simply a tracker client.\n"
  },
  {
    "path": "src/lib/subtl/__init__.py",
    "content": ""
  },
  {
    "path": "src/lib/subtl/subtl.py",
    "content": "'''\nBased on the specification at http://bittorrent.org/beps/bep_0015.html\n'''\nimport binascii\nimport random\nimport struct\nimport time\nimport socket\nfrom collections import defaultdict\n\n\n__version__ = '0.0.1'\n\nCONNECT = 0\nANNOUNCE = 1\nSCRAPE = 2\nERROR = 3\n\n\nclass UdpTrackerClientException(Exception):\n    pass\n\n\nclass UdpTrackerClient:\n\n    def __init__(self, host, port):\n        self.host = host\n        self.port = port\n        self.peer_port = 6881\n        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        self.conn_id = 0x41727101980\n        self.transactions = {}\n        self.peer_id = self._generate_peer_id()\n        self.timeout = 9\n\n    def connect(self):\n        return self._send(CONNECT)\n\n    def announce(self, **kwargs):\n        if not kwargs:\n            raise UdpTrackerClientException('arguments missing')\n        args = {\n            'peer_id': self.peer_id,\n            'downloaded': 0,\n            'left': 0,\n            'uploaded': 0,\n            'event': 0,\n            'key': 0,\n            'num_want': 10,\n            'ip_address': 0,\n            'port': self.peer_port,\n        }\n        args.update(kwargs)\n\n        fields = 'info_hash peer_id downloaded left uploaded event ' \\\n            'ip_address key num_want port'\n\n        # Check and raise if missing fields\n        self._check_fields(args, fields)\n\n        # Humans tend to use hex representations of the hash. Wasteful humans.\n        args['info_hash'] = args['info_hash']\n\n        values = [args[a] for a in fields.split()]\n        values[1] = values[1].encode(\"utf8\")\n        payload = struct.pack('!20s20sQQQLLLLH', *values)\n        return self._send(ANNOUNCE, payload)\n\n    def scrape(self, info_hash_list):\n        if len(info_hash_list) > 74:\n            raise UdpTrackerClientException('Max info_hashes is 74')\n\n        payload = ''\n        for info_hash in info_hash_list:\n            payload += info_hash\n\n        trans = self._send(SCRAPE, payload)\n        trans['sent_hashes'] = info_hash_list\n        return trans\n\n    def poll_once(self):\n        self.sock.settimeout(self.timeout)\n        try:\n            response = self.sock.recv(10240)\n        except socket.timeout:\n            return\n\n        header = response[:8]\n        payload = response[8:]\n        action, trans_id = struct.unpack('!LL', header)\n        try:\n            trans = self.transactions[trans_id]\n        except KeyError:\n            self.error('transaction_id not found')\n            return\n        trans['response'] = self._process_response(action, payload, trans)\n        trans['completed'] = True\n        del self.transactions[trans_id]\n        return trans\n\n    def error(self, message):\n        raise Exception('error: {}'.format(message))\n\n    def _send(self, action, payload=None):\n        if not payload:\n            payload = b''\n        trans_id, header = self._request_header(action)\n        self.transactions[trans_id] = trans = {\n            'action': action,\n            'time': time.time(),\n            'payload': payload,\n            'completed': False,\n        }\n        self.sock.connect((self.host, self.port))\n        self.sock.send(header + payload)\n        return trans\n\n    def _request_header(self, action):\n        trans_id = random.randint(0, (1 << 32) - 1)\n        return trans_id, struct.pack('!QLL', self.conn_id, action, trans_id)\n\n    def _process_response(self, action, payload, trans):\n        if action == CONNECT:\n            return self._process_connect(payload, trans)\n        elif action == ANNOUNCE:\n            return self._process_announce(payload, trans)\n        elif action == SCRAPE:\n            return self._process_scrape(payload, trans)\n        elif action == ERROR:\n            return self._process_error(payload, trans)\n        else:\n            raise UdpTrackerClientException(\n                'Unknown action response: {}'.format(action))\n\n    def _process_connect(self, payload, trans):\n        self.conn_id = struct.unpack('!Q', payload)[0]\n        return self.conn_id\n\n    def _process_announce(self, payload, trans):\n        response = {}\n\n        info_struct = '!LLL'\n        info_size = struct.calcsize(info_struct)\n        info = payload[:info_size]\n        interval, leechers, seeders = struct.unpack(info_struct, info)\n\n        peer_data = payload[info_size:]\n        peer_struct = '!LH'\n        peer_size = struct.calcsize(peer_struct)\n        peer_count = int(len(peer_data) / peer_size)\n        peers = []\n\n        for peer_offset in range(peer_count):\n            off = peer_size * peer_offset\n            peer = peer_data[off:off + peer_size]\n            addr, port = struct.unpack(peer_struct, peer)\n            peers.append({\n                'addr': socket.inet_ntoa(struct.pack('!L', addr)),\n                'port': port,\n            })\n\n        return {\n            'interval': interval,\n            'leechers': leechers,\n            'seeders': seeders,\n            'peers': peers,\n        }\n\n    def _process_scrape(self, payload, trans):\n        info_struct = '!LLL'\n        info_size = struct.calcsize(info_struct)\n        info_count = len(payload) / info_size\n        hashes = trans['sent_hashes']\n        response = {}\n        for info_offset in range(info_count):\n            off = info_size * info_offset\n            info = payload[off:off + info_size]\n            seeders, completed, leechers = struct.unpack(info_struct, info)\n            response[hashes[info_offset]] = {\n                'seeders': seeders,\n                'completed': completed,\n                'leechers': leechers,\n            }\n        return response\n\n    def _process_error(self, payload, trans):\n        '''\n        I haven't seen this action type be sent from a tracker, but I've left\n        it here for the possibility.\n        '''\n        self.error(payload)\n        return False\n\n    def _generate_peer_id(self):\n        '''http://www.bittorrent.org/beps/bep_0020.html'''\n        peer_id = '-PU' + __version__.replace('.', '-') + '-'\n        remaining = 20 - len(peer_id)\n        numbers = [str(random.randint(0, 9)) for _ in range(remaining)]\n        peer_id += ''.join(numbers)\n        assert(len(peer_id) == 20)\n        return peer_id\n\n    def _check_fields(self, args, fields):\n        for f in fields:\n            try:\n                args.get(f)\n            except KeyError:\n                raise UdpTrackerClientException('field missing: {}'.format(f))\n\n"
  },
  {
    "path": "src/main.py",
    "content": "# Included modules\nimport os\nimport sys\nimport stat\nimport time\nimport logging\n\nstartup_errors = []\ndef startupError(msg):\n    startup_errors.append(msg)\n    print(\"Startup error: %s\" % msg)\n\n# Third party modules\nimport gevent\nif gevent.version_info.major <= 1:  # Workaround for random crash when libuv used with threads\n    try:\n        if \"libev\" not in str(gevent.config.loop):\n            gevent.config.loop = \"libev-cext\"\n    except Exception as err:\n        startupError(\"Unable to switch gevent loop to libev: %s\" % err)\n\nimport gevent.monkey\ngevent.monkey.patch_all(thread=False, subprocess=False)\n\nupdate_after_shutdown = False  # If set True then update and restart zeronet after main loop ended\nrestart_after_shutdown = False  # If set True then restart zeronet after main loop ended\n\n# Load config\nfrom Config import config\nconfig.parse(silent=True)  # Plugins need to access the configuration\nif not config.arguments:  # Config parse failed, show the help screen and exit\n    config.parse()\n\nif not os.path.isdir(config.data_dir):\n    os.mkdir(config.data_dir)\n    try:\n        os.chmod(config.data_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)\n    except Exception as err:\n        startupError(\"Can't change permission of %s: %s\" % (config.data_dir, err))\n\nif not os.path.isfile(\"%s/sites.json\" % config.data_dir):\n    open(\"%s/sites.json\" % config.data_dir, \"w\").write(\"{}\")\nif not os.path.isfile(\"%s/users.json\" % config.data_dir):\n    open(\"%s/users.json\" % config.data_dir, \"w\").write(\"{}\")\n\nif config.action == \"main\":\n    from util import helper\n    try:\n        lock = helper.openLocked(\"%s/lock.pid\" % config.data_dir, \"w\")\n        lock.write(\"%s\" % os.getpid())\n    except BlockingIOError as err:\n        startupError(\"Can't open lock file, your ZeroNet client is probably already running, exiting... (%s)\" % err)\n        if config.open_browser and config.open_browser != \"False\":\n            print(\"Opening browser: %s...\", config.open_browser)\n            import webbrowser\n            try:\n                if config.open_browser == \"default_browser\":\n                    browser = webbrowser.get()\n                else:\n                    browser = webbrowser.get(config.open_browser)\n                browser.open(\"http://%s:%s/%s\" % (\n                    config.ui_ip if config.ui_ip != \"*\" else \"127.0.0.1\", config.ui_port, config.homepage\n                ), new=2)\n            except Exception as err:\n                startupError(\"Error starting browser: %s\" % err)\n        sys.exit()\n\nconfig.initLogging()\n\n# Debug dependent configuration\nfrom Debug import DebugHook\n\n# Load plugins\nfrom Plugin import PluginManager\nPluginManager.plugin_manager.loadPlugins()\nconfig.loadPlugins()\nconfig.parse()  # Parse again to add plugin configuration options\n\n# Log current config\nlogging.debug(\"Config: %s\" % config)\n\n# Modify stack size on special hardwares\nif config.stack_size:\n    import threading\n    threading.stack_size(config.stack_size)\n\n# Use pure-python implementation of msgpack to save CPU\nif config.msgpack_purepython:\n    os.environ[\"MSGPACK_PUREPYTHON\"] = \"True\"\n\n# Fix console encoding on Windows\nif sys.platform.startswith(\"win\"):\n    import subprocess\n    try:\n        chcp_res = subprocess.check_output(\"chcp 65001\", shell=True).decode(errors=\"ignore\").strip()\n        logging.debug(\"Changed console encoding to utf8: %s\" % chcp_res)\n    except Exception as err:\n        logging.error(\"Error changing console encoding to utf8: %s\" % err)\n\n# Socket monkey patch\nif config.proxy:\n    from util import SocksProxy\n    import urllib.request\n    logging.info(\"Patching sockets to socks proxy: %s\" % config.proxy)\n    if config.fileserver_ip == \"*\":\n        config.fileserver_ip = '127.0.0.1'  # Do not accept connections anywhere but localhost\n    config.disable_udp = True  # UDP not supported currently with proxy\n    SocksProxy.monkeyPatch(*config.proxy.split(\":\"))\nelif config.tor == \"always\":\n    from util import SocksProxy\n    import urllib.request\n    logging.info(\"Patching sockets to tor socks proxy: %s\" % config.tor_proxy)\n    if config.fileserver_ip == \"*\":\n        config.fileserver_ip = '127.0.0.1'  # Do not accept connections anywhere but localhost\n    SocksProxy.monkeyPatch(*config.tor_proxy.split(\":\"))\n    config.disable_udp = True\nelif config.bind:\n    bind = config.bind\n    if \":\" not in config.bind:\n        bind += \":0\"\n    from util import helper\n    helper.socketBindMonkeyPatch(*bind.split(\":\"))\n\n# -- Actions --\n\n\n@PluginManager.acceptPlugins\nclass Actions(object):\n    def call(self, function_name, kwargs):\n        logging.info(\"Version: %s r%s, Python %s, Gevent: %s\" % (config.version, config.rev, sys.version, gevent.__version__))\n\n        func = getattr(self, function_name, None)\n        back = func(**kwargs)\n        if back:\n            print(back)\n\n    # Default action: Start serving UiServer and FileServer\n    def main(self):\n        global ui_server, file_server\n        from File import FileServer\n        from Ui import UiServer\n        logging.info(\"Creating FileServer....\")\n        file_server = FileServer()\n        logging.info(\"Creating UiServer....\")\n        ui_server = UiServer()\n        file_server.ui_server = ui_server\n\n        for startup_error in startup_errors:\n            logging.error(\"Startup error: %s\" % startup_error)\n\n        logging.info(\"Removing old SSL certs...\")\n        from Crypt import CryptConnection\n        CryptConnection.manager.removeCerts()\n\n        logging.info(\"Starting servers....\")\n        gevent.joinall([gevent.spawn(ui_server.start), gevent.spawn(file_server.start)])\n        logging.info(\"All server stopped\")\n\n    # Site commands\n\n    def siteCreate(self, use_master_seed=True):\n        logging.info(\"Generating new privatekey (use_master_seed: %s)...\" % config.use_master_seed)\n        from Crypt import CryptBitcoin\n        if use_master_seed:\n            from User import UserManager\n            user = UserManager.user_manager.get()\n            if not user:\n                user = UserManager.user_manager.create()\n            address, address_index, site_data = user.getNewSiteData()\n            privatekey = site_data[\"privatekey\"]\n            logging.info(\"Generated using master seed from users.json, site index: %s\" % address_index)\n        else:\n            privatekey = CryptBitcoin.newPrivatekey()\n            address = CryptBitcoin.privatekeyToAddress(privatekey)\n        logging.info(\"----------------------------------------------------------------------\")\n        logging.info(\"Site private key: %s\" % privatekey)\n        logging.info(\"                  !!! ^ Save it now, required to modify the site ^ !!!\")\n        logging.info(\"Site address:     %s\" % address)\n        logging.info(\"----------------------------------------------------------------------\")\n\n        while True and not config.batch and not use_master_seed:\n            if input(\"? Have you secured your private key? (yes, no) > \").lower() == \"yes\":\n                break\n            else:\n                logging.info(\"Please, secure it now, you going to need it to modify your site!\")\n\n        logging.info(\"Creating directory structure...\")\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        os.mkdir(\"%s/%s\" % (config.data_dir, address))\n        open(\"%s/%s/index.html\" % (config.data_dir, address), \"w\").write(\"Hello %s!\" % address)\n\n        logging.info(\"Creating content.json...\")\n        site = Site(address)\n        extend = {\"postmessage_nonce_security\": True}\n        if use_master_seed:\n            extend[\"address_index\"] = address_index\n\n        site.content_manager.sign(privatekey=privatekey, extend=extend)\n        site.settings[\"own\"] = True\n        site.saveSettings()\n\n        logging.info(\"Site created!\")\n\n    def siteSign(self, address, privatekey=None, inner_path=\"content.json\", publish=False, remove_missing_optional=False):\n        from Site.Site import Site\n        from Site import SiteManager\n        from Debug import Debug\n        SiteManager.site_manager.load()\n        logging.info(\"Signing site: %s...\" % address)\n        site = Site(address, allow_create=False)\n\n        if not privatekey:  # If no privatekey defined\n            from User import UserManager\n            user = UserManager.user_manager.get()\n            if user:\n                site_data = user.getSiteData(address)\n                privatekey = site_data.get(\"privatekey\")\n            else:\n                privatekey = None\n            if not privatekey:\n                # Not found in users.json, ask from console\n                import getpass\n                privatekey = getpass.getpass(\"Private key (input hidden):\")\n        try:\n            succ = site.content_manager.sign(\n                inner_path=inner_path, privatekey=privatekey,\n                update_changed_files=True, remove_missing_optional=remove_missing_optional\n            )\n        except Exception as err:\n            logging.error(\"Sign error: %s\" % Debug.formatException(err))\n            succ = False\n        if succ and publish:\n            self.sitePublish(address, inner_path=inner_path)\n\n    def siteVerify(self, address):\n        import time\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        s = time.time()\n        logging.info(\"Verifing site: %s...\" % address)\n        site = Site(address)\n        bad_files = []\n\n        for content_inner_path in site.content_manager.contents:\n            s = time.time()\n            logging.info(\"Verifing %s signature...\" % content_inner_path)\n            err = None\n            try:\n                file_correct = site.content_manager.verifyFile(\n                    content_inner_path, site.storage.open(content_inner_path, \"rb\"), ignore_same=False\n                )\n            except Exception as err:\n                file_correct = False\n\n            if file_correct is True:\n                logging.info(\"[OK] %s (Done in %.3fs)\" % (content_inner_path, time.time() - s))\n            else:\n                logging.error(\"[ERROR] %s: invalid file: %s!\" % (content_inner_path, err))\n                input(\"Continue?\")\n                bad_files += content_inner_path\n\n        logging.info(\"Verifying site files...\")\n        bad_files += site.storage.verifyFiles()[\"bad_files\"]\n        if not bad_files:\n            logging.info(\"[OK] All file sha512sum matches! (%.3fs)\" % (time.time() - s))\n        else:\n            logging.error(\"[ERROR] Error during verifying site files!\")\n\n    def dbRebuild(self, address):\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        logging.info(\"Rebuilding site sql cache: %s...\" % address)\n        site = SiteManager.site_manager.get(address)\n        s = time.time()\n        try:\n            site.storage.rebuildDb()\n            logging.info(\"Done in %.3fs\" % (time.time() - s))\n        except Exception as err:\n            logging.error(err)\n\n    def dbQuery(self, address, query):\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        import json\n        site = Site(address)\n        result = []\n        for row in site.storage.query(query):\n            result.append(dict(row))\n        print(json.dumps(result, indent=4))\n\n    def siteAnnounce(self, address):\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        logging.info(\"Opening a simple connection server\")\n        global file_server\n        from File import FileServer\n        file_server = FileServer(\"127.0.0.1\", 1234)\n        file_server.start()\n\n        logging.info(\"Announcing site %s to tracker...\" % address)\n        site = Site(address)\n\n        s = time.time()\n        site.announce()\n        print(\"Response time: %.3fs\" % (time.time() - s))\n        print(site.peers)\n\n    def siteDownload(self, address):\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        logging.info(\"Opening a simple connection server\")\n        global file_server\n        from File import FileServer\n        file_server = FileServer(\"127.0.0.1\", 1234)\n        file_server_thread = gevent.spawn(file_server.start, check_sites=False)\n\n        site = Site(address)\n\n        on_completed = gevent.event.AsyncResult()\n\n        def onComplete(evt):\n            evt.set(True)\n\n        site.onComplete.once(lambda: onComplete(on_completed))\n        print(\"Announcing...\")\n        site.announce()\n\n        s = time.time()\n        print(\"Downloading...\")\n        site.downloadContent(\"content.json\", check_modifications=True)\n\n        print(\"Downloaded in %.3fs\" % (time.time()-s))\n\n    def siteNeedFile(self, address, inner_path):\n        from Site.Site import Site\n        from Site import SiteManager\n        SiteManager.site_manager.load()\n\n        def checker():\n            while 1:\n                s = time.time()\n                time.sleep(1)\n                print(\"Switch time:\", time.time() - s)\n        gevent.spawn(checker)\n\n        logging.info(\"Opening a simple connection server\")\n        global file_server\n        from File import FileServer\n        file_server = FileServer(\"127.0.0.1\", 1234)\n        file_server_thread = gevent.spawn(file_server.start, check_sites=False)\n\n        site = Site(address)\n        site.announce()\n        print(site.needFile(inner_path, update=True))\n\n    def siteCmd(self, address, cmd, parameters):\n        import json\n        from Site import SiteManager\n\n        site = SiteManager.site_manager.get(address)\n\n        if not site:\n            logging.error(\"Site not found: %s\" % address)\n            return None\n\n        ws = self.getWebsocket(site)\n\n        ws.send(json.dumps({\"cmd\": cmd, \"params\": parameters, \"id\": 1}))\n        res_raw = ws.recv()\n\n        try:\n            res = json.loads(res_raw)\n        except Exception as err:\n            return {\"error\": \"Invalid result: %s\" % err, \"res_raw\": res_raw}\n\n        if \"result\" in res:\n            return res[\"result\"]\n        else:\n            return res\n\n    def getWebsocket(self, site):\n        import websocket\n\n        ws_address = \"ws://%s:%s/Websocket?wrapper_key=%s\" % (config.ui_ip, config.ui_port, site.settings[\"wrapper_key\"])\n        logging.info(\"Connecting to %s\" % ws_address)\n        ws = websocket.create_connection(ws_address)\n        return ws\n\n    def sitePublish(self, address, peer_ip=None, peer_port=15441, inner_path=\"content.json\"):\n        global file_server\n        from Site.Site import Site\n        from Site import SiteManager\n        from File import FileServer  # We need fileserver to handle incoming file requests\n        from Peer import Peer\n        file_server = FileServer()\n        site = SiteManager.site_manager.get(address)\n        logging.info(\"Loading site...\")\n        site.settings[\"serving\"] = True  # Serving the site even if its disabled\n\n        try:\n            ws = self.getWebsocket(site)\n            logging.info(\"Sending siteReload\")\n            self.siteCmd(address, \"siteReload\", inner_path)\n\n            logging.info(\"Sending sitePublish\")\n            self.siteCmd(address, \"sitePublish\", {\"inner_path\": inner_path, \"sign\": False})\n            logging.info(\"Done.\")\n\n        except Exception as err:\n            logging.info(\"Can't connect to local websocket client: %s\" % err)\n            logging.info(\"Creating FileServer....\")\n            file_server_thread = gevent.spawn(file_server.start, check_sites=False)  # Dont check every site integrity\n            time.sleep(0.001)\n\n            # Started fileserver\n            file_server.portCheck()\n            if peer_ip:  # Announce ip specificed\n                site.addPeer(peer_ip, peer_port)\n            else:  # Just ask the tracker\n                logging.info(\"Gathering peers from tracker\")\n                site.announce()  # Gather peers\n            published = site.publish(5, inner_path)  # Push to peers\n            if published > 0:\n                time.sleep(3)\n                logging.info(\"Serving files (max 60s)...\")\n                gevent.joinall([file_server_thread], timeout=60)\n                logging.info(\"Done.\")\n            else:\n                logging.info(\"No peers found, sitePublish command only works if you already have visitors serving your site\")\n\n    # Crypto commands\n    def cryptPrivatekeyToAddress(self, privatekey=None):\n        from Crypt import CryptBitcoin\n        if not privatekey:  # If no privatekey in args then ask it now\n            import getpass\n            privatekey = getpass.getpass(\"Private key (input hidden):\")\n\n        print(CryptBitcoin.privatekeyToAddress(privatekey))\n\n    def cryptSign(self, message, privatekey):\n        from Crypt import CryptBitcoin\n        print(CryptBitcoin.sign(message, privatekey))\n\n    def cryptVerify(self, message, sign, address):\n        from Crypt import CryptBitcoin\n        print(CryptBitcoin.verify(message, address, sign))\n\n    def cryptGetPrivatekey(self, master_seed, site_address_index=None):\n        from Crypt import CryptBitcoin\n        if len(master_seed) != 64:\n            logging.error(\"Error: Invalid master seed length: %s (required: 64)\" % len(master_seed))\n            return False\n        privatekey = CryptBitcoin.hdPrivatekey(master_seed, site_address_index)\n        print(\"Requested private key: %s\" % privatekey)\n\n    # Peer\n    def peerPing(self, peer_ip, peer_port=None):\n        if not peer_port:\n            peer_port = 15441\n        logging.info(\"Opening a simple connection server\")\n        global file_server\n        from Connection import ConnectionServer\n        file_server = ConnectionServer(\"127.0.0.1\", 1234)\n        file_server.start(check_connections=False)\n        from Crypt import CryptConnection\n        CryptConnection.manager.loadCerts()\n\n        from Peer import Peer\n        logging.info(\"Pinging 5 times peer: %s:%s...\" % (peer_ip, int(peer_port)))\n        s = time.time()\n        peer = Peer(peer_ip, peer_port)\n        peer.connect()\n\n        if not peer.connection:\n            print(\"Error: Can't connect to peer (connection error: %s)\" % peer.connection_error)\n            return False\n        if \"shared_ciphers\" in dir(peer.connection.sock):\n            print(\"Shared ciphers:\", peer.connection.sock.shared_ciphers())\n        if \"cipher\" in dir(peer.connection.sock):\n            print(\"Cipher:\", peer.connection.sock.cipher()[0])\n        if \"version\" in dir(peer.connection.sock):\n            print(\"TLS version:\", peer.connection.sock.version())\n        print(\"Connection time: %.3fs  (connection error: %s)\" % (time.time() - s, peer.connection_error))\n\n        for i in range(5):\n            ping_delay = peer.ping()\n            print(\"Response time: %.3fs\" % ping_delay)\n            time.sleep(1)\n        peer.remove()\n        print(\"Reconnect test...\")\n        peer = Peer(peer_ip, peer_port)\n        for i in range(5):\n            ping_delay = peer.ping()\n            print(\"Response time: %.3fs\" % ping_delay)\n            time.sleep(1)\n\n    def peerGetFile(self, peer_ip, peer_port, site, filename, benchmark=False):\n        logging.info(\"Opening a simple connection server\")\n        global file_server\n        from Connection import ConnectionServer\n        file_server = ConnectionServer(\"127.0.0.1\", 1234)\n        file_server.start(check_connections=False)\n        from Crypt import CryptConnection\n        CryptConnection.manager.loadCerts()\n\n        from Peer import Peer\n        logging.info(\"Getting %s/%s from peer: %s:%s...\" % (site, filename, peer_ip, peer_port))\n        peer = Peer(peer_ip, peer_port)\n        s = time.time()\n        if benchmark:\n            for i in range(10):\n                peer.getFile(site, filename),\n            print(\"Response time: %.3fs\" % (time.time() - s))\n            input(\"Check memory\")\n        else:\n            print(peer.getFile(site, filename).read())\n\n    def peerCmd(self, peer_ip, peer_port, cmd, parameters):\n        logging.info(\"Opening a simple connection server\")\n        global file_server\n        from Connection import ConnectionServer\n        file_server = ConnectionServer()\n        file_server.start(check_connections=False)\n        from Crypt import CryptConnection\n        CryptConnection.manager.loadCerts()\n\n        from Peer import Peer\n        peer = Peer(peer_ip, peer_port)\n\n        import json\n        if parameters:\n            parameters = json.loads(parameters.replace(\"'\", '\"'))\n        else:\n            parameters = {}\n        try:\n            res = peer.request(cmd, parameters)\n            print(json.dumps(res, indent=2, ensure_ascii=False))\n        except Exception as err:\n            print(\"Unknown response (%s): %s\" % (err, res))\n\n    def getConfig(self):\n        import json\n        print(json.dumps(config.getServerInfo(), indent=2, ensure_ascii=False))\n\n    def test(self, test_name, *args, **kwargs):\n        import types\n        def funcToName(func_name):\n            test_name = func_name.replace(\"test\", \"\")\n            return test_name[0].lower() + test_name[1:]\n\n        test_names = [funcToName(name) for name in dir(self) if name.startswith(\"test\") and name != \"test\"]\n        if not test_name:\n            # No test specificed, list tests\n            print(\"\\nNo test specified, possible tests:\")\n            for test_name in test_names:\n                func_name = \"test\" + test_name[0].upper() + test_name[1:]\n                func = getattr(self, func_name)\n                if func.__doc__:\n                    print(\"- %s: %s\" % (test_name, func.__doc__.strip()))\n                else:\n                    print(\"- %s\" % test_name)\n            return None\n\n        # Run tests\n        func_name = \"test\" + test_name[0].upper() + test_name[1:]\n        if hasattr(self, func_name):\n            func = getattr(self, func_name)\n            print(\"- Running test: %s\" % test_name, end=\"\")\n            s = time.time()\n            ret = func(*args, **kwargs)\n            if type(ret) is types.GeneratorType:\n                for progress in ret:\n                    print(progress, end=\"\")\n                    sys.stdout.flush()\n            print(\"\\n* Test %s done in %.3fs\" % (test_name, time.time() - s))\n        else:\n            print(\"Unknown test: %r (choose from: %s)\" % (\n                test_name, test_names\n            ))\n\n\nactions = Actions()\n# Starts here when running zeronet.py\n\n\ndef start():\n    # Call function\n    action_kwargs = config.getActionArguments()\n    actions.call(config.action, action_kwargs)\n"
  },
  {
    "path": "src/util/Cached.py",
    "content": "import time\n\n\nclass Cached(object):\n    def __init__(self, timeout):\n        self.cache_db = {}\n        self.timeout = timeout\n\n    def __call__(self, func):\n        def wrapper(*args, **kwargs):\n            key = \"%s %s\" % (args, kwargs)\n            cached_value = None\n            cache_hit = False\n            if key in self.cache_db:\n                cache_hit = True\n                cached_value, time_cached_end = self.cache_db[key]\n                if time.time() > time_cached_end:\n                    self.cleanupExpired()\n                    cached_value = None\n                    cache_hit = False\n\n            if cache_hit:\n                return cached_value\n            else:\n                cached_value = func(*args, **kwargs)\n                time_cached_end = time.time() + self.timeout\n                self.cache_db[key] = (cached_value, time_cached_end)\n                return cached_value\n\n        wrapper.emptyCache = self.emptyCache\n\n        return wrapper\n\n    def cleanupExpired(self):\n        for key in list(self.cache_db.keys()):\n            cached_value, time_cached_end = self.cache_db[key]\n            if time.time() > time_cached_end:\n                del(self.cache_db[key])\n\n    def emptyCache(self):\n        num = len(self.cache_db)\n        self.cache_db.clear()\n        return num\n\n\nif __name__ == \"__main__\":\n    from gevent import monkey\n    monkey.patch_all()\n\n    @Cached(timeout=2)\n    def calcAdd(a, b):\n        print(\"CalcAdd\", a, b)\n        return a + b\n\n    @Cached(timeout=1)\n    def calcMultiply(a, b):\n        print(\"calcMultiply\", a, b)\n        return a * b\n\n    for i in range(5):\n        print(\"---\")\n        print(\"Emptied\", calcAdd.emptyCache())\n        assert calcAdd(1, 2) == 3\n        print(\"Emptied\", calcAdd.emptyCache())\n        assert calcAdd(1, 2) == 3\n        assert calcAdd(2, 3) == 5\n        assert calcMultiply(2, 3) == 6\n        time.sleep(1)\n"
  },
  {
    "path": "src/util/Diff.py",
    "content": "import io\n\nimport difflib\n\n\ndef sumLen(lines):\n    return sum(map(len, lines))\n\n\ndef diff(old, new, limit=False):\n    matcher = difflib.SequenceMatcher(None, old, new)\n    actions = []\n    size = 0\n    for tag, old_from, old_to, new_from, new_to in matcher.get_opcodes():\n        if tag == \"insert\":\n            new_line = new[new_from:new_to]\n            actions.append((\"+\", new_line))\n            size += sum(map(len, new_line))\n        elif tag == \"equal\":\n            actions.append((\"=\", sumLen(old[old_from:old_to])))\n        elif tag == \"delete\":\n            actions.append((\"-\", sumLen(old[old_from:old_to])))\n        elif tag == \"replace\":\n            actions.append((\"-\", sumLen(old[old_from:old_to])))\n            new_lines = new[new_from:new_to]\n            actions.append((\"+\", new_lines))\n            size += sumLen(new_lines)\n        if limit and size > limit:\n            return False\n    return actions\n\n\ndef patch(old_f, actions):\n    new_f = io.BytesIO()\n    for action, param in actions:\n        if type(action) is bytes:\n            action = action.decode()\n        if action == \"=\":  # Same lines\n            new_f.write(old_f.read(param))\n        elif action == \"-\":  # Delete lines\n            old_f.seek(param, 1)  # Seek from current position\n            continue\n        elif action == \"+\":  # Add lines\n            for add_line in param:\n                new_f.write(add_line)\n        else:\n            raise \"Unknown action: %s\" % action\n    return new_f\n"
  },
  {
    "path": "src/util/Electrum.py",
    "content": "import hashlib\nimport struct\n\n\n# Electrum, the heck?!\n\ndef bchr(i):\n    return struct.pack(\"B\", i)\n\ndef encode(val, base, minlen=0):\n    base, minlen = int(base), int(minlen)\n    code_string = b\"\".join([bchr(x) for x in range(256)])\n    result = b\"\"\n    while val > 0:\n        index = val % base\n        result = code_string[index:index + 1] + result\n        val //= base\n    return code_string[0:1] * max(minlen - len(result), 0) + result\n\ndef insane_int(x):\n    x = int(x)\n    if x < 253:\n        return bchr(x)\n    elif x < 65536:\n        return bchr(253) + encode(x, 256, 2)[::-1]\n    elif x < 4294967296:\n        return bchr(254) + encode(x, 256, 4)[::-1]\n    else:\n        return bchr(255) + encode(x, 256, 8)[::-1]\n\n\ndef magic(message):\n    return b\"\\x18Bitcoin Signed Message:\\n\" + insane_int(len(message)) + message\n\ndef format(message):\n    return hashlib.sha256(magic(message)).digest()\n\ndef dbl_format(message):\n    return hashlib.sha256(format(message)).digest()\n"
  },
  {
    "path": "src/util/Event.py",
    "content": "# Based on http://stackoverflow.com/a/2022629\n\n\nclass Event(list):\n\n    def __call__(self, *args, **kwargs):\n        for f in self[:]:\n            if \"once\" in dir(f) and f in self:\n                self.remove(f)\n            f(*args, **kwargs)\n\n    def __repr__(self):\n        return \"Event(%s)\" % list.__repr__(self)\n\n    def once(self, func, name=None):\n        func.once = True\n        func.name = None\n        if name:  # Dont function with same name twice\n            names = [f.name for f in self if \"once\" in dir(f)]\n            if name not in names:\n                func.name = name\n                self.append(func)\n        else:\n            self.append(func)\n        return self\n\n\nif __name__ == \"__main__\":\n    def testBenchmark():\n        def say(pre, text):\n            print(\"%s Say: %s\" % (pre, text))\n\n        import time\n        s = time.time()\n        on_changed = Event()\n        for i in range(1000):\n            on_changed.once(lambda pre: say(pre, \"once\"), \"once\")\n        print(\"Created 1000 once in %.3fs\" % (time.time() - s))\n        on_changed(\"#1\")\n\n    def testUsage():\n        def say(pre, text):\n            print(\"%s Say: %s\" % (pre, text))\n\n        on_changed = Event()\n        on_changed.once(lambda pre: say(pre, \"once\"))\n        on_changed.once(lambda pre: say(pre, \"once\"))\n        on_changed.once(lambda pre: say(pre, \"namedonce\"), \"namedonce\")\n        on_changed.once(lambda pre: say(pre, \"namedonce\"), \"namedonce\")\n        on_changed.append(lambda pre: say(pre, \"always\"))\n        on_changed(\"#1\")\n        on_changed(\"#2\")\n        on_changed(\"#3\")\n\n    testBenchmark()\n"
  },
  {
    "path": "src/util/Flag.py",
    "content": "from collections import defaultdict\n\n\nclass Flag(object):\n    def __init__(self):\n        self.valid_flags = set([\n            \"admin\",  # Only allowed to run sites with ADMIN permission\n            \"async_run\",  # Action will be ran async with gevent.spawn\n            \"no_multiuser\"  # Action disabled if Multiuser plugin running in open proxy mode\n        ])\n        self.db = defaultdict(set)\n\n    def __getattr__(self, key):\n        def func(f):\n            if key not in self.valid_flags:\n                raise Exception(\"Invalid flag: %s (valid: %s)\" % (key, self.valid_flags))\n            self.db[f.__name__].add(key)\n            return f\n        return func\n\n\nflag = Flag()\n"
  },
  {
    "path": "src/util/GreenletManager.py",
    "content": "import gevent\nfrom Debug import Debug\n\n\nclass GreenletManager:\n    def __init__(self):\n        self.greenlets = set()\n\n    def spawnLater(self, *args, **kwargs):\n        greenlet = gevent.spawn_later(*args, **kwargs)\n        greenlet.link(lambda greenlet: self.greenlets.remove(greenlet))\n        self.greenlets.add(greenlet)\n        return greenlet\n\n    def spawn(self, *args, **kwargs):\n        greenlet = gevent.spawn(*args, **kwargs)\n        greenlet.link(lambda greenlet: self.greenlets.remove(greenlet))\n        self.greenlets.add(greenlet)\n        return greenlet\n\n    def stopGreenlets(self, reason=\"Stopping all greenlets\"):\n        num = len(self.greenlets)\n        gevent.killall(list(self.greenlets), Debug.createNotifyType(reason), block=False)\n        return num\n"
  },
  {
    "path": "src/util/Msgpack.py",
    "content": "import os\nimport struct\nimport io\n\nimport msgpack\nimport msgpack.fallback\n\n\ndef msgpackHeader(size):\n    if size <= 2 ** 8 - 1:\n        return b\"\\xc4\" + struct.pack(\"B\", size)\n    elif size <= 2 ** 16 - 1:\n        return b\"\\xc5\" + struct.pack(\">H\", size)\n    elif size <= 2 ** 32 - 1:\n        return b\"\\xc6\" + struct.pack(\">I\", size)\n    else:\n        raise Exception(\"huge binary string\")\n\n\ndef stream(data, writer):\n    packer = msgpack.Packer(use_bin_type=True)\n    writer(packer.pack_map_header(len(data)))\n    for key, val in data.items():\n        writer(packer.pack(key))\n        if isinstance(val, io.IOBase):  # File obj\n            max_size = os.fstat(val.fileno()).st_size - val.tell()\n            size = min(max_size, val.read_bytes)\n            bytes_left = size\n            writer(msgpackHeader(size))\n            buff = 1024 * 64\n            while 1:\n                writer(val.read(min(bytes_left, buff)))\n                bytes_left = bytes_left - buff\n                if bytes_left <= 0:\n                    break\n        else:  # Simple\n            writer(packer.pack(val))\n    return size\n\n\nclass FilePart(object):\n    __slots__ = (\"file\", \"read_bytes\", \"__class__\")\n\n    def __init__(self, *args, **kwargs):\n        self.file = open(*args, **kwargs)\n        self.__enter__ == self.file.__enter__\n\n    def __getattr__(self, attr):\n        return getattr(self.file, attr)\n\n    def __enter__(self, *args, **kwargs):\n        return self.file.__enter__(*args, **kwargs)\n\n    def __exit__(self, *args, **kwargs):\n        return self.file.__exit__(*args, **kwargs)\n\n\n# Don't try to decode the value of these fields as utf8\nbin_value_keys = (\"hashfield_raw\", \"peers\", \"peers_ipv6\", \"peers_onion\", \"body\", \"sites\", \"bin\")\n\n\ndef objectDecoderHook(obj):\n    global bin_value_keys\n    back = {}\n    for key, val in obj:\n        if type(key) is bytes:\n            key = key.decode(\"utf8\")\n        if key in bin_value_keys or type(val) is not bytes or len(key) >= 64:\n            back[key] = val\n        else:\n            back[key] = val.decode(\"utf8\")\n    return back\n\n\ndef getUnpacker(fallback=False, decode=True):\n    if fallback:  # Pure Python\n        unpacker = msgpack.fallback.Unpacker\n    else:\n        unpacker = msgpack.Unpacker\n\n    extra_kwargs = {\"max_buffer_size\": 5 * 1024 * 1024}\n    if msgpack.version[0] >= 1:\n        extra_kwargs[\"strict_map_key\"] = False\n\n    if decode:  # Workaround for backward compatibility: Try to decode bin to str\n        unpacker = unpacker(raw=True, object_pairs_hook=objectDecoderHook, **extra_kwargs)\n    else:\n        unpacker = unpacker(raw=False, **extra_kwargs)\n\n    return unpacker\n\n\ndef pack(data, use_bin_type=True):\n    return msgpack.packb(data, use_bin_type=use_bin_type)\n\n\ndef unpack(data, decode=True):\n    unpacker = getUnpacker(decode=decode)\n    unpacker.feed(data)\n    return next(unpacker)\n\n"
  },
  {
    "path": "src/util/Noparallel.py",
    "content": "import gevent\nimport time\nfrom gevent.event import AsyncResult\n\nfrom . import ThreadPool\n\n\nclass Noparallel:  # Only allow function running once in same time\n\n    def __init__(self, blocking=True, ignore_args=False, ignore_class=False, queue=False):\n        self.threads = {}\n        self.blocking = blocking  # Blocking: Acts like normal function else thread returned\n        self.queue = queue  # Execute again when blocking is done\n        self.queued = False\n        self.ignore_args = ignore_args  # Block does not depend on function call arguments\n        self.ignore_class = ignore_class  # Block does not depeds on class instance\n\n    def __call__(self, func):\n        def wrapper(*args, **kwargs):\n            if not ThreadPool.isMainThread():\n                return ThreadPool.main_loop.call(wrapper, *args, **kwargs)\n\n            if self.ignore_class:\n                key = func  # Unique key only by function and class object\n            elif self.ignore_args:\n                key = (func, args[0])  # Unique key only by function and class object\n            else:\n                key = (func, tuple(args), str(kwargs))  # Unique key for function including parameters\n            if key in self.threads:  # Thread already running (if using blocking mode)\n                if self.queue:\n                    self.queued = True\n                thread = self.threads[key]\n                if self.blocking:\n                    if self.queued:\n                        res = thread.get()  # Blocking until its finished\n                        if key in self.threads:\n                            return self.threads[key].get()  # Queue finished since started running\n                        self.queued = False\n                        return wrapper(*args, **kwargs)  # Run again after the end\n                    else:\n                        return thread.get()  # Return the value\n\n                else:  # No blocking\n                    if thread.ready():  # Its finished, create a new\n                        thread = gevent.spawn(func, *args, **kwargs)\n                        self.threads[key] = thread\n                        return thread\n                    else:  # Still running\n                        return thread\n            else:  # Thread not running\n                if self.blocking:  # Wait for finish\n                    asyncres = AsyncResult()\n                    self.threads[key] = asyncres\n                    try:\n                        res = func(*args, **kwargs)\n                        asyncres.set(res)\n                        self.cleanup(key, asyncres)\n                        return res\n                    except Exception as err:\n                        asyncres.set_exception(err)\n                        self.cleanup(key, asyncres)\n                        raise(err)\n                else:  # No blocking just return the thread\n                    thread = gevent.spawn(func, *args, **kwargs)  # Spawning new thread\n                    thread.link(lambda thread: self.cleanup(key, thread))\n                    self.threads[key] = thread\n                    return thread\n        wrapper.__name__ = func.__name__\n\n        return wrapper\n\n    # Cleanup finished threads\n    def cleanup(self, key, thread):\n        if key in self.threads:\n            del(self.threads[key])\n\n\nif __name__ == \"__main__\":\n\n\n    class Test():\n\n        @Noparallel()\n        def count(self, num=5):\n            for i in range(num):\n                print(self, i)\n                time.sleep(1)\n            return \"%s return:%s\" % (self, i)\n\n    class TestNoblock():\n\n        @Noparallel(blocking=False)\n        def count(self, num=5):\n            for i in range(num):\n                print(self, i)\n                time.sleep(1)\n            return \"%s return:%s\" % (self, i)\n\n    def testBlocking():\n        test = Test()\n        test2 = Test()\n        print(\"Counting...\")\n        print(\"Creating class1/thread1\")\n        thread1 = gevent.spawn(test.count)\n        print(\"Creating class1/thread2 (ignored)\")\n        thread2 = gevent.spawn(test.count)\n        print(\"Creating class2/thread3\")\n        thread3 = gevent.spawn(test2.count)\n\n        print(\"Joining class1/thread1\")\n        thread1.join()\n        print(\"Joining class1/thread2\")\n        thread2.join()\n        print(\"Joining class2/thread3\")\n        thread3.join()\n\n        print(\"Creating class1/thread4 (its finished, allowed again)\")\n        thread4 = gevent.spawn(test.count)\n        print(\"Joining thread4\")\n        thread4.join()\n\n        print(thread1.value, thread2.value, thread3.value, thread4.value)\n        print(\"Done.\")\n\n    def testNoblocking():\n        test = TestNoblock()\n        test2 = TestNoblock()\n        print(\"Creating class1/thread1\")\n        thread1 = test.count()\n        print(\"Creating class1/thread2 (ignored)\")\n        thread2 = test.count()\n        print(\"Creating class2/thread3\")\n        thread3 = test2.count()\n        print(\"Joining class1/thread1\")\n        thread1.join()\n        print(\"Joining class1/thread2\")\n        thread2.join()\n        print(\"Joining class2/thread3\")\n        thread3.join()\n\n        print(\"Creating class1/thread4 (its finished, allowed again)\")\n        thread4 = test.count()\n        print(\"Joining thread4\")\n        thread4.join()\n\n        print(thread1.value, thread2.value, thread3.value, thread4.value)\n        print(\"Done.\")\n\n    def testBenchmark():\n        import time\n\n        def printThreadNum():\n            import gc\n            from greenlet import greenlet\n            objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)]\n            print(\"Greenlets: %s\" % len(objs))\n\n        printThreadNum()\n        test = TestNoblock()\n        s = time.time()\n        for i in range(3):\n            gevent.spawn(test.count, i + 1)\n        print(\"Created in %.3fs\" % (time.time() - s))\n        printThreadNum()\n        time.sleep(5)\n\n    def testException():\n        import time\n        @Noparallel(blocking=True, queue=True)\n        def count(self, num=5):\n            s = time.time()\n            # raise Exception(\"err\")\n            for i in range(num):\n                print(self, i)\n                time.sleep(1)\n            return \"%s return:%s\" % (s, i)\n        def caller():\n            try:\n                print(\"Ret:\", count(5))\n            except Exception as err:\n                print(\"Raised:\", repr(err))\n\n        gevent.joinall([\n            gevent.spawn(caller),\n            gevent.spawn(caller),\n            gevent.spawn(caller),\n            gevent.spawn(caller)\n        ])\n\n\n    from gevent import monkey\n    monkey.patch_all()\n\n    testException()\n\n    \"\"\"\n    testBenchmark()\n    print(\"Testing blocking mode...\")\n    testBlocking()\n    print(\"Testing noblocking mode...\")\n    testNoblocking()\n    \"\"\"\n"
  },
  {
    "path": "src/util/OpensslFindPatch.py",
    "content": "import logging\nimport os\nimport sys\nimport ctypes.util\n\nfrom Config import config\n\nfind_library_original = ctypes.util.find_library\n\n\ndef getOpensslPath():\n    if config.openssl_lib_file:\n        return config.openssl_lib_file\n\n    if sys.platform.startswith(\"win\"):\n        lib_paths = [\n            os.path.join(os.getcwd(), \"tools/openssl/libeay32.dll\"),  # ZeroBundle Windows\n            os.path.join(os.path.dirname(sys.executable), \"DLLs/libcrypto-1_1-x64.dll\"),\n            os.path.join(os.path.dirname(sys.executable), \"DLLs/libcrypto-1_1.dll\")\n        ]\n    elif sys.platform == \"cygwin\":\n        lib_paths = [\"/bin/cygcrypto-1.0.0.dll\"]\n    else:\n        lib_paths = [\n            \"../runtime/lib/libcrypto.so.1.1\",  # ZeroBundle Linux\n            \"../../Frameworks/libcrypto.1.1.dylib\",  # ZeroBundle macOS\n            \"/opt/lib/libcrypto.so.1.0.0\",  # For optware and entware\n            \"/usr/local/ssl/lib/libcrypto.so\"\n        ]\n\n    for lib_path in lib_paths:\n        if os.path.isfile(lib_path):\n            return lib_path\n\n    if \"ANDROID_APP_PATH\" in os.environ:\n        try:\n            lib_dir = os.environ[\"ANDROID_APP_PATH\"] + \"/../../lib\"\n            return [lib for lib in os.listdir(lib_dir) if \"crypto\" in lib][0]\n        except Exception as err:\n            logging.debug(\"OpenSSL lib not found in: %s (%s)\" % (lib_dir, err))\n\n    if \"LD_LIBRARY_PATH\" in os.environ:\n        lib_dir_paths = os.environ[\"LD_LIBRARY_PATH\"].split(\":\")\n        for path in lib_dir_paths:\n            try:\n                return [lib for lib in os.listdir(path) if \"libcrypto.so\" in lib][0]\n            except Exception as err:\n                logging.debug(\"OpenSSL lib not found in: %s (%s)\" % (path, err))\n\n    lib_path = (\n        find_library_original('ssl.so') or find_library_original('ssl') or\n        find_library_original('crypto') or find_library_original('libcrypto') or 'libeay32'\n    )\n\n    return lib_path\n\n\ndef patchCtypesOpensslFindLibrary():\n    def findLibraryPatched(name):\n        if name in (\"ssl\", \"crypto\", \"libeay32\"):\n            lib_path = getOpensslPath()\n            return lib_path\n        else:\n            return find_library_original(name)\n\n    ctypes.util.find_library = findLibraryPatched\n\n\npatchCtypesOpensslFindLibrary()\n"
  },
  {
    "path": "src/util/Platform.py",
    "content": "import sys\nimport logging\n\n\ndef setMaxfilesopened(limit):\n    try:\n        if sys.platform == \"win32\":\n            import ctypes\n            dll = None\n            last_err = None\n            for dll_name in [\"msvcr100\", \"msvcr110\", \"msvcr120\"]:\n                try:\n                    dll = getattr(ctypes.cdll, dll_name)\n                    break\n                except OSError as err:\n                    last_err = err\n\n            if not dll:\n                raise last_err\n\n            maxstdio = dll._getmaxstdio()\n            if maxstdio < limit:\n                logging.debug(\"%s: Current maxstdio: %s, changing to %s...\" % (dll, maxstdio, limit))\n                dll._setmaxstdio(limit)\n                return True\n        else:\n            import resource\n            soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)\n            if soft < limit:\n                logging.debug(\"Current RLIMIT_NOFILE: %s (max: %s), changing to %s...\" % (soft, hard, limit))\n                resource.setrlimit(resource.RLIMIT_NOFILE, (limit, hard))\n                return True\n\n    except Exception as err:\n        logging.error(\"Failed to modify max files open limit: %s\" % err)\n        return False\n"
  },
  {
    "path": "src/util/Pooled.py",
    "content": "import gevent.pool\n\n\nclass Pooled(object):\n    def __init__(self, size=100):\n        self.pool = gevent.pool.Pool(size)\n        self.pooler_running = False\n        self.queue = []\n        self.func = None\n\n    def waiter(self, evt, args, kwargs):\n        res = self.func(*args, **kwargs)\n        if type(res) == gevent.event.AsyncResult:\n            evt.set(res.get())\n        else:\n            evt.set(res)\n\n    def pooler(self):\n        while self.queue:\n            evt, args, kwargs = self.queue.pop(0)\n            self.pool.spawn(self.waiter, evt, args, kwargs)\n        self.pooler_running = False\n\n    def __call__(self, func):\n        def wrapper(*args, **kwargs):\n            evt = gevent.event.AsyncResult()\n            self.queue.append((evt, args, kwargs))\n            if not self.pooler_running:\n                self.pooler_running = True\n                gevent.spawn(self.pooler)\n            return evt\n        wrapper.__name__ = func.__name__\n        self.func = func\n\n        return wrapper\n\nif __name__ == \"__main__\":\n    import gevent\n    import gevent.pool\n    import gevent.queue\n    import gevent.event\n    import gevent.monkey\n    import time\n\n    gevent.monkey.patch_all()\n\n    def addTask(inner_path):\n        evt = gevent.event.AsyncResult()\n        gevent.spawn_later(1, lambda: evt.set(True))\n        return evt\n\n    def needFile(inner_path):\n        return addTask(inner_path)\n\n    @Pooled(10)\n    def pooledNeedFile(inner_path):\n        return needFile(inner_path)\n\n    threads = []\n    for i in range(100):\n        threads.append(pooledNeedFile(i))\n\n    s = time.time()\n    gevent.joinall(threads)  # Should take 10 second\n    print(time.time() - s)\n"
  },
  {
    "path": "src/util/QueryJson.py",
    "content": "import json\nimport re\nimport os\n\n\ndef queryFile(file_path, filter_path, filter_key=None, filter_val=None):\n    back = []\n    data = json.load(open(file_path))\n    if filter_path == ['']:\n        return [data]\n    for key in filter_path:  # Get to the point\n        data = data.get(key)\n        if not data:\n            return\n\n    if type(data) == list:\n        for row in data:\n            if filter_val:  # Filter by value\n                if row[filter_key] == filter_val:\n                    back.append(row)\n            else:\n                back.append(row)\n    else:\n        back.append({\"value\": data})\n\n    return back\n\n\n# Find in json files\n# Return: [{u'body': u'Hello Topic 1!!', 'inner_path': '1KRxE1...beEp6', u'added': 1422740732, u'message_id': 1},...]\ndef query(path_pattern, filter):\n    if \"=\" in filter:  # Filter by value\n        filter_path, filter_val = filter.split(\"=\")\n        filter_path = filter_path.split(\".\")\n        filter_key = filter_path.pop()  # Last element is the key\n        filter_val = int(filter_val)\n    else:  # No filter\n        filter_path = filter\n        filter_path = filter_path.split(\".\")\n        filter_key = None\n        filter_val = None\n\n    if \"/*/\" in path_pattern:  # Wildcard search\n        root_dir, file_pattern = path_pattern.replace(\"\\\\\", \"/\").split(\"/*/\")\n    else:  # No wildcard\n        root_dir, file_pattern = re.match(\"(.*)/(.*?)$\", path_pattern.replace(\"\\\\\", \"/\")).groups()\n    for root, dirs, files in os.walk(root_dir, topdown=False):\n        root = root.replace(\"\\\\\", \"/\")\n        inner_path = root.replace(root_dir, \"\").strip(\"/\")\n        for file_name in files:\n            if file_pattern != file_name:\n                continue\n\n            try:\n                res = queryFile(root + \"/\" + file_name, filter_path, filter_key, filter_val)\n                if not res:\n                    continue\n            except Exception:  # Json load error\n                continue\n            for row in res:\n                row[\"inner_path\"] = inner_path\n                yield row\n\n\nif __name__ == \"__main__\":\n    for row in list(query(\"../../data/12Hw8rTgzrNo4DSh2AkqwPRqDyTticwJyH/data/users/*/data.json\", \"\")):\n        print(row)\n"
  },
  {
    "path": "src/util/RateLimit.py",
    "content": "import time\nimport gevent\nimport logging\n\nlog = logging.getLogger(\"RateLimit\")\n\ncalled_db = {}  # Holds events last call time\nqueue_db = {}  # Commands queued to run\n\n# Register event as called\n# Return: None\n\n\ndef called(event, penalty=0):\n    called_db[event] = time.time() + penalty\n\n\n# Check if calling event is allowed\n# Return: True if allowed False if not\ndef isAllowed(event, allowed_again=10):\n    last_called = called_db.get(event)\n    if not last_called:  # Its not called before\n        return True\n    elif time.time() - last_called >= allowed_again:\n        del called_db[event]  # Delete last call time to save memory\n        return True\n    else:\n        return False\n\ndef delayLeft(event, allowed_again=10):\n    last_called = called_db.get(event)\n    if not last_called:  # Its not called before\n        return 0\n    else:\n        return allowed_again - (time.time() - last_called)\n\ndef callQueue(event):\n    func, args, kwargs, thread = queue_db[event]\n    log.debug(\"Calling: %s\" % event)\n    called(event)\n    del queue_db[event]\n    return func(*args, **kwargs)\n\n\n# Rate limit and delay function call if necessary\n# If the function called again within the rate limit interval then previous queued call will be dropped\n# Return: Immediately gevent thread\ndef callAsync(event, allowed_again=10, func=None, *args, **kwargs):\n    if isAllowed(event, allowed_again):  # Not called recently, call it now\n        called(event)\n        # print \"Calling now\"\n        return gevent.spawn(func, *args, **kwargs)\n    else:  # Called recently, schedule it for later\n        time_left = allowed_again - max(0, time.time() - called_db[event])\n        log.debug(\"Added to queue (%.2fs left): %s \" % (time_left, event))\n        if not queue_db.get(event):  # Function call not queued yet\n            thread = gevent.spawn_later(time_left, lambda: callQueue(event))  # Call this function later\n            queue_db[event] = (func, args, kwargs, thread)\n            return thread\n        else:  # Function call already queued, just update the parameters\n            thread = queue_db[event][3]\n            queue_db[event] = (func, args, kwargs, thread)\n            return thread\n\n\n# Rate limit and delay function call if needed\n# Return: Wait for execution/delay then return value\ndef call(event, allowed_again=10, func=None, *args, **kwargs):\n    if isAllowed(event):  # Not called recently, call it now\n        called(event)\n        # print \"Calling now\", allowed_again\n        return func(*args, **kwargs)\n\n    else:  # Called recently, schedule it for later\n        time_left = max(0, allowed_again - (time.time() - called_db[event]))\n        # print \"Time left: %s\" % time_left, args, kwargs\n        log.debug(\"Calling sync (%.2fs left): %s\" % (time_left, event))\n        called(event, time_left)\n        time.sleep(time_left)\n        back = func(*args, **kwargs)\n        called(event)\n        return back\n\n\n# Cleanup expired events every 3 minutes\ndef rateLimitCleanup():\n    while 1:\n        expired = time.time() - 60 * 2  # Cleanup if older than 2 minutes\n        for event in list(called_db.keys()):\n            if called_db[event] < expired:\n                del called_db[event]\n        time.sleep(60 * 3)  # Every 3 minutes\ngevent.spawn(rateLimitCleanup)\n\n\nif __name__ == \"__main__\":\n    from gevent import monkey\n    monkey.patch_all()\n    import random\n\n    def publish(inner_path):\n        print(\"Publishing %s...\" % inner_path)\n        return 1\n\n    def cb(thread):\n        print(\"Value:\", thread.value)\n\n    print(\"Testing async spam requests rate limit to 1/sec...\")\n    for i in range(3000):\n        thread = callAsync(\"publish content.json\", 1, publish, \"content.json %s\" % i)\n        time.sleep(float(random.randint(1, 20)) / 100000)\n    print(thread.link(cb))\n    print(\"Done\")\n\n    time.sleep(2)\n\n    print(\"Testing sync spam requests rate limit to 1/sec...\")\n    for i in range(5):\n        call(\"publish data.json\", 1, publish, \"data.json %s\" % i)\n        time.sleep(float(random.randint(1, 100)) / 100)\n    print(\"Done\")\n\n    print(\"Testing cleanup\")\n    thread = callAsync(\"publish content.json single\", 1, publish, \"content.json single\")\n    print(\"Needs to cleanup:\", called_db, queue_db)\n    print(\"Waiting 3min for cleanup process...\")\n    time.sleep(60 * 3)\n    print(\"Cleaned up:\", called_db, queue_db)\n"
  },
  {
    "path": "src/util/SafeRe.py",
    "content": "import re\n\n\nclass UnsafePatternError(Exception):\n    pass\n\ncached_patterns = {}\n\n\ndef isSafePattern(pattern):\n    if len(pattern) > 255:\n        raise UnsafePatternError(\"Pattern too long: %s characters in %s\" % (len(pattern), pattern))\n\n    unsafe_pattern_match = re.search(r\"[^\\.][\\*\\{\\+]\", pattern)  # Always should be \".\" before \"*{+\" characters to avoid ReDoS\n    if unsafe_pattern_match:\n        raise UnsafePatternError(\"Potentially unsafe part of the pattern: %s in %s\" % (unsafe_pattern_match.group(0), pattern))\n\n    repetitions = re.findall(r\"\\.[\\*\\{\\+]\", pattern)\n    if len(repetitions) >= 10:\n        raise UnsafePatternError(\"More than 10 repetitions of %s in %s\" % (repetitions[0], pattern))\n\n    return True\n\n\ndef match(pattern, *args, **kwargs):\n    cached_pattern = cached_patterns.get(pattern)\n    if cached_pattern:\n        return cached_pattern.match(*args, **kwargs)\n    else:\n        if isSafePattern(pattern):\n            cached_patterns[pattern] = re.compile(pattern)\n            return cached_patterns[pattern].match(*args, **kwargs)\n"
  },
  {
    "path": "src/util/SocksProxy.py",
    "content": "import socket\n\nimport socks\nfrom Config import config\n\ndef create_connection(address, timeout=None, source_address=None):\n    if address in config.ip_local:\n        sock = socket.socket_noproxy(socket.AF_INET, socket.SOCK_STREAM)\n        sock.connect(address)\n    else:\n        sock = socks.socksocket()\n        sock.connect(address)\n    return sock\n\n\n# Dns queries using the proxy\ndef getaddrinfo(*args):\n    return [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]\n\n\ndef monkeyPatch(proxy_ip, proxy_port):\n    socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, proxy_ip, int(proxy_port))\n    socket.socket_noproxy = socket.socket\n    socket.socket = socks.socksocket\n    socket.create_connection = create_connection\n    socket.getaddrinfo = getaddrinfo\n"
  },
  {
    "path": "src/util/ThreadPool.py",
    "content": "import threading\nimport time\nimport queue\n\nimport gevent\nimport gevent.monkey\nimport gevent.threadpool\nimport gevent._threading\n\n\nclass ThreadPool:\n    def __init__(self, max_size, name=None):\n        self.setMaxSize(max_size)\n        if name:\n            self.name = name\n        else:\n            self.name = \"ThreadPool#%s\" % id(self)\n\n    def setMaxSize(self, max_size):\n        self.max_size = max_size\n        if max_size > 0:\n            self.pool = gevent.threadpool.ThreadPool(max_size)\n        else:\n            self.pool = None\n\n    def wrap(self, func):\n        if self.pool is None:\n            return func\n\n        def wrapper(*args, **kwargs):\n            if not isMainThread():  # Call directly if not in main thread\n                return func(*args, **kwargs)\n            res = self.apply(func, args, kwargs)\n            return res\n\n        return wrapper\n\n    def spawn(self, *args, **kwargs):\n        if not isMainThread() and not self.pool._semaphore.ready():\n            # Avoid semaphore error when spawning from other thread and the pool is full\n            return main_loop.call(self.spawn, *args, **kwargs)\n        res = self.pool.spawn(*args, **kwargs)\n        return res\n\n    def apply(self, func, args=(), kwargs={}):\n        t = self.spawn(func, *args, **kwargs)\n        if self.pool._apply_immediately():\n            return main_loop.call(t.get)\n        else:\n            return t.get()\n\n    def kill(self):\n        if self.pool is not None and self.pool.size > 0 and main_loop:\n            main_loop.call(lambda: gevent.spawn(self.pool.kill).join(timeout=1))\n\n        del self.pool\n        self.pool = None\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *args):\n        self.kill()\n\n\nlock_pool = gevent.threadpool.ThreadPool(50)\nmain_thread_id = threading.current_thread().ident\n\n\ndef isMainThread():\n    return threading.current_thread().ident == main_thread_id\n\n\nclass Lock:\n    def __init__(self):\n        self.lock = gevent._threading.Lock()\n        self.locked = self.lock.locked\n        self.release = self.lock.release\n        self.time_lock = 0\n\n    def acquire(self, *args, **kwargs):\n        self.time_lock = time.time()\n        if self.locked() and isMainThread():\n            # Start in new thread to avoid blocking gevent loop\n            return lock_pool.apply(self.lock.acquire, args, kwargs)\n        else:\n            return self.lock.acquire(*args, **kwargs)\n\n    def __del__(self):\n        while self.locked():\n            self.release()\n\n\nclass Event:\n    def __init__(self):\n        self.get_lock = Lock()\n        self.res = None\n        self.get_lock.acquire(False)\n        self.done = False\n\n    def set(self, res):\n        if self.done:\n            raise Exception(\"Event already has value\")\n        self.res = res\n        self.get_lock.release()\n        self.done = True\n\n    def get(self):\n        if not self.done:\n            self.get_lock.acquire(True)\n        if self.get_lock.locked():\n            self.get_lock.release()\n        back = self.res\n        return back\n\n    def __del__(self):\n        self.res = None\n        while self.get_lock.locked():\n            self.get_lock.release()\n\n\n# Execute function calls in main loop from other threads\nclass MainLoopCaller():\n    def __init__(self):\n        self.queue_call = queue.Queue()\n\n        self.pool = gevent.threadpool.ThreadPool(1)\n        self.num_direct = 0\n        self.running = True\n\n    def caller(self, func, args, kwargs, event_done):\n        try:\n            res = func(*args, **kwargs)\n            event_done.set((True, res))\n        except Exception as err:\n            event_done.set((False, err))\n\n    def start(self):\n        gevent.spawn(self.run)\n        time.sleep(0.001)\n\n    def run(self):\n        while self.running:\n            if self.queue_call.qsize() == 0:  # Get queue in new thread to avoid gevent blocking\n                func, args, kwargs, event_done = self.pool.apply(self.queue_call.get)\n            else:\n                func, args, kwargs, event_done = self.queue_call.get()\n            gevent.spawn(self.caller, func, args, kwargs, event_done)\n            del func, args, kwargs, event_done\n        self.running = False\n\n    def call(self, func, *args, **kwargs):\n        if threading.current_thread().ident == main_thread_id:\n            return func(*args, **kwargs)\n        else:\n            event_done = Event()\n            self.queue_call.put((func, args, kwargs, event_done))\n            success, res = event_done.get()\n            del event_done\n            self.queue_call.task_done()\n            if success:\n                return res\n            else:\n                raise res\n\n\ndef patchSleep():  # Fix memory leak by using real sleep in threads\n    real_sleep = gevent.monkey.get_original(\"time\", \"sleep\")\n\n    def patched_sleep(seconds):\n        if isMainThread():\n            gevent.sleep(seconds)\n        else:\n            real_sleep(seconds)\n    time.sleep = patched_sleep\n\n\nmain_loop = MainLoopCaller()\nmain_loop.start()\npatchSleep()\n"
  },
  {
    "path": "src/util/UpnpPunch.py",
    "content": "import re\nimport urllib.request\nimport http.client\nimport logging\nfrom urllib.parse import urlparse\nfrom xml.dom.minidom import parseString\nfrom xml.parsers.expat import ExpatError\n\nfrom gevent import socket\nimport gevent\n\n# Relevant UPnP spec:\n# http://www.upnp.org/specs/gw/UPnP-gw-WANIPConnection-v1-Service.pdf\n\n# General TODOs:\n# Handle 0 or >1 IGDs\n\nlogger = logging.getLogger(\"Upnp\")\n\nclass UpnpError(Exception):\n    pass\n\n\nclass IGDError(UpnpError):\n    \"\"\"\n    Signifies a problem with the IGD.\n    \"\"\"\n    pass\n\n\nREMOVE_WHITESPACE = re.compile(r'>\\s*<')\n\n\ndef perform_m_search(local_ip):\n    \"\"\"\n    Broadcast a UDP SSDP M-SEARCH packet and return response.\n    \"\"\"\n    search_target = \"urn:schemas-upnp-org:device:InternetGatewayDevice:1\"\n\n    ssdp_request = ''.join(\n        ['M-SEARCH * HTTP/1.1\\r\\n',\n         'HOST: 239.255.255.250:1900\\r\\n',\n         'MAN: \"ssdp:discover\"\\r\\n',\n         'MX: 2\\r\\n',\n         'ST: {0}\\r\\n'.format(search_target),\n         '\\r\\n']\n    ).encode(\"utf8\")\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n\n    sock.bind((local_ip, 0))\n\n    sock.sendto(ssdp_request, ('239.255.255.250', 1900))\n    if local_ip == \"127.0.0.1\":\n        sock.settimeout(1)\n    else:\n        sock.settimeout(5)\n\n    try:\n        return sock.recv(2048).decode(\"utf8\")\n    except socket.error:\n        raise UpnpError(\"No reply from IGD using {} as IP\".format(local_ip))\n    finally:\n        sock.close()\n\n\ndef _retrieve_location_from_ssdp(response):\n    \"\"\"\n    Parse raw HTTP response to retrieve the UPnP location header\n    and return a ParseResult object.\n    \"\"\"\n    parsed_headers = re.findall(r'(?P<name>.*?): (?P<value>.*?)\\r\\n', response)\n    header_locations = [header[1]\n                        for header in parsed_headers\n                        if header[0].lower() == 'location']\n\n    if len(header_locations) < 1:\n        raise IGDError('IGD response does not contain a \"location\" header.')\n\n    return urlparse(header_locations[0])\n\n\ndef _retrieve_igd_profile(url):\n    \"\"\"\n    Retrieve the device's UPnP profile.\n    \"\"\"\n    try:\n        return urllib.request.urlopen(url.geturl(), timeout=5).read().decode('utf-8')\n    except socket.error:\n        raise IGDError('IGD profile query timed out')\n\n\ndef _get_first_child_data(node):\n    \"\"\"\n    Get the text value of the first child text node of a node.\n    \"\"\"\n    return node.childNodes[0].data\n\n\ndef _parse_igd_profile(profile_xml):\n    \"\"\"\n    Traverse the profile xml DOM looking for either\n    WANIPConnection or WANPPPConnection and return\n    the 'controlURL' and the service xml schema.\n    \"\"\"\n    try:\n        dom = parseString(profile_xml)\n    except ExpatError as e:\n        raise IGDError(\n            'Unable to parse IGD reply: {0} \\n\\n\\n {1}'.format(profile_xml, e))\n\n    service_types = dom.getElementsByTagName('serviceType')\n    for service in service_types:\n        if _get_first_child_data(service).find('WANIPConnection') > 0 or \\\n           _get_first_child_data(service).find('WANPPPConnection') > 0:\n            try:\n                control_url = _get_first_child_data(\n                    service.parentNode.getElementsByTagName('controlURL')[0])\n                upnp_schema = _get_first_child_data(service).split(':')[-2]\n                return control_url, upnp_schema\n            except IndexError:\n                # Pass the error because any error here should raise the\n                # that's specified outside the for loop.\n                pass\n    raise IGDError(\n        'Could not find a control url or UPNP schema in IGD response.')\n\n\n# add description\ndef _get_local_ips():\n    def method1():\n        try:\n            # get local ip using UDP and a  broadcast address\n            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n            s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\n            # Not using <broadcast> because gevents getaddrinfo doesn't like that\n            # using port 1 as per hobbldygoop's comment about port 0 not working on osx:\n            # https://github.com/sirMackk/ZeroNet/commit/fdcd15cf8df0008a2070647d4d28ffedb503fba2#commitcomment-9863928\n            s.connect(('239.255.255.250', 1))\n            return [s.getsockname()[0]]\n        except:\n            pass\n\n    def method2():\n        # Get ip by using UDP and a normal address (google dns ip)\n        try:\n            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n            s.connect(('8.8.8.8', 0))\n            return [s.getsockname()[0]]\n        except:\n            pass\n\n    def method3():\n        # Get ip by '' hostname . Not supported on all platforms.\n        try:\n            return socket.gethostbyname_ex('')[2]\n        except:\n            pass\n\n    threads = [\n        gevent.spawn(method1),\n        gevent.spawn(method2),\n        gevent.spawn(method3)\n    ]\n\n    gevent.joinall(threads, timeout=5)\n\n    local_ips = []\n    for thread in threads:\n        if thread.value:\n            local_ips += thread.value\n\n    # Delete duplicates\n    local_ips = list(set(local_ips))\n\n\n    # Probably we looking for an ip starting with 192\n    local_ips = sorted(local_ips, key=lambda a: a.startswith(\"192\"), reverse=True)\n\n    return local_ips\n\n\ndef _create_open_message(local_ip,\n                         port,\n                         description=\"UPnPPunch\",\n                         protocol=\"TCP\",\n                         upnp_schema='WANIPConnection'):\n    \"\"\"\n    Build a SOAP AddPortMapping message.\n    \"\"\"\n\n    soap_message = \"\"\"<?xml version=\"1.0\"?>\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n    <s:Body>\n        <u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:{upnp_schema}:1\">\n            <NewRemoteHost></NewRemoteHost>\n            <NewExternalPort>{port}</NewExternalPort>\n            <NewProtocol>{protocol}</NewProtocol>\n            <NewInternalPort>{port}</NewInternalPort>\n            <NewInternalClient>{host_ip}</NewInternalClient>\n            <NewEnabled>1</NewEnabled>\n            <NewPortMappingDescription>{description}</NewPortMappingDescription>\n            <NewLeaseDuration>0</NewLeaseDuration>\n        </u:AddPortMapping>\n    </s:Body>\n</s:Envelope>\"\"\".format(port=port,\n                        protocol=protocol,\n                        host_ip=local_ip,\n                        description=description,\n                        upnp_schema=upnp_schema)\n    return (REMOVE_WHITESPACE.sub('><', soap_message), 'AddPortMapping')\n\n\ndef _create_close_message(local_ip,\n                          port,\n                          description=None,\n                          protocol='TCP',\n                          upnp_schema='WANIPConnection'):\n    soap_message = \"\"\"<?xml version=\"1.0\"?>\n<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n    <s:Body>\n        <u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:{upnp_schema}:1\">\n            <NewRemoteHost></NewRemoteHost>\n            <NewExternalPort>{port}</NewExternalPort>\n            <NewProtocol>{protocol}</NewProtocol>\n        </u:DeletePortMapping>\n    </s:Body>\n</s:Envelope>\"\"\".format(port=port,\n                        protocol=protocol,\n                        upnp_schema=upnp_schema)\n    return (REMOVE_WHITESPACE.sub('><', soap_message), 'DeletePortMapping')\n\n\ndef _parse_for_errors(soap_response):\n    logger.debug(soap_response.status)\n    if soap_response.status >= 400:\n        response_data = soap_response.read()\n        logger.debug(response_data)\n        try:\n            err_dom = parseString(response_data)\n            err_code = _get_first_child_data(err_dom.getElementsByTagName(\n                'errorCode')[0])\n            err_msg = _get_first_child_data(\n                err_dom.getElementsByTagName('errorDescription')[0]\n            )\n        except Exception as err:\n            raise IGDError(\n                'Unable to parse SOAP error: {0}. Got: \"{1}\"'.format(\n                    err, response_data))\n        raise IGDError(\n            'SOAP request error: {0} - {1}'.format(err_code, err_msg)\n        )\n    return soap_response\n\n\ndef _send_soap_request(location, upnp_schema, control_path, soap_fn,\n                       soap_message):\n    \"\"\"\n    Send out SOAP request to UPnP device and return a response.\n    \"\"\"\n    headers = {\n        'SOAPAction': (\n            '\"urn:schemas-upnp-org:service:{schema}:'\n            '1#{fn_name}\"'.format(schema=upnp_schema, fn_name=soap_fn)\n        ),\n        'Content-Type': 'text/xml'\n    }\n    logger.debug(\"Sending UPnP request to {0}:{1}...\".format(\n        location.hostname, location.port))\n    conn = http.client.HTTPConnection(location.hostname, location.port)\n    conn.request('POST', control_path, soap_message, headers)\n\n    response = conn.getresponse()\n    conn.close()\n\n    return _parse_for_errors(response)\n\n\ndef _collect_idg_data(ip_addr):\n    idg_data = {}\n    idg_response = perform_m_search(ip_addr)\n    idg_data['location'] = _retrieve_location_from_ssdp(idg_response)\n    idg_data['control_path'], idg_data['upnp_schema'] = _parse_igd_profile(\n        _retrieve_igd_profile(idg_data['location']))\n    return idg_data\n\n\ndef _send_requests(messages, location, upnp_schema, control_path):\n    responses = [_send_soap_request(location, upnp_schema, control_path,\n                                    message_tup[1], message_tup[0])\n                 for message_tup in messages]\n\n    if all(rsp.status == 200 for rsp in responses):\n        return\n    raise UpnpError('Sending requests using UPnP failed.')\n\n\ndef _orchestrate_soap_request(ip, port, msg_fn, desc=None, protos=(\"TCP\", \"UDP\")):\n    logger.debug(\"Trying using local ip: %s\" % ip)\n    idg_data = _collect_idg_data(ip)\n\n    soap_messages = [\n        msg_fn(ip, port, desc, proto, idg_data['upnp_schema'])\n        for proto in protos\n    ]\n\n    _send_requests(soap_messages, **idg_data)\n\n\ndef _communicate_with_igd(port=15441,\n                          desc=\"UpnpPunch\",\n                          retries=3,\n                          fn=_create_open_message,\n                          protos=(\"TCP\", \"UDP\")):\n    \"\"\"\n    Manage sending a message generated by 'fn'.\n    \"\"\"\n\n    local_ips = _get_local_ips()\n    success = False\n\n    def job(local_ip):\n        for retry in range(retries):\n            try:\n                _orchestrate_soap_request(local_ip, port, fn, desc, protos)\n                return True\n            except Exception as e:\n                logger.debug('Upnp request using \"{0}\" failed: {1}'.format(local_ip, e))\n                gevent.sleep(1)\n        return False\n\n    threads = []\n\n    for local_ip in local_ips:\n        job_thread = gevent.spawn(job, local_ip)\n        threads.append(job_thread)\n        gevent.sleep(0.1)\n        if any([thread.value for thread in threads]):\n            success = True\n            break\n\n    # Wait another 10sec for competition or any positive result\n    for _ in range(10):\n        all_done = all([thread.value is not None for thread in threads])\n        any_succeed = any([thread.value for thread in threads])\n        if all_done or any_succeed:\n            break\n        gevent.sleep(1)\n\n    if any([thread.value for thread in threads]):\n        success = True\n\n    if not success:\n        raise UpnpError(\n            'Failed to communicate with igd using port {0} on local machine after {1} tries.'.format(\n                port, retries))\n\n    return success\n\n\ndef ask_to_open_port(port=15441, desc=\"UpnpPunch\", retries=3, protos=(\"TCP\", \"UDP\")):\n    logger.debug(\"Trying to open port %d.\" % port)\n    return _communicate_with_igd(port=port,\n                          desc=desc,\n                          retries=retries,\n                          fn=_create_open_message,\n                          protos=protos)\n\n\ndef ask_to_close_port(port=15441, desc=\"UpnpPunch\", retries=3, protos=(\"TCP\", \"UDP\")):\n    logger.debug(\"Trying to close port %d.\" % port)\n    # retries=1 because multiple successes cause 500 response and failure\n    return _communicate_with_igd(port=port,\n                          desc=desc,\n                          retries=retries,\n                          fn=_create_close_message,\n                          protos=protos)\n\n\nif __name__ == \"__main__\":\n    from gevent import monkey\n    monkey.patch_all()\n    logging.basicConfig(level=logging.DEBUG)\n    import time\n\n    s = time.time()\n    print(\"Opening port...\")\n    print(\"Success:\", ask_to_open_port(15443, \"ZeroNet\", protos=[\"TCP\"]))\n    print(\"Done in\", time.time() - s)\n\n\n    print(\"Closing port...\")\n    print(\"Success:\", ask_to_close_port(15443, \"ZeroNet\", protos=[\"TCP\"]))\n    print(\"Done in\", time.time() - s)\n\n"
  },
  {
    "path": "src/util/__init__.py",
    "content": "from .Cached import Cached\r\nfrom .Event import Event\r\nfrom .Noparallel import Noparallel\r\nfrom .Pooled import Pooled\r\n"
  },
  {
    "path": "src/util/helper.py",
    "content": "import os\nimport stat\nimport socket\nimport struct\nimport re\nimport collections\nimport time\nimport logging\nimport base64\nimport json\n\nimport gevent\n\nfrom Config import config\n\n\ndef atomicWrite(dest, content, mode=\"wb\"):\n    try:\n        with open(dest + \"-tmpnew\", mode) as f:\n            f.write(content)\n            f.flush()\n            os.fsync(f.fileno())\n        if os.path.isfile(dest + \"-tmpold\"):  # Previous incomplete write\n            os.rename(dest + \"-tmpold\", dest + \"-tmpold-%s\" % time.time())\n        if os.path.isfile(dest):  # Rename old file to -tmpold\n            os.rename(dest, dest + \"-tmpold\")\n        os.rename(dest + \"-tmpnew\", dest)\n        if os.path.isfile(dest + \"-tmpold\"):\n            os.unlink(dest + \"-tmpold\")  # Remove old file\n        return True\n    except Exception as err:\n        from Debug import Debug\n        logging.error(\n            \"File %s write failed: %s, (%s) reverting...\" %\n            (dest, Debug.formatException(err), Debug.formatStack())\n        )\n        if os.path.isfile(dest + \"-tmpold\") and not os.path.isfile(dest):\n            os.rename(dest + \"-tmpold\", dest)\n        return False\n\n\ndef jsonDumps(data):\n    content = json.dumps(data, indent=1, sort_keys=True)\n\n    # Make it a little more compact by removing unnecessary white space\n    def compact_dict(match):\n        if \"\\n\" in match.group(0):\n            return match.group(0).replace(match.group(1), match.group(1).strip())\n        else:\n            return match.group(0)\n\n    content = re.sub(r\"\\{(\\n[^,\\[\\{]{10,100000}?)\\}[, ]{0,2}\\n\", compact_dict, content, flags=re.DOTALL)\n\n    def compact_list(match):\n        if \"\\n\" in match.group(0):\n            stripped_lines = re.sub(\"\\n[ ]*\", \"\", match.group(1))\n            return match.group(0).replace(match.group(1), stripped_lines)\n        else:\n            return match.group(0)\n\n    content = re.sub(r\"\\[([^\\[\\{]{2,100000}?)\\][, ]{0,2}\\n\", compact_list, content, flags=re.DOTALL)\n\n    # Remove end of line whitespace\n    content = re.sub(r\"(?m)[ ]+$\", \"\", content)\n    return content\n\n\ndef openLocked(path, mode=\"wb\"):\n    try:\n        if os.name == \"posix\":\n            import fcntl\n            f = open(path, mode)\n            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)\n        elif os.name == \"nt\":\n            import msvcrt\n            f = open(path, mode)\n            msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)\n        else:\n            f = open(path, mode)\n    except (IOError, PermissionError, BlockingIOError) as err:\n        raise BlockingIOError(\"Unable to lock file: %s\" % err)\n    return f\n\n\ndef getFreeSpace():\n    free_space = -1\n    if \"statvfs\" in dir(os):  # Unix\n        statvfs = os.statvfs(config.data_dir.encode(\"utf8\"))\n        free_space = statvfs.f_frsize * statvfs.f_bavail\n    else:  # Windows\n        try:\n            import ctypes\n            free_space_pointer = ctypes.c_ulonglong(0)\n            ctypes.windll.kernel32.GetDiskFreeSpaceExW(\n                ctypes.c_wchar_p(config.data_dir), None, None, ctypes.pointer(free_space_pointer)\n            )\n            free_space = free_space_pointer.value\n        except Exception as err:\n            logging.error(\"GetFreeSpace error: %s\" % err)\n    return free_space\n\n\ndef sqlquote(value):\n    if type(value) is int:\n        return str(value)\n    else:\n        return \"'%s'\" % value.replace(\"'\", \"''\")\n\n\ndef shellquote(*args):\n    if len(args) == 1:\n        return '\"%s\"' % args[0].replace('\"', \"\")\n    else:\n        return tuple(['\"%s\"' % arg.replace('\"', \"\") for arg in args])\n\n\ndef packPeers(peers):\n    packed_peers = {\"ipv4\": [], \"ipv6\": [], \"onion\": []}\n    for peer in peers:\n        try:\n            ip_type = getIpType(peer.ip)\n            if ip_type in packed_peers:\n                packed_peers[ip_type].append(peer.packMyAddress())\n        except Exception:\n            logging.debug(\"Error packing peer address: %s\" % peer)\n    return packed_peers\n\n\n# ip, port to packed 6byte or 18byte format\ndef packAddress(ip, port):\n    if \":\" in ip:\n        return socket.inet_pton(socket.AF_INET6, ip) + struct.pack(\"H\", port)\n    else:\n        return socket.inet_aton(ip) + struct.pack(\"H\", port)\n\n\n# From 6byte or 18byte format to ip, port\ndef unpackAddress(packed):\n    if len(packed) == 18:\n        return socket.inet_ntop(socket.AF_INET6, packed[0:16]), struct.unpack_from(\"H\", packed, 16)[0]\n    else:\n        if len(packed) != 6:\n            raise Exception(\"Invalid length ip4 packed address: %s\" % len(packed))\n        return socket.inet_ntoa(packed[0:4]), struct.unpack_from(\"H\", packed, 4)[0]\n\n\n# onion, port to packed 12byte format\ndef packOnionAddress(onion, port):\n    onion = onion.replace(\".onion\", \"\")\n    return base64.b32decode(onion.upper()) + struct.pack(\"H\", port)\n\n\n# From 12byte format to ip, port\ndef unpackOnionAddress(packed):\n    return base64.b32encode(packed[0:-2]).lower().decode() + \".onion\", struct.unpack(\"H\", packed[-2:])[0]\n\n\n# Get dir from file\n# Return: data/site/content.json -> data/site/\ndef getDirname(path):\n    if \"/\" in path:\n        return path[:path.rfind(\"/\") + 1].lstrip(\"/\")\n    else:\n        return \"\"\n\n\n# Get dir from file\n# Return: data/site/content.json -> content.json\ndef getFilename(path):\n    return path[path.rfind(\"/\") + 1:]\n\n\ndef getFilesize(path):\n    try:\n        s = os.stat(path)\n    except Exception:\n        return None\n    if stat.S_ISREG(s.st_mode):  # Test if it's file\n        return s.st_size\n    else:\n        return None\n\n\n# Convert hash to hashid for hashfield\ndef toHashId(hash):\n    return int(hash[0:4], 16)\n\n\n# Merge dict values\ndef mergeDicts(dicts):\n    back = collections.defaultdict(set)\n    for d in dicts:\n        for key, val in d.items():\n            back[key].update(val)\n    return dict(back)\n\n\n# Request https url using gevent SSL error workaround\ndef httpRequest(url, as_file=False):\n    if url.startswith(\"http://\"):\n        import urllib.request\n        response = urllib.request.urlopen(url)\n    else:  # Hack to avoid Python gevent ssl errors\n        import socket\n        import http.client\n        import ssl\n\n        host, request = re.match(\"https://(.*?)(/.*?)$\", url).groups()\n\n        conn = http.client.HTTPSConnection(host)\n        sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address)\n        conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file)\n        conn.request(\"GET\", request)\n        response = conn.getresponse()\n        if response.status in [301, 302, 303, 307, 308]:\n            logging.info(\"Redirect to: %s\" % response.getheader('Location'))\n            response = httpRequest(response.getheader('Location'))\n\n    if as_file:\n        import io\n        data = io.BytesIO()\n        while True:\n            buff = response.read(1024 * 16)\n            if not buff:\n                break\n            data.write(buff)\n        return data\n    else:\n        return response\n\n\ndef timerCaller(secs, func, *args, **kwargs):\n    gevent.spawn_later(secs, timerCaller, secs, func, *args, **kwargs)\n    func(*args, **kwargs)\n\n\ndef timer(secs, func, *args, **kwargs):\n    return gevent.spawn_later(secs, timerCaller, secs, func, *args, **kwargs)\n\n\ndef create_connection(address, timeout=None, source_address=None):\n    if address in config.ip_local:\n        sock = socket.create_connection_original(address, timeout, source_address)\n    else:\n        sock = socket.create_connection_original(address, timeout, socket.bind_addr)\n    return sock\n\n\ndef socketBindMonkeyPatch(bind_ip, bind_port):\n    import socket\n    logging.info(\"Monkey patching socket to bind to: %s:%s\" % (bind_ip, bind_port))\n    socket.bind_addr = (bind_ip, int(bind_port))\n    socket.create_connection_original = socket.create_connection\n    socket.create_connection = create_connection\n\n\ndef limitedGzipFile(*args, **kwargs):\n    import gzip\n\n    class LimitedGzipFile(gzip.GzipFile):\n        def read(self, size=-1):\n            return super(LimitedGzipFile, self).read(1024 * 1024 * 25)\n    return LimitedGzipFile(*args, **kwargs)\n\n\ndef avg(items):\n    if len(items) > 0:\n        return sum(items) / len(items)\n    else:\n        return 0\n\n\ndef isIp(ip):\n    if \":\" in ip:  # IPv6\n        try:\n            socket.inet_pton(socket.AF_INET6, ip)\n            return True\n        except Exception:\n            return False\n\n    else:  # IPv4\n        try:\n            socket.inet_aton(ip)\n            return True\n        except Exception:\n            return False\n\n\nlocal_ip_pattern = re.compile(r\"^127\\.|192\\.168\\.|10\\.|172\\.1[6-9]\\.|172\\.2[0-9]\\.|172\\.3[0-1]\\.|169\\.254\\.|::1$|fe80\")\ndef isPrivateIp(ip):\n    return local_ip_pattern.match(ip)\n\n\ndef getIpType(ip):\n    if ip.endswith(\".onion\"):\n        return \"onion\"\n    elif \":\" in ip:\n        return \"ipv6\"\n    elif re.match(r\"[0-9\\.]+$\", ip):\n        return \"ipv4\"\n    else:\n        return \"unknown\"\n\n\ndef createSocket(ip, sock_type=socket.SOCK_STREAM):\n    ip_type = getIpType(ip)\n    if ip_type == \"ipv6\":\n        return socket.socket(socket.AF_INET6, sock_type)\n    else:\n        return socket.socket(socket.AF_INET, sock_type)\n\n\ndef getInterfaceIps(ip_type=\"ipv4\"):\n    res = []\n    if ip_type == \"ipv6\":\n        test_ips = [\"ff0e::c\", \"2606:4700:4700::1111\"]\n    else:\n        test_ips = ['239.255.255.250', \"8.8.8.8\"]\n\n    for test_ip in test_ips:\n        try:\n            s = createSocket(test_ip, sock_type=socket.SOCK_DGRAM)\n            s.connect((test_ip, 1))\n            res.append(s.getsockname()[0])\n        except Exception:\n            pass\n\n    try:\n        res += [ip[4][0] for ip in socket.getaddrinfo(socket.gethostname(), 1)]\n    except Exception:\n        pass\n\n    res = [re.sub(\"%.*\", \"\", ip) for ip in res if getIpType(ip) == ip_type and isIp(ip)]\n    return list(set(res))\n\n\ndef cmp(a, b):\n    return (a > b) - (a < b)\n\n\ndef encodeResponse(func):  # Encode returned data from utf8 to bytes\n    def wrapper(*args, **kwargs):\n        back = func(*args, **kwargs)\n        if \"__next__\" in dir(back):\n            for part in back:\n                if type(part) == bytes:\n                    yield part\n                else:\n                    yield part.encode()\n        else:\n            if type(back) == bytes:\n                yield back\n            else:\n                yield back.encode()\n\n    return wrapper\n"
  },
  {
    "path": "start.py",
    "content": "#!/usr/bin/env python3\n\n\n# Included modules\nimport sys\n\n# ZeroNet Modules\nimport zeronet\n\n\ndef main():\n    if \"--open_browser\" not in sys.argv:\n        sys.argv = [sys.argv[0]] + [\"--open_browser\", \"default_browser\"] + sys.argv[1:]\n    zeronet.start()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "tools/coffee/README.md",
    "content": "# CoffeeScript compiler for Windows\n\nA simple command-line utilty for Windows that will compile `*.coffee` files to JavaScript `*.js` files using [CoffeeScript](http://jashkenas.github.com/coffee-script/) and the venerable Windows Script Host, ubiquitous on Windows since the 90s.\n\n## Usage\n\nTo use it, invoke `coffee.cmd` like so:\n\n    coffee input.coffee output.js\n    \nIf an output is not specified, it is written to `stdout`. In neither an input or output are specified then data is assumed to be on `stdin`. For example:\n\n    type input.coffee | coffee > output.js\n\nErrors are written to `stderr`.\n\nIn the `test` directory there's a version of the standard CoffeeScript tests which can be kicked off using `test.cmd`. The test just attempts to compile the *.coffee files but doesn't execute them.\n\nTo upgrade to the latest CoffeeScript simply replace `coffee-script.js` from the upstream https://github.com/jashkenas/coffee-script/blob/master/extras/coffee-script.js (the tests will likely need updating as well, if you want to run them).\n"
  },
  {
    "path": "tools/coffee/coffee-script.js",
    "content": "/**\n * CoffeeScript Compiler v1.12.6\n * http://coffeescript.org\n *\n * Copyright 2011, Jeremy Ashkenas\n * Released under the MIT License\n */\nvar $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.checkStringArgs=function(u,xa,va){if(null==u)throw new TypeError(\"The 'this' value for String.prototype.\"+va+\" must not be null or undefined\");if(xa instanceof RegExp)throw new TypeError(\"First argument to String.prototype.\"+va+\" must not be a regular expression\");return u+\"\"};\n$jscomp.defineProperty=\"function\"==typeof Object.defineProperties?Object.defineProperty:function(u,xa,va){if(va.get||va.set)throw new TypeError(\"ES3 does not support getters and setters.\");u!=Array.prototype&&u!=Object.prototype&&(u[xa]=va.value)};$jscomp.getGlobal=function(u){return\"undefined\"!=typeof window&&window===u?u:\"undefined\"!=typeof global&&null!=global?global:u};$jscomp.global=$jscomp.getGlobal(this);\n$jscomp.polyfill=function(u,xa,va,f){if(xa){va=$jscomp.global;u=u.split(\".\");for(f=0;f<u.length-1;f++){var qa=u[f];qa in va||(va[qa]={});va=va[qa]}u=u[u.length-1];f=va[u];xa=xa(f);xa!=f&&null!=xa&&$jscomp.defineProperty(va,u,{configurable:!0,writable:!0,value:xa})}};\n$jscomp.polyfill(\"String.prototype.repeat\",function(u){return u?u:function(u){var va=$jscomp.checkStringArgs(this,null,\"repeat\");if(0>u||1342177279<u)throw new RangeError(\"Invalid count value\");u|=0;for(var f=\"\";u;)if(u&1&&(f+=va),u>>>=1)va+=va;return f}},\"es6-impl\",\"es3\");$jscomp.findInternal=function(u,xa,va){u instanceof String&&(u=String(u));for(var f=u.length,qa=0;qa<f;qa++){var q=u[qa];if(xa.call(va,q,qa,u))return{i:qa,v:q}}return{i:-1,v:void 0}};\n$jscomp.polyfill(\"Array.prototype.find\",function(u){return u?u:function(u,va){return $jscomp.findInternal(this,u,va).v}},\"es6-impl\",\"es3\");$jscomp.SYMBOL_PREFIX=\"jscomp_symbol_\";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(u){return $jscomp.SYMBOL_PREFIX+(u||\"\")+$jscomp.symbolCounter_++};\n$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var u=$jscomp.global.Symbol.iterator;u||(u=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol(\"iterator\"));\"function\"!=typeof Array.prototype[u]&&$jscomp.defineProperty(Array.prototype,u,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};\n$jscomp.arrayIterator=function(u){var xa=0;return $jscomp.iteratorPrototype(function(){return xa<u.length?{done:!1,value:u[xa++]}:{done:!0}})};$jscomp.iteratorPrototype=function(u){$jscomp.initSymbolIterator();u={next:u};u[$jscomp.global.Symbol.iterator]=function(){return this};return u};\n$jscomp.iteratorFromArray=function(u,xa){$jscomp.initSymbolIterator();u instanceof String&&(u+=\"\");var va=0,f={next:function(){if(va<u.length){var qa=va++;return{value:xa(qa,u[qa]),done:!1}}f.next=function(){return{done:!0,value:void 0}};return f.next()}};f[Symbol.iterator]=function(){return f};return f};$jscomp.polyfill(\"Array.prototype.keys\",function(u){return u?u:function(){return $jscomp.iteratorFromArray(this,function(u){return u})}},\"es6-impl\",\"es3\");\n(function(u){var xa=function(){function u(f){return u[f]}u[\"../../package.json\"]={name:\"coffee-script\",description:\"Unfancy JavaScript\",keywords:[\"javascript\",\"language\",\"coffeescript\",\"compiler\"],author:\"Jeremy Ashkenas\",version:\"1.12.6\",license:\"MIT\",engines:{node:\"\\x3e\\x3d0.8.0\"},directories:{lib:\"./lib/coffee-script\"},main:\"./lib/coffee-script/coffee-script\",bin:{coffee:\"./bin/coffee\",cake:\"./bin/cake\"},files:[\"bin\",\"lib\",\"register.js\",\"repl.js\"],scripts:{test:\"node ./bin/cake test\",\"test-harmony\":\"node --harmony ./bin/cake test\"},\nhomepage:\"http://coffeescript.org\",bugs:\"https://github.com/jashkenas/coffeescript/issues\",repository:{type:\"git\",url:\"git://github.com/jashkenas/coffeescript.git\"},devDependencies:{docco:\"~0.7.0\",\"google-closure-compiler-js\":\"^20170423.0.0\",\"highlight.js\":\"~9.11.0\",jison:\"\\x3e\\x3d0.4.17\",\"markdown-it\":\"^8.3.1\",underscore:\"~1.8.3\"}};u[\"./helpers\"]=function(){var f={};(function(){var u,q,y;f.starts=function(a,h,r){return h===a.substr(r,h.length)};f.ends=function(a,h,r){var g=h.length;return h===a.substr(a.length-\ng-(r||0),g)};f.repeat=y=function(a,h){var g;for(g=\"\";0<h;)h&1&&(g+=a),h>>>=1,a+=a;return g};f.compact=function(a){var g,b;var n=[];var y=0;for(b=a.length;y<b;y++)(g=a[y])&&n.push(g);return n};f.count=function(a,h){var g;var b=g=0;if(!h.length)return 1/0;for(;g=1+a.indexOf(h,g);)b++;return b};f.merge=function(g,h){return a(a({},g),h)};var a=f.extend=function(a,h){var g;for(g in h){var b=h[g];a[g]=b}return a};f.flatten=u=function(a){var g;var b=[];var y=0;for(g=a.length;y<g;y++){var f=a[y];\"[object Array]\"===\nObject.prototype.toString.call(f)?b=b.concat(u(f)):b.push(f)}return b};f.del=function(a,h){var g=a[h];delete a[h];return g};f.some=null!=(q=Array.prototype.some)?q:function(a){var g;var b=0;for(g=this.length;b<g;b++){var y=this[b];if(a(y))return!0}return!1};f.invertLiterate=function(a){var g=!0;var b;var y=a.split(\"\\n\");var f=[];var H=0;for(b=y.length;H<b;H++)a=y[H],g&&/^([ ]{4}|[ ]{0,3}\\t)/.test(a)?f.push(a):(g=/^\\s*$/.test(a))?f.push(a):f.push(\"# \"+a);return f.join(\"\\n\")};var b=function(a,b){return b?\n{first_line:a.first_line,first_column:a.first_column,last_line:b.last_line,last_column:b.last_column}:a};f.addLocationDataFn=function(a,h){return function(g){\"object\"===typeof g&&g.updateLocationDataIfMissing&&g.updateLocationDataIfMissing(b(a,h));return g}};f.locationDataToString=function(a){var g;\"2\"in a&&\"first_line\"in a[2]?g=a[2]:\"first_line\"in a&&(g=a);return g?g.first_line+1+\":\"+(g.first_column+1)+\"-\"+(g.last_line+1+\":\"+(g.last_column+1)):\"No location data\"};f.baseFileName=function(a,b,y){null==\nb&&(b=!1);null==y&&(y=!1);a=a.split(y?/\\\\|\\//:/\\//);a=a[a.length-1];if(!(b&&0<=a.indexOf(\".\")))return a;a=a.split(\".\");a.pop();\"coffee\"===a[a.length-1]&&1<a.length&&a.pop();return a.join(\".\")};f.isCoffee=function(a){return/\\.((lit)?coffee|coffee\\.md)$/.test(a)};f.isLiterate=function(a){return/\\.(litcoffee|coffee\\.md)$/.test(a)};f.throwSyntaxError=function(a,b){a=new SyntaxError(a);a.location=b;a.toString=ya;a.stack=a.toString();throw a;};f.updateSyntaxError=function(a,b,y){a.toString===ya&&(a.code||\n(a.code=b),a.filename||(a.filename=y),a.stack=a.toString());return a};var ya=function(){var a,b,f;if(!this.code||!this.location)return Error.prototype.toString.call(this);var n=this.location;var B=n.first_line;var H=n.first_column;var I=n.last_line;var F=n.last_column;null==I&&(I=B);null==F&&(F=H);var u=this.filename||\"[stdin]\";n=this.code.split(\"\\n\")[B];I=B===I?F+1:n.length;F=n.slice(0,H).replace(/[^\\s]/g,\" \")+y(\"^\",I-H);if(\"undefined\"!==typeof process&&null!==process)var x=(null!=(a=process.stdout)?\na.isTTY:void 0)&&!(null!=(b=process.env)&&b.NODE_DISABLE_COLORS);if(null!=(f=this.colorful)?f:x)x=function(a){return\"\\u001b[1;31m\"+a+\"\\u001b[0m\"},n=n.slice(0,H)+x(n.slice(H,I))+n.slice(I),F=x(F);return u+\":\"+(B+1)+\":\"+(H+1)+\": error: \"+this.message+\"\\n\"+n+\"\\n\"+F};f.nameWhitespaceCharacter=function(a){switch(a){case \" \":return\"space\";case \"\\n\":return\"newline\";case \"\\r\":return\"carriage return\";case \"\\t\":return\"tab\";default:return a}}}).call(this);return f}();u[\"./rewriter\"]=function(){var f={};(function(){var u,\nq,y=[].indexOf||function(a){for(var c=0,b=this.length;c<b;c++)if(c in this&&this[c]===a)return c;return-1},a=[].slice;var b=function(a,c,b){a=[a,c];a.generated=!0;b&&(a.origin=b);return a};f.Rewriter=function(){function l(){}l.prototype.rewrite=function(a){this.tokens=a;this.removeLeadingNewlines();this.closeOpenCalls();this.closeOpenIndexes();this.normalizeLines();this.tagPostfixConditionals();this.addImplicitBracesAndParens();this.addLocationDataToGeneratedTokens();this.fixOutdentLocationData();\nreturn this.tokens};l.prototype.scanTokens=function(a){var c,b;var k=this.tokens;for(c=0;b=k[c];)c+=a.call(this,b,c,k);return!0};l.prototype.detectEnd=function(a,b,m){var c,w,l,L;var f=this.tokens;for(c=0;L=f[a];){if(0===c&&b.call(this,L,a))return m.call(this,L,a);if(!L||0>c)return m.call(this,L,a-1);(w=L[0],0<=y.call(g,w))?c+=1:(l=L[0],0<=y.call(h,l))&&--c;a+=1}return a-1};l.prototype.removeLeadingNewlines=function(){var a,b;var m=this.tokens;var k=a=0;for(b=m.length;a<b;k=++a){var g=m[k][0];if(\"TERMINATOR\"!==\ng)break}if(k)return this.tokens.splice(0,k)};l.prototype.closeOpenCalls=function(){var a=function(a,c){var k;return\")\"===(k=a[0])||\"CALL_END\"===k||\"OUTDENT\"===a[0]&&\")\"===this.tag(c-1)};var b=function(a,c){return this.tokens[\"OUTDENT\"===a[0]?c-1:c][0]=\"CALL_END\"};return this.scanTokens(function(c,k){\"CALL_START\"===c[0]&&this.detectEnd(k+1,a,b);return 1})};l.prototype.closeOpenIndexes=function(){var a=function(a,c){var k;return\"]\"===(k=a[0])||\"INDEX_END\"===k};var b=function(a,c){return a[0]=\"INDEX_END\"};\nreturn this.scanTokens(function(c,k){\"INDEX_START\"===c[0]&&this.detectEnd(k+1,a,b);return 1})};l.prototype.indexOfTag=function(){var c,b,g,k;var l=arguments[0];var h=2<=arguments.length?a.call(arguments,1):[];var f=b=c=0;for(g=h.length;0<=g?b<g:b>g;f=0<=g?++b:--b){for(;\"HERECOMMENT\"===this.tag(l+f+c);)c+=2;if(null!=h[f]&&(\"string\"===typeof h[f]&&(h[f]=[h[f]]),k=this.tag(l+f+c),0>y.call(h[f],k)))return-1}return l+f+c-1};l.prototype.looksObjectish=function(a){if(-1<this.indexOfTag(a,\"@\",null,\":\")||\n-1<this.indexOfTag(a,null,\":\"))return!0;a=this.indexOfTag(a,g);if(-1<a){var c=null;this.detectEnd(a+1,function(a){var c;return c=a[0],0<=y.call(h,c)},function(a,b){return c=b});if(\":\"===this.tag(c+1))return!0}return!1};l.prototype.findTagsBackwards=function(a,b){var c,k,l,w,f,n,x;for(c=[];0<=a&&(c.length||(w=this.tag(a),0>y.call(b,w))&&((f=this.tag(a),0>y.call(g,f))||this.tokens[a].generated)&&(n=this.tag(a),0>y.call(R,n)));)(k=this.tag(a),0<=y.call(h,k))&&c.push(this.tag(a)),(l=this.tag(a),0<=y.call(g,\nl))&&c.length&&c.pop(),--a;return x=this.tag(a),0<=y.call(b,x)};l.prototype.addImplicitBracesAndParens=function(){var a=[];var l=null;return this.scanTokens(function(c,k,f){var m,w,n,r;var G=c[0];var K=(m=0<k?f[k-1]:[])[0];var u=(k<f.length-1?f[k+1]:[])[0];var B=function(){return a[a.length-1]};var D=k;var A=function(a){return k-D+a};var H=function(a){var b;return null!=a?null!=(b=a[2])?b.ours:void 0:void 0};var E=function(a){return H(a)&&\"{\"===(null!=a?a[0]:void 0)};var J=function(a){return H(a)&&\n\"(\"===(null!=a?a[0]:void 0)};var O=function(){return H(B())};var C=function(){return J(B())};var T=function(){return E(B())};var v=function(){var a;return O&&\"CONTROL\"===(null!=(a=B())?a[0]:void 0)};var Y=function(c){var g=null!=c?c:k;a.push([\"(\",g,{ours:!0}]);f.splice(g,0,b(\"CALL_START\",\"(\"));if(null==c)return k+=1};var S=function(){a.pop();f.splice(k,0,b(\"CALL_END\",\")\",[\"\",\"end of input\",c[2]]));return k+=1};var M=function(g,l){null==l&&(l=!0);var m=null!=g?g:k;a.push([\"{\",m,{sameLine:!0,startsLine:l,\nours:!0}]);l=new String(\"{\");l.generated=!0;f.splice(m,0,b(\"{\",l,c));if(null==g)return k+=1};var q=function(g){g=null!=g?g:k;a.pop();f.splice(g,0,b(\"}\",\"}\",c));return k+=1};if(C()&&(\"IF\"===G||\"TRY\"===G||\"FINALLY\"===G||\"CATCH\"===G||\"CLASS\"===G||\"SWITCH\"===G))return a.push([\"CONTROL\",k,{ours:!0}]),A(1);if(\"INDENT\"===G&&O()){if(\"\\x3d\\x3e\"!==K&&\"-\\x3e\"!==K&&\"[\"!==K&&\"(\"!==K&&\",\"!==K&&\"{\"!==K&&\"TRY\"!==K&&\"ELSE\"!==K&&\"\\x3d\"!==K)for(;C();)S();v()&&a.pop();a.push([G,k]);return A(1)}if(0<=y.call(g,G))return a.push([G,\nk]),A(1);if(0<=y.call(h,G)){for(;O();)C()?S():T()?q():a.pop();l=a.pop()}if((0<=y.call(I,G)&&c.spaced||\"?\"===G&&0<k&&!f[k-1].spaced)&&(0<=y.call(F,u)||0<=y.call(Q,u)&&(null==(w=f[k+1])||!w.spaced)&&(null==(n=f[k+1])||!n.newLine)))return\"?\"===G&&(G=c[0]=\"FUNC_EXIST\"),Y(k+1),A(2);if(0<=y.call(I,G)&&-1<this.indexOfTag(k+1,\"INDENT\")&&this.looksObjectish(k+2)&&!this.findTagsBackwards(k,\"CLASS EXTENDS IF CATCH SWITCH LEADING_WHEN FOR WHILE UNTIL\".split(\" \")))return Y(k+1),a.push([\"INDENT\",k+2]),A(3);if(\":\"===\nG){for(q=function(){var a;switch(!1){case a=this.tag(k-1),0>y.call(h,a):return l[1];case \"@\"!==this.tag(k-2):return k-2;default:return k-1}}.call(this);\"HERECOMMENT\"===this.tag(q-2);)q-=2;this.insideForDeclaration=\"FOR\"===u;m=0===q||(r=this.tag(q-1),0<=y.call(R,r))||f[q-1].newLine;if(B()&&(T=B(),r=T[0],v=T[1],(\"{\"===r||\"INDENT\"===r&&\"{\"===this.tag(v-1))&&(m||\",\"===this.tag(q-1)||\"{\"===this.tag(q-1))))return A(1);M(q,!!m);return A(2)}if(0<=y.call(R,G))for(M=a.length-1;0<=M;M+=-1)r=a[M],E(r)&&(r[2].sameLine=\n!1);M=\"OUTDENT\"===K||m.newLine;if(0<=y.call(x,G)||0<=y.call(z,G)&&M)for(;O();)if(M=B(),r=M[0],v=M[1],m=M[2],M=m.sameLine,m=m.startsLine,C()&&\",\"!==K)S();else if(T()&&!this.insideForDeclaration&&M&&\"TERMINATOR\"!==G&&\":\"!==K)q();else if(!T()||\"TERMINATOR\"!==G||\",\"===K||m&&this.looksObjectish(k+1))break;else{if(\"HERECOMMENT\"===u)return A(1);q()}if(!(\",\"!==G||this.looksObjectish(k+1)||!T()||this.insideForDeclaration||\"TERMINATOR\"===u&&this.looksObjectish(k+2)))for(u=\"OUTDENT\"===u?1:0;T();)q(k+u);return A(1)})};\nl.prototype.addLocationDataToGeneratedTokens=function(){return this.scanTokens(function(a,b,g){var c,l;if(a[2]||!a.generated&&!a.explicit)return 1;if(\"{\"===a[0]&&(c=null!=(l=g[b+1])?l[2]:void 0)){var m=c.first_line;c=c.first_column}else(c=null!=(m=g[b-1])?m[2]:void 0)?(m=c.last_line,c=c.last_column):m=c=0;a[2]={first_line:m,first_column:c,last_line:m,last_column:c};return 1})};l.prototype.fixOutdentLocationData=function(){return this.scanTokens(function(a,b,g){if(!(\"OUTDENT\"===a[0]||a.generated&&\n\"CALL_END\"===a[0]||a.generated&&\"}\"===a[0]))return 1;b=g[b-1][2];a[2]={first_line:b.last_line,first_column:b.last_column,last_line:b.last_line,last_column:b.last_column};return 1})};l.prototype.normalizeLines=function(){var b,g;var l=b=g=null;var k=function(a,b){var c,g,k,f;return\";\"!==a[1]&&(c=a[0],0<=y.call(O,c))&&!(\"TERMINATOR\"===a[0]&&(g=this.tag(b+1),0<=y.call(H,g)))&&!(\"ELSE\"===a[0]&&\"THEN\"!==l)&&!!(\"CATCH\"!==(k=a[0])&&\"FINALLY\"!==k||\"-\\x3e\"!==l&&\"\\x3d\\x3e\"!==l)||(f=a[0],0<=y.call(z,f))&&(this.tokens[b-\n1].newLine||\"OUTDENT\"===this.tokens[b-1][0])};var f=function(a,b){return this.tokens.splice(\",\"===this.tag(b-1)?b-1:b,0,g)};return this.scanTokens(function(c,m,h){var w,n,r;c=c[0];if(\"TERMINATOR\"===c){if(\"ELSE\"===this.tag(m+1)&&\"OUTDENT\"!==this.tag(m-1))return h.splice.apply(h,[m,1].concat(a.call(this.indentation()))),1;if(w=this.tag(m+1),0<=y.call(H,w))return h.splice(m,1),0}if(\"CATCH\"===c)for(w=n=1;2>=n;w=++n)if(\"OUTDENT\"===(r=this.tag(m+w))||\"TERMINATOR\"===r||\"FINALLY\"===r)return h.splice.apply(h,\n[m+w,0].concat(a.call(this.indentation()))),2+w;0<=y.call(J,c)&&\"INDENT\"!==this.tag(m+1)&&(\"ELSE\"!==c||\"IF\"!==this.tag(m+1))&&(l=c,r=this.indentation(h[m]),b=r[0],g=r[1],\"THEN\"===l&&(b.fromThen=!0),h.splice(m+1,0,b),this.detectEnd(m+2,k,f),\"THEN\"===c&&h.splice(m,1));return 1})};l.prototype.tagPostfixConditionals=function(){var a=null;var b=function(a,b){a=a[0];b=this.tokens[b-1][0];return\"TERMINATOR\"===a||\"INDENT\"===a&&0>y.call(J,b)};var g=function(b,c){if(\"INDENT\"!==b[0]||b.generated&&!b.fromThen)return a[0]=\n\"POST_\"+a[0]};return this.scanTokens(function(c,l){if(\"IF\"!==c[0])return 1;a=c;this.detectEnd(l+1,b,g);return 1})};l.prototype.indentation=function(a){var b=[\"INDENT\",2];var c=[\"OUTDENT\",2];a?(b.generated=c.generated=!0,b.origin=c.origin=a):b.explicit=c.explicit=!0;return[b,c]};l.prototype.generate=b;l.prototype.tag=function(a){var b;return null!=(b=this.tokens[a])?b[0]:void 0};return l}();var ya=[[\"(\",\")\"],[\"[\",\"]\"],[\"{\",\"}\"],[\"INDENT\",\"OUTDENT\"],[\"CALL_START\",\"CALL_END\"],[\"PARAM_START\",\"PARAM_END\"],\n[\"INDEX_START\",\"INDEX_END\"],[\"STRING_START\",\"STRING_END\"],[\"REGEX_START\",\"REGEX_END\"]];f.INVERSES=u={};var g=[];var h=[];var r=0;for(q=ya.length;r<q;r++){var n=ya[r];var B=n[0];n=n[1];g.push(u[n]=B);h.push(u[B]=n)}var H=[\"CATCH\",\"THEN\",\"ELSE\",\"FINALLY\"].concat(h);var I=\"IDENTIFIER PROPERTY SUPER ) CALL_END ] INDEX_END @ THIS\".split(\" \");var F=\"IDENTIFIER PROPERTY NUMBER INFINITY NAN STRING STRING_START REGEX REGEX_START JS NEW PARAM_START CLASS IF TRY SWITCH THIS UNDEFINED NULL BOOL UNARY YIELD UNARY_MATH SUPER THROW @ -\\x3e \\x3d\\x3e [ ( { -- ++\".split(\" \");\nvar Q=[\"+\",\"-\"];var x=\"POST_IF FOR WHILE UNTIL WHEN BY LOOP TERMINATOR\".split(\" \");var J=\"ELSE -\\x3e \\x3d\\x3e TRY FINALLY THEN\".split(\" \");var O=\"TERMINATOR CATCH FINALLY ELSE OUTDENT LEADING_WHEN\".split(\" \");var R=[\"TERMINATOR\",\"INDENT\",\"OUTDENT\"];var z=[\".\",\"?.\",\"::\",\"?::\"]}).call(this);return f}();u[\"./lexer\"]=function(){var f={};(function(){var qa,q=[].indexOf||function(a){for(var N=0,b=this.length;N<b;N++)if(N in this&&this[N]===a)return N;return-1},y=[].slice;var a=u(\"./rewriter\");var b=a.Rewriter;\nvar ya=a.INVERSES;a=u(\"./helpers\");var g=a.count;var h=a.repeat;var r=a.invertLiterate;var n=a.throwSyntaxError;f.Lexer=function(){function a(){}a.prototype.tokenize=function(a,c){var N,g;null==c&&(c={});this.literate=c.literate;this.outdebt=this.indebt=this.baseIndent=this.indent=0;this.indents=[];this.ends=[];this.tokens=[];this.exportSpecifierList=this.importSpecifierList=this.seenExport=this.seenImport=this.seenFor=!1;this.chunkLine=c.line||0;this.chunkColumn=c.column||0;a=this.clean(a);for(g=\n0;this.chunk=a.slice(g);){var l=this.identifierToken()||this.commentToken()||this.whitespaceToken()||this.lineToken()||this.stringToken()||this.numberToken()||this.regexToken()||this.jsToken()||this.literalToken();var k=this.getLineAndColumnFromChunk(l);this.chunkLine=k[0];this.chunkColumn=k[1];g+=l;if(c.untilBalanced&&0===this.ends.length)return{tokens:this.tokens,index:g}}this.closeIndentation();(N=this.ends.pop())&&this.error(\"missing \"+N.tag,N.origin[2]);return!1===c.rewrite?this.tokens:(new b).rewrite(this.tokens)};\na.prototype.clean=function(a){a.charCodeAt(0)===R&&(a=a.slice(1));a=a.replace(/\\r/g,\"\").replace(Z,\"\");w.test(a)&&(a=\"\\n\"+a,this.chunkLine--);this.literate&&(a=r(a));return a};a.prototype.identifierToken=function(){var a,b,c,g,l,k,m;if(!(a=z.exec(this.chunk)))return 0;var f=a[0];var h=a[1];a=a[2];var y=h.length;var w=void 0;if(\"own\"===h&&\"FOR\"===this.tag())return this.token(\"OWN\",h),h.length;if(\"from\"===h&&\"YIELD\"===this.tag())return this.token(\"FROM\",h),h.length;if(\"as\"===h&&this.seenImport){if(\"*\"===\nthis.value())this.tokens[this.tokens.length-1][0]=\"IMPORT_ALL\";else if(b=this.value(),0<=q.call(F,b))this.tokens[this.tokens.length-1][0]=\"IDENTIFIER\";if(\"DEFAULT\"===(c=this.tag())||\"IMPORT_ALL\"===c||\"IDENTIFIER\"===c)return this.token(\"AS\",h),h.length}if(\"as\"===h&&this.seenExport&&(\"IDENTIFIER\"===(g=this.tag())||\"DEFAULT\"===g))return this.token(\"AS\",h),h.length;if(\"default\"===h&&this.seenExport&&(\"EXPORT\"===(l=this.tag())||\"AS\"===l))return this.token(\"DEFAULT\",h),h.length;b=this.tokens;b=b[b.length-\n1];var n=a||null!=b&&(\".\"===(k=b[0])||\"?.\"===k||\"::\"===k||\"?::\"===k||!b.spaced&&\"@\"===b[0])?\"PROPERTY\":\"IDENTIFIER\";\"IDENTIFIER\"!==n||!(0<=q.call(I,h)||0<=q.call(F,h))||this.exportSpecifierList&&0<=q.call(F,h)?\"IDENTIFIER\"===n&&this.seenFor&&\"from\"===h&&H(b)&&(n=\"FORFROM\",this.seenFor=!1):(n=h.toUpperCase(),\"WHEN\"===n&&(m=this.tag(),0<=q.call(ra,m))?n=\"LEADING_WHEN\":\"FOR\"===n?this.seenFor=!0:\"UNLESS\"===n?n=\"IF\":\"IMPORT\"===n?this.seenImport=!0:\"EXPORT\"===n?this.seenExport=!0:0<=q.call(ia,n)?n=\"UNARY\":\n0<=q.call(pa,n)&&(\"INSTANCEOF\"!==n&&this.seenFor?(n=\"FOR\"+n,this.seenFor=!1):(n=\"RELATION\",\"!\"===this.value()&&(w=this.tokens.pop(),h=\"!\"+h))));\"IDENTIFIER\"===n&&0<=q.call(J,h)&&this.error(\"reserved word '\"+h+\"'\",{length:h.length});if(\"PROPERTY\"!==n){if(0<=q.call(x,h)){var r=h;h=Q[h]}n=function(){switch(h){case \"!\":return\"UNARY\";case \"\\x3d\\x3d\":case \"!\\x3d\":return\"COMPARE\";case \"true\":case \"false\":return\"BOOL\";case \"break\":case \"continue\":case \"debugger\":return\"STATEMENT\";case \"\\x26\\x26\":case \"||\":return h;\ndefault:return n}}()}k=this.token(n,h,0,y);r&&(k.origin=[n,r,k[2]]);w&&(r=[w[2].first_line,w[2].first_column],k[2].first_line=r[0],k[2].first_column=r[1]);a&&(r=f.lastIndexOf(\":\"),this.token(\":\",\":\",r,a.length));return f.length};a.prototype.numberToken=function(){var a,b;if(!(a=l.exec(this.chunk)))return 0;var c=a[0];a=c.length;switch(!1){case !/^0[BOX]/.test(c):this.error(\"radix prefix in '\"+c+\"' must be lowercase\",{offset:1});break;case !/^(?!0x).*E/.test(c):this.error(\"exponential notation in '\"+\nc+\"' must be indicated with a lowercase 'e'\",{offset:c.indexOf(\"E\")});break;case !/^0\\d*[89]/.test(c):this.error(\"decimal literal '\"+c+\"' must not be prefixed with '0'\",{length:a});break;case !/^0\\d+/.test(c):this.error(\"octal literal '\"+c+\"' must be prefixed with '0o'\",{length:a})}var g=function(){switch(c.charAt(1)){case \"b\":return 2;case \"o\":return 8;case \"x\":return 16;default:return null}}();g=null!=g?parseInt(c.slice(2),g):parseFloat(c);if(\"b\"===(b=c.charAt(1))||\"o\"===b)c=\"0x\"+g.toString(16);\nthis.token(Infinity===g?\"INFINITY\":\"NUMBER\",c,0,a);return a};a.prototype.stringToken=function(){var a,b,c,g,l;var k=(V.exec(this.chunk)||[])[0];if(!k)return 0;this.tokens.length&&\"from\"===this.value()&&(this.seenImport||this.seenExport)&&(this.tokens[this.tokens.length-1][0]=\"FROM\");var h=function(){switch(k){case \"'\":return X;case '\"':return G;case \"'''\":return aa;case '\"\"\"':return U}}();var m=3===k.length;h=this.matchWithInterpolations(h,k);var f=h.tokens;var n=h.index;var y=f.length-1;h=k.charAt(0);\nif(m){var w=null;for(m=function(){var a,c;var N=[];b=a=0;for(c=f.length;a<c;b=++a)l=f[b],\"NEOSTRING\"===l[0]&&N.push(l[1]);return N}().join(\"#{}\");a=A.exec(m);)if(a=a[1],null===w||0<(g=a.length)&&g<w.length)w=a;w&&(c=RegExp(\"\\\\n\"+w,\"g\"));this.mergeInterpolationTokens(f,{delimiter:h},function(a){return function(b,N){b=a.formatString(b,{delimiter:k});c&&(b=b.replace(c,\"\\n\"));0===N&&(b=b.replace(za,\"\"));N===y&&(b=b.replace(ma,\"\"));return b}}(this))}else this.mergeInterpolationTokens(f,{delimiter:h},function(a){return function(b,\nN){b=a.formatString(b,{delimiter:k});return b=b.replace(D,function(a,p){return 0===N&&0===p||N===y&&p+a.length===b.length?\"\":\" \"})}}(this));return n};a.prototype.commentToken=function(){var a,b;if(!(b=this.chunk.match(m)))return 0;var c=b[0];if(a=b[1])(b=Y.exec(c))&&this.error(\"block comments cannot contain \"+b[0],{offset:b.index,length:b[0].length}),0<=a.indexOf(\"\\n\")&&(a=a.replace(RegExp(\"\\\\n\"+h(\" \",this.indent),\"g\"),\"\\n\")),this.token(\"HERECOMMENT\",a,0,c.length);return c.length};a.prototype.jsToken=\nfunction(){var a;if(\"`\"!==this.chunk.charAt(0)||!(a=L.exec(this.chunk)||P.exec(this.chunk)))return 0;var b=a[1].replace(/\\\\+(`|$)/g,function(a){return a.slice(-Math.ceil(a.length/2))});this.token(\"JS\",b,0,a[0].length);return a[0].length};a.prototype.regexToken=function(){var a,b,c;switch(!1){case !(a=T.exec(this.chunk)):this.error(\"regular expressions cannot begin with \"+a[2],{offset:a.index+a[1].length});break;case !(a=this.matchWithInterpolations(ca,\"///\")):var g=a.tokens;var k=a.index;break;case !(a=\nfc.exec(this.chunk)):var l=a[0];var h=a[1];a=a[2];this.validateEscapes(h,{isRegex:!0,offsetInChunk:1});h=this.formatRegex(h,{delimiter:\"/\"});k=l.length;var m=this.tokens;if(m=m[m.length-1])if(m.spaced&&(b=m[0],0<=q.call(ha,b))){if(!a||v.test(l))return 0}else if(c=m[0],0<=q.call(na,c))return 0;a||this.error(\"missing / (unclosed regex)\");break;default:return 0}c=E.exec(this.chunk.slice(k))[0];b=k+c.length;a=this.makeToken(\"REGEX\",null,0,b);switch(!1){case !!ba.test(c):this.error(\"invalid regular expression flags \"+\nc,{offset:k,length:c.length});break;case !(l||1===g.length):null==h&&(h=this.formatHeregex(g[0][1]));this.token(\"REGEX\",\"\"+this.makeDelimitedLiteral(h,{delimiter:\"/\"})+c,0,b,a);break;default:this.token(\"REGEX_START\",\"(\",0,0,a),this.token(\"IDENTIFIER\",\"RegExp\",0,0),this.token(\"CALL_START\",\"(\",0,0),this.mergeInterpolationTokens(g,{delimiter:'\"',double:!0},this.formatHeregex),c&&(this.token(\",\",\",\",k-1,0),this.token(\"STRING\",'\"'+c+'\"',k-1,c.length)),this.token(\")\",\")\",b-1,0),this.token(\"REGEX_END\",\")\",\nb-1,0)}return b};a.prototype.lineToken=function(){var a;if(!(a=K.exec(this.chunk)))return 0;a=a[0];this.seenFor=!1;this.importSpecifierList||(this.seenImport=!1);this.exportSpecifierList||(this.seenExport=!1);var b=a.length-1-a.lastIndexOf(\"\\n\");var c=this.unfinished();if(b-this.indebt===this.indent)return c?this.suppressNewlines():this.newlineToken(0),a.length;if(b>this.indent){if(c||\"RETURN\"===this.tag())return this.indebt=b-this.indent,this.suppressNewlines(),a.length;if(!this.tokens.length)return this.baseIndent=\nthis.indent=b,a.length;c=b-this.indent+this.outdebt;this.token(\"INDENT\",c,a.length-b,b);this.indents.push(c);this.ends.push({tag:\"OUTDENT\"});this.outdebt=this.indebt=0;this.indent=b}else b<this.baseIndent?this.error(\"missing indentation\",{offset:a.length}):(this.indebt=0,this.outdentToken(this.indent-b,c,a.length));return a.length};a.prototype.outdentToken=function(a,b,c){var g,N,k;for(g=this.indent-a;0<a;)if(N=this.indents[this.indents.length-1])if(N===this.outdebt)a-=this.outdebt,this.outdebt=0;\nelse if(N<this.outdebt)this.outdebt-=N,a-=N;else{var h=this.indents.pop()+this.outdebt;c&&(k=this.chunk[c],0<=q.call(da,k))&&(g-=h-a,a=h);this.outdebt=0;this.pair(\"OUTDENT\");this.token(\"OUTDENT\",a,0,c);a-=h}else a=0;h&&(this.outdebt-=a);for(;\";\"===this.value();)this.tokens.pop();\"TERMINATOR\"===this.tag()||b||this.token(\"TERMINATOR\",\"\\n\",c,0);this.indent=g;return this};a.prototype.whitespaceToken=function(){var a;if(!(a=w.exec(this.chunk))&&\"\\n\"!==this.chunk.charAt(0))return 0;var b=this.tokens;(b=\nb[b.length-1])&&(b[a?\"spaced\":\"newLine\"]=!0);return a?a[0].length:0};a.prototype.newlineToken=function(a){for(;\";\"===this.value();)this.tokens.pop();\"TERMINATOR\"!==this.tag()&&this.token(\"TERMINATOR\",\"\\n\",a,0);return this};a.prototype.suppressNewlines=function(){\"\\\\\"===this.value()&&this.tokens.pop();return this};a.prototype.literalToken=function(){var a,b,g,h,l;(a=c.exec(this.chunk))?(a=a[0],k.test(a)&&this.tagParameters()):a=this.chunk.charAt(0);var m=a;var f=this.tokens;if((f=f[f.length-1])&&0<=\nq.call([\"\\x3d\"].concat(y.call(fa)),a)){var n=!1;\"\\x3d\"!==a||\"||\"!==(g=f[1])&&\"\\x26\\x26\"!==g||f.spaced||(f[0]=\"COMPOUND_ASSIGN\",f[1]+=\"\\x3d\",f=this.tokens[this.tokens.length-2],n=!0);f&&\"PROPERTY\"!==f[0]&&(g=null!=(b=f.origin)?b:f,(b=B(f[1],g[1]))&&this.error(b,g[2]));if(n)return a.length}\"{\"===a&&this.seenImport?this.importSpecifierList=!0:this.importSpecifierList&&\"}\"===a?this.importSpecifierList=!1:\"{\"===a&&\"EXPORT\"===(null!=f?f[0]:void 0)?this.exportSpecifierList=!0:this.exportSpecifierList&&\"}\"===\na&&(this.exportSpecifierList=!1);if(\";\"===a)this.seenFor=this.seenImport=this.seenExport=!1,m=\"TERMINATOR\";else if(\"*\"===a&&\"EXPORT\"===f[0])m=\"EXPORT_ALL\";else if(0<=q.call(oa,a))m=\"MATH\";else if(0<=q.call(la,a))m=\"COMPARE\";else if(0<=q.call(fa,a))m=\"COMPOUND_ASSIGN\";else if(0<=q.call(ia,a))m=\"UNARY\";else if(0<=q.call(ga,a))m=\"UNARY_MATH\";else if(0<=q.call(ja,a))m=\"SHIFT\";else if(\"?\"===a&&null!=f&&f.spaced)m=\"BIN?\";else if(f&&!f.spaced)if(\"(\"===a&&(h=f[0],0<=q.call(ha,h)))\"?\"===f[0]&&(f[0]=\"FUNC_EXIST\"),\nm=\"CALL_START\";else if(\"[\"===a&&(l=f[0],0<=q.call(ka,l)))switch(m=\"INDEX_START\",f[0]){case \"?\":f[0]=\"INDEX_SOAK\"}h=this.makeToken(m,a);switch(a){case \"(\":case \"{\":case \"[\":this.ends.push({tag:ya[a],origin:h});break;case \")\":case \"}\":case \"]\":this.pair(a)}this.tokens.push(h);return a.length};a.prototype.tagParameters=function(){var a;if(\")\"!==this.tag())return this;var b=[];var c=this.tokens;var g=c.length;for(c[--g][0]=\"PARAM_END\";a=c[--g];)switch(a[0]){case \")\":b.push(a);break;case \"(\":case \"CALL_START\":if(b.length)b.pop();\nelse return\"(\"===a[0]&&(a[0]=\"PARAM_START\"),this}return this};a.prototype.closeIndentation=function(){return this.outdentToken(this.indent)};a.prototype.matchWithInterpolations=function(b,c){var g,h;var k=[];var l=c.length;if(this.chunk.slice(0,l)!==c)return null;for(h=this.chunk.slice(l);;){var f=b.exec(h)[0];this.validateEscapes(f,{isRegex:\"/\"===c.charAt(0),offsetInChunk:l});k.push(this.makeToken(\"NEOSTRING\",f,l));h=h.slice(f.length);l+=f.length;if(\"#{\"!==h.slice(0,2))break;var m=this.getLineAndColumnFromChunk(l+\n1);f=m[0];m=m[1];m=(new a).tokenize(h.slice(1),{line:f,column:m,untilBalanced:!0});f=m.tokens;var N=m.index;N+=1;var n=f[0];m=f[f.length-1];n[0]=n[1]=\"(\";m[0]=m[1]=\")\";m.origin=[\"\",\"end of interpolation\",m[2]];\"TERMINATOR\"===(null!=(g=f[1])?g[0]:void 0)&&f.splice(1,1);k.push([\"TOKENS\",f]);h=h.slice(N);l+=N}h.slice(0,c.length)!==c&&this.error(\"missing \"+c,{length:c.length});b=k[0];g=k[k.length-1];b[2].first_column-=c.length;\"\\n\"===g[1].substr(-1)?(g[2].last_line+=1,g[2].last_column=c.length-1):g[2].last_column+=\nc.length;0===g[1].length&&--g[2].last_column;return{tokens:k,index:l+c.length}};a.prototype.mergeInterpolationTokens=function(a,b,c){var g,h,k,f;1<a.length&&(k=this.token(\"STRING_START\",\"(\",0,0));var l=this.tokens.length;var m=g=0;for(h=a.length;g<h;m=++g){var n=a[m];var N=n[0];var y=n[1];switch(N){case \"TOKENS\":if(2===y.length)continue;var w=y[0];var r=y;break;case \"NEOSTRING\":N=c.call(this,n[1],m);if(0===N.length)if(0===m)var Ha=this.tokens.length;else continue;2===m&&null!=Ha&&this.tokens.splice(Ha,\n2);n[0]=\"STRING\";n[1]=this.makeDelimitedLiteral(N,b);w=n;r=[n]}this.tokens.length>l&&(m=this.token(\"+\",\"+\"),m[2]={first_line:w[2].first_line,first_column:w[2].first_column,last_line:w[2].first_line,last_column:w[2].first_column});(f=this.tokens).push.apply(f,r)}if(k)return a=a[a.length-1],k.origin=[\"STRING\",null,{first_line:k[2].first_line,first_column:k[2].first_column,last_line:a[2].last_line,last_column:a[2].last_column}],k=this.token(\"STRING_END\",\")\"),k[2]={first_line:a[2].last_line,first_column:a[2].last_column,\nlast_line:a[2].last_line,last_column:a[2].last_column}};a.prototype.pair=function(a){var b=this.ends;b=b[b.length-1];return a!==(b=null!=b?b.tag:void 0)?(\"OUTDENT\"!==b&&this.error(\"unmatched \"+a),b=this.indents,b=b[b.length-1],this.outdentToken(b,!0),this.pair(a)):this.ends.pop()};a.prototype.getLineAndColumnFromChunk=function(a){if(0===a)return[this.chunkLine,this.chunkColumn];var b=a>=this.chunk.length?this.chunk:this.chunk.slice(0,+(a-1)+1||9E9);a=g(b,\"\\n\");var c=this.chunkColumn;0<a?(c=b.split(\"\\n\"),\nc=c[c.length-1],c=c.length):c+=b.length;return[this.chunkLine+a,c]};a.prototype.makeToken=function(a,b,c,g){null==c&&(c=0);null==g&&(g=b.length);var k={};var h=this.getLineAndColumnFromChunk(c);k.first_line=h[0];k.first_column=h[1];c=this.getLineAndColumnFromChunk(c+(0<g?g-1:0));k.last_line=c[0];k.last_column=c[1];return[a,b,k]};a.prototype.token=function(a,b,c,g,k){a=this.makeToken(a,b,c,g);k&&(a.origin=k);this.tokens.push(a);return a};a.prototype.tag=function(){var a=this.tokens;a=a[a.length-1];\nreturn null!=a?a[0]:void 0};a.prototype.value=function(){var a=this.tokens;a=a[a.length-1];return null!=a?a[1]:void 0};a.prototype.unfinished=function(){var a;return S.test(this.chunk)||\"\\\\\"===(a=this.tag())||\".\"===a||\"?.\"===a||\"?::\"===a||\"UNARY\"===a||\"MATH\"===a||\"UNARY_MATH\"===a||\"+\"===a||\"-\"===a||\"**\"===a||\"SHIFT\"===a||\"RELATION\"===a||\"COMPARE\"===a||\"\\x26\"===a||\"^\"===a||\"|\"===a||\"\\x26\\x26\"===a||\"||\"===a||\"BIN?\"===a||\"THROW\"===a||\"EXTENDS\"===a||\"DEFAULT\"===a};a.prototype.formatString=function(a,\nb){return this.replaceUnicodeCodePointEscapes(a.replace(W,\"$1\"),b)};a.prototype.formatHeregex=function(a){return this.formatRegex(a.replace(C,\"$1$2\"),{delimiter:\"///\"})};a.prototype.formatRegex=function(a,b){return this.replaceUnicodeCodePointEscapes(a,b)};a.prototype.unicodeCodePointToUnicodeEscapes=function(a){var b=function(a){a=a.toString(16);return\"\\\\u\"+h(\"0\",4-a.length)+a};if(65536>a)return b(a);var c=Math.floor((a-65536)/1024)+55296;a=(a-65536)%1024+56320;return\"\"+b(c)+b(a)};a.prototype.replaceUnicodeCodePointEscapes=\nfunction(a,b){return a.replace(sa,function(a){return function(c,g,k,h){if(g)return g;c=parseInt(k,16);1114111<c&&a.error(\"unicode code point escapes greater than \\\\u{10ffff} are not allowed\",{offset:h+b.delimiter.length,length:k.length+4});return a.unicodeCodePointToUnicodeEscapes(c)}}(this))};a.prototype.validateEscapes=function(a,b){var c,g;null==b&&(b={});if(c=(b.isRegex?va:M).exec(a)){c[0];a=c[1];var k=c[2];var h=c[3];var f=c[4];var l=c[5];h=\"\\\\\"+(k||h||f||l);return this.error((k?\"octal escape sequences are not allowed\":\n\"invalid escape sequence\")+\" \"+h,{offset:(null!=(g=b.offsetInChunk)?g:0)+c.index+a.length,length:h.length})}};a.prototype.makeDelimitedLiteral=function(a,b){null==b&&(b={});\"\"===a&&\"/\"===b.delimiter&&(a=\"(?:)\");a=a.replace(RegExp(\"(\\\\\\\\\\\\\\\\)|(\\\\\\\\0(?\\x3d[1-7]))|\\\\\\\\?(\"+b.delimiter+\")|\\\\\\\\?(?:(\\\\n)|(\\\\r)|(\\\\u2028)|(\\\\u2029))|(\\\\\\\\.)\",\"g\"),function(a,c,g,k,h,f,l,m,n){switch(!1){case !c:return b.double?c+c:c;case !g:return\"\\\\x00\";case !k:return\"\\\\\"+k;case !h:return\"\\\\n\";case !f:return\"\\\\r\";case !l:return\"\\\\u2028\";\ncase !m:return\"\\\\u2029\";case !n:return b.double?\"\\\\\"+n:n}});return\"\"+b.delimiter+a+b.delimiter};a.prototype.error=function(a,b){var c,g,k,h,f;null==b&&(b={});b=\"first_line\"in b?b:(h=this.getLineAndColumnFromChunk(null!=(k=b.offset)?k:0),g=h[0],c=h[1],h,{first_line:g,first_column:c,last_column:c+(null!=(f=b.length)?f:1)-1});return n(a,b)};return a}();var B=function(a,b){null==b&&(b=a);switch(!1){case 0>q.call(y.call(I).concat(y.call(F)),a):return\"keyword '\"+b+\"' can't be assigned\";case 0>q.call(O,\na):return\"'\"+b+\"' can't be assigned\";case 0>q.call(J,a):return\"reserved word '\"+b+\"' can't be assigned\";default:return!1}};f.isUnassignable=B;var H=function(a){var b;return\"IDENTIFIER\"===a[0]?(\"from\"===a[1]&&(a[1][0]=\"IDENTIFIER\",!0),!0):\"FOR\"===a[0]?!1:\"{\"===(b=a[1])||\"[\"===b||\",\"===b||\":\"===b?!1:!0};var I=\"true false null this new delete typeof in instanceof return throw break continue debugger yield if else switch for while do try catch finally class extends super import export default\".split(\" \");\nvar F=\"undefined Infinity NaN then unless until loop of by when\".split(\" \");var Q={and:\"\\x26\\x26\",or:\"||\",is:\"\\x3d\\x3d\",isnt:\"!\\x3d\",not:\"!\",yes:\"true\",no:\"false\",on:\"true\",off:\"false\"};var x=function(){var a=[];for(qa in Q)a.push(qa);return a}();F=F.concat(x);var J=\"case function var void with const let enum native implements interface package private protected public static\".split(\" \");var O=[\"arguments\",\"eval\"];f.JS_FORBIDDEN=I.concat(J).concat(O);var R=65279;var z=/^(?!\\d)((?:(?!\\s)[$\\w\\x7f-\\uffff])+)([^\\n\\S]*:(?!:))?/;\nvar l=/^0b[01]+|^0o[0-7]+|^0x[\\da-f]+|^\\d*\\.?\\d+(?:e[+-]?\\d+)?/i;var c=/^(?:[-=]>|[-+*\\/%<>&|^!?=]=|>>>=?|([-+:])\\1|([&|<>*\\/%])\\2=?|\\?(\\.|::)|\\.{2,3})/;var w=/^[^\\n\\S]+/;var m=/^###([^#][\\s\\S]*?)(?:###[^\\n\\S]*|###$)|^(?:\\s*#(?!##[^#]).*)+/;var k=/^[-=]>/;var K=/^(?:\\n[^\\n\\S]*)+/;var P=/^`(?!``)((?:[^`\\\\]|\\\\[\\s\\S])*)`/;var L=/^```((?:[^`\\\\]|\\\\[\\s\\S]|`(?!``))*)```/;var V=/^(?:'''|\"\"\"|'|\")/;var X=/^(?:[^\\\\']|\\\\[\\s\\S])*/;var G=/^(?:[^\\\\\"#]|\\\\[\\s\\S]|\\#(?!\\{))*/;var aa=/^(?:[^\\\\']|\\\\[\\s\\S]|'(?!''))*/;\nvar U=/^(?:[^\\\\\"#]|\\\\[\\s\\S]|\"(?!\"\")|\\#(?!\\{))*/;var W=/((?:\\\\\\\\)+)|\\\\[^\\S\\n]*\\n\\s*/g;var D=/\\s*\\n\\s*/g;var A=/\\n+([^\\n\\S]*)(?=\\S)/g;var fc=/^\\/(?!\\/)((?:[^[\\/\\n\\\\]|\\\\[^\\n]|\\[(?:\\\\[^\\n]|[^\\]\\n\\\\])*\\])*)(\\/)?/;var E=/^\\w*/;var ba=/^(?!.*(.).*\\1)[imguy]*$/;var ca=/^(?:[^\\\\\\/#]|\\\\[\\s\\S]|\\/(?!\\/\\/)|\\#(?!\\{))*/;var C=/((?:\\\\\\\\)+)|\\\\(\\s)|\\s+(?:#.*)?/g;var T=/^(\\/|\\/{3}\\s*)(\\*)/;var v=/^\\/=?\\s/;var Y=/\\*\\//;var S=/^\\s*(?:,|\\??\\.(?![.\\d])|::)/;var M=/((?:^|[^\\\\])(?:\\\\\\\\)*)\\\\(?:(0[0-7]|[1-7])|(x(?![\\da-fA-F]{2}).{0,2})|(u\\{(?![\\da-fA-F]{1,}\\})[^}]*\\}?)|(u(?!\\{|[\\da-fA-F]{4}).{0,4}))/;\nvar va=/((?:^|[^\\\\])(?:\\\\\\\\)*)\\\\(?:(0[0-7])|(x(?![\\da-fA-F]{2}).{0,2})|(u\\{(?![\\da-fA-F]{1,}\\})[^}]*\\}?)|(u(?!\\{|[\\da-fA-F]{4}).{0,4}))/;var sa=/(\\\\\\\\)|\\\\u\\{([\\da-fA-F]+)\\}/g;var za=/^[^\\n\\S]*\\n/;var ma=/\\n[^\\n\\S]*$/;var Z=/\\s+$/;var fa=\"-\\x3d +\\x3d /\\x3d *\\x3d %\\x3d ||\\x3d \\x26\\x26\\x3d ?\\x3d \\x3c\\x3c\\x3d \\x3e\\x3e\\x3d \\x3e\\x3e\\x3e\\x3d \\x26\\x3d ^\\x3d |\\x3d **\\x3d //\\x3d %%\\x3d\".split(\" \");var ia=[\"NEW\",\"TYPEOF\",\"DELETE\",\"DO\"];var ga=[\"!\",\"~\"];var ja=[\"\\x3c\\x3c\",\"\\x3e\\x3e\",\"\\x3e\\x3e\\x3e\"];var la=\"\\x3d\\x3d !\\x3d \\x3c \\x3e \\x3c\\x3d \\x3e\\x3d\".split(\" \");\nvar oa=[\"*\",\"/\",\"%\",\"//\",\"%%\"];var pa=[\"IN\",\"OF\",\"INSTANCEOF\"];var ha=\"IDENTIFIER PROPERTY ) ] ? @ THIS SUPER\".split(\" \");var ka=ha.concat(\"NUMBER INFINITY NAN STRING STRING_END REGEX REGEX_END BOOL NULL UNDEFINED } ::\".split(\" \"));var na=ka.concat([\"++\",\"--\"]);var ra=[\"INDENT\",\"OUTDENT\",\"TERMINATOR\"];var da=[\")\",\"}\",\"]\"]}).call(this);return f}();u[\"./parser\"]=function(){var f={},qa={exports:f},q=function(){function f(){this.yy={}}var a=function(a,p,t,d){t=t||{};for(d=a.length;d--;t[a[d]]=p);return t},\nb=[1,22],u=[1,25],g=[1,83],h=[1,79],r=[1,84],n=[1,85],B=[1,81],H=[1,82],I=[1,56],F=[1,58],Q=[1,59],x=[1,60],J=[1,61],O=[1,62],R=[1,49],z=[1,50],l=[1,32],c=[1,68],w=[1,69],m=[1,78],k=[1,47],K=[1,51],P=[1,52],L=[1,67],V=[1,65],X=[1,66],G=[1,64],aa=[1,42],U=[1,48],W=[1,63],D=[1,73],A=[1,74],q=[1,75],E=[1,76],ba=[1,46],ca=[1,72],C=[1,34],T=[1,35],v=[1,36],Y=[1,37],S=[1,38],M=[1,39],qa=[1,86],sa=[1,6,32,42,131],za=[1,101],ma=[1,89],Z=[1,88],fa=[1,87],ia=[1,90],ga=[1,91],ja=[1,92],la=[1,93],oa=[1,94],pa=\n[1,95],ha=[1,96],ka=[1,97],na=[1,98],ra=[1,99],da=[1,100],va=[1,104],N=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],xa=[2,166],ta=[1,110],Na=[1,111],Fa=[1,112],Ga=[1,113],Ca=[1,115],Pa=[1,116],Ia=[1,109],Ea=[1,6,32,42,131,133,135,139,156],Va=[2,27],ea=[1,123],Ya=[1,121],Ba=[1,6,31,32,40,41,42,65,70,73,82,83,84,85,87,89,90,94,113,114,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,\n173,174],Ha=[2,94],t=[1,6,31,32,42,46,65,70,73,82,83,84,85,87,89,90,94,113,114,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],p=[2,73],d=[1,128],wa=[1,133],e=[1,134],Da=[1,136],Ta=[1,6,31,32,40,41,42,55,65,70,73,82,83,84,85,87,89,90,94,113,114,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],ua=[2,91],Eb=[1,6,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,\n169,170,171,172,173,174],Za=[2,63],Fb=[1,166],$a=[1,178],Ua=[1,180],Gb=[1,175],Oa=[1,182],sb=[1,184],La=[1,6,31,32,40,41,42,55,65,70,73,82,83,84,85,87,89,90,94,96,113,114,115,120,122,131,133,134,135,139,140,156,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175],Hb=[2,110],Ib=[1,6,31,32,40,41,42,58,65,70,73,82,83,84,85,87,89,90,94,113,114,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],Jb=[1,6,31,32,40,41,42,46,58,65,70,73,82,83,84,\n85,87,89,90,94,113,114,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],Kb=[40,41,114],Lb=[1,241],tb=[1,240],Ma=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156],Ja=[2,71],Mb=[1,250],Sa=[6,31,32,65,70],fb=[6,31,32,55,65,70,73],ab=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,159,160,164,166,167,168,169,170,171,172,173,174],Nb=[40,41,82,83,84,85,87,90,113,114],gb=[1,269],bb=[2,62],hb=[1,279],Wa=[1,281],ub=[1,\n286],cb=[1,288],Ob=[2,187],vb=[1,6,31,32,40,41,42,55,65,70,73,82,83,84,85,87,89,90,94,113,114,115,120,122,131,133,134,135,139,140,146,147,148,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],ib=[1,297],Qa=[6,31,32,70,115,120],Pb=[1,6,31,32,40,41,42,55,58,65,70,73,82,83,84,85,87,89,90,94,96,113,114,115,120,122,131,133,134,135,139,140,146,147,148,156,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175],Qb=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,140,156],Xa=[1,6,31,32,\n42,65,70,73,89,94,115,120,122,131,134,140,156],jb=[146,147,148],kb=[70,146,147,148],lb=[6,31,94],Rb=[1,311],Aa=[6,31,32,70,94],Sb=[6,31,32,58,70,94],wb=[6,31,32,55,58,70,94],Tb=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,159,160,166,167,168,169,170,171,172,173,174],Ub=[12,28,34,38,40,41,44,45,48,49,50,51,52,53,61,62,63,67,68,89,92,95,97,105,112,117,118,119,125,129,130,133,135,137,139,149,155,157,158,159,160,161,162],Vb=[2,176],Ra=[6,31,32],db=[2,72],Wb=[1,323],Xb=[1,324],\nYb=[1,6,31,32,42,65,70,73,89,94,115,120,122,127,128,131,133,134,135,139,140,151,153,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],mb=[32,151,153],Zb=[1,6,32,42,65,70,73,89,94,115,120,122,131,134,140,156],nb=[1,350],xb=[1,356],yb=[1,6,32,42,131,156],eb=[2,86],ob=[1,367],pb=[1,368],$b=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,151,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],zb=[1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,135,139,140,156],ac=\n[1,381],bc=[1,382],Ab=[6,31,32,94],cc=[6,31,32,70],Bb=[1,6,31,32,42,65,70,73,89,94,115,120,122,127,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],dc=[31,70],qb=[1,408],rb=[1,409],Cb=[1,415],Db=[1,416],ec={trace:function(){},yy:{},symbols_:{error:2,Root:3,Body:4,Line:5,TERMINATOR:6,Expression:7,Statement:8,YieldReturn:9,Return:10,Comment:11,STATEMENT:12,Import:13,Export:14,Value:15,Invocation:16,Code:17,Operation:18,Assign:19,If:20,Try:21,While:22,For:23,Switch:24,\nClass:25,Throw:26,Yield:27,YIELD:28,FROM:29,Block:30,INDENT:31,OUTDENT:32,Identifier:33,IDENTIFIER:34,Property:35,PROPERTY:36,AlphaNumeric:37,NUMBER:38,String:39,STRING:40,STRING_START:41,STRING_END:42,Regex:43,REGEX:44,REGEX_START:45,REGEX_END:46,Literal:47,JS:48,UNDEFINED:49,NULL:50,BOOL:51,INFINITY:52,NAN:53,Assignable:54,\"\\x3d\":55,AssignObj:56,ObjAssignable:57,\":\":58,SimpleObjAssignable:59,ThisProperty:60,RETURN:61,HERECOMMENT:62,PARAM_START:63,ParamList:64,PARAM_END:65,FuncGlyph:66,\"-\\x3e\":67,\n\"\\x3d\\x3e\":68,OptComma:69,\",\":70,Param:71,ParamVar:72,\"...\":73,Array:74,Object:75,Splat:76,SimpleAssignable:77,Accessor:78,Parenthetical:79,Range:80,This:81,\".\":82,\"?.\":83,\"::\":84,\"?::\":85,Index:86,INDEX_START:87,IndexValue:88,INDEX_END:89,INDEX_SOAK:90,Slice:91,\"{\":92,AssignList:93,\"}\":94,CLASS:95,EXTENDS:96,IMPORT:97,ImportDefaultSpecifier:98,ImportNamespaceSpecifier:99,ImportSpecifierList:100,ImportSpecifier:101,AS:102,DEFAULT:103,IMPORT_ALL:104,EXPORT:105,ExportSpecifierList:106,EXPORT_ALL:107,\nExportSpecifier:108,OptFuncExist:109,Arguments:110,Super:111,SUPER:112,FUNC_EXIST:113,CALL_START:114,CALL_END:115,ArgList:116,THIS:117,\"@\":118,\"[\":119,\"]\":120,RangeDots:121,\"..\":122,Arg:123,SimpleArgs:124,TRY:125,Catch:126,FINALLY:127,CATCH:128,THROW:129,\"(\":130,\")\":131,WhileSource:132,WHILE:133,WHEN:134,UNTIL:135,Loop:136,LOOP:137,ForBody:138,FOR:139,BY:140,ForStart:141,ForSource:142,ForVariables:143,OWN:144,ForValue:145,FORIN:146,FOROF:147,FORFROM:148,SWITCH:149,Whens:150,ELSE:151,When:152,LEADING_WHEN:153,\nIfBlock:154,IF:155,POST_IF:156,UNARY:157,UNARY_MATH:158,\"-\":159,\"+\":160,\"--\":161,\"++\":162,\"?\":163,MATH:164,\"**\":165,SHIFT:166,COMPARE:167,\"\\x26\":168,\"^\":169,\"|\":170,\"\\x26\\x26\":171,\"||\":172,\"BIN?\":173,RELATION:174,COMPOUND_ASSIGN:175,$accept:0,$end:1},terminals_:{2:\"error\",6:\"TERMINATOR\",12:\"STATEMENT\",28:\"YIELD\",29:\"FROM\",31:\"INDENT\",32:\"OUTDENT\",34:\"IDENTIFIER\",36:\"PROPERTY\",38:\"NUMBER\",40:\"STRING\",41:\"STRING_START\",42:\"STRING_END\",44:\"REGEX\",45:\"REGEX_START\",46:\"REGEX_END\",48:\"JS\",49:\"UNDEFINED\",\n50:\"NULL\",51:\"BOOL\",52:\"INFINITY\",53:\"NAN\",55:\"\\x3d\",58:\":\",61:\"RETURN\",62:\"HERECOMMENT\",63:\"PARAM_START\",65:\"PARAM_END\",67:\"-\\x3e\",68:\"\\x3d\\x3e\",70:\",\",73:\"...\",82:\".\",83:\"?.\",84:\"::\",85:\"?::\",87:\"INDEX_START\",89:\"INDEX_END\",90:\"INDEX_SOAK\",92:\"{\",94:\"}\",95:\"CLASS\",96:\"EXTENDS\",97:\"IMPORT\",102:\"AS\",103:\"DEFAULT\",104:\"IMPORT_ALL\",105:\"EXPORT\",107:\"EXPORT_ALL\",112:\"SUPER\",113:\"FUNC_EXIST\",114:\"CALL_START\",115:\"CALL_END\",117:\"THIS\",118:\"@\",119:\"[\",120:\"]\",122:\"..\",125:\"TRY\",127:\"FINALLY\",128:\"CATCH\",\n129:\"THROW\",130:\"(\",131:\")\",133:\"WHILE\",134:\"WHEN\",135:\"UNTIL\",137:\"LOOP\",139:\"FOR\",140:\"BY\",144:\"OWN\",146:\"FORIN\",147:\"FOROF\",148:\"FORFROM\",149:\"SWITCH\",151:\"ELSE\",153:\"LEADING_WHEN\",155:\"IF\",156:\"POST_IF\",157:\"UNARY\",158:\"UNARY_MATH\",159:\"-\",160:\"+\",161:\"--\",162:\"++\",163:\"?\",164:\"MATH\",165:\"**\",166:\"SHIFT\",167:\"COMPARE\",168:\"\\x26\",169:\"^\",170:\"|\",171:\"\\x26\\x26\",172:\"||\",173:\"BIN?\",174:\"RELATION\",175:\"COMPOUND_ASSIGN\"},productions_:[0,[3,0],[3,1],[4,1],[4,3],[4,2],[5,1],[5,1],[5,1],[8,1],[8,1],[8,\n1],[8,1],[8,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[7,1],[27,1],[27,2],[27,3],[30,2],[30,3],[33,1],[35,1],[37,1],[37,1],[39,1],[39,3],[43,1],[43,3],[47,1],[47,1],[47,1],[47,1],[47,1],[47,1],[47,1],[47,1],[19,3],[19,4],[19,5],[56,1],[56,3],[56,5],[56,3],[56,5],[56,1],[59,1],[59,1],[59,1],[57,1],[57,1],[10,2],[10,1],[9,3],[9,2],[11,1],[17,5],[17,2],[66,1],[66,1],[69,0],[69,1],[64,0],[64,1],[64,3],[64,4],[64,6],[71,1],[71,2],[71,3],[71,1],[72,1],[72,1],[72,1],[72,\n1],[76,2],[77,1],[77,2],[77,2],[77,1],[54,1],[54,1],[54,1],[15,1],[15,1],[15,1],[15,1],[15,1],[78,2],[78,2],[78,2],[78,2],[78,1],[78,1],[86,3],[86,2],[88,1],[88,1],[75,4],[93,0],[93,1],[93,3],[93,4],[93,6],[25,1],[25,2],[25,3],[25,4],[25,2],[25,3],[25,4],[25,5],[13,2],[13,4],[13,4],[13,5],[13,7],[13,6],[13,9],[100,1],[100,3],[100,4],[100,4],[100,6],[101,1],[101,3],[101,1],[101,3],[98,1],[99,3],[14,3],[14,5],[14,2],[14,4],[14,5],[14,6],[14,3],[14,4],[14,7],[106,1],[106,3],[106,4],[106,4],[106,6],[108,\n1],[108,3],[108,3],[108,1],[108,3],[16,3],[16,3],[16,3],[16,1],[111,1],[111,2],[109,0],[109,1],[110,2],[110,4],[81,1],[81,1],[60,2],[74,2],[74,4],[121,1],[121,1],[80,5],[91,3],[91,2],[91,2],[91,1],[116,1],[116,3],[116,4],[116,4],[116,6],[123,1],[123,1],[123,1],[124,1],[124,3],[21,2],[21,3],[21,4],[21,5],[126,3],[126,3],[126,2],[26,2],[79,3],[79,5],[132,2],[132,4],[132,2],[132,4],[22,2],[22,2],[22,2],[22,1],[136,2],[136,2],[23,2],[23,2],[23,2],[138,2],[138,4],[138,2],[141,2],[141,3],[145,1],[145,1],\n[145,1],[145,1],[143,1],[143,3],[142,2],[142,2],[142,4],[142,4],[142,4],[142,6],[142,6],[142,2],[142,4],[24,5],[24,7],[24,4],[24,6],[150,1],[150,2],[152,3],[152,4],[154,3],[154,5],[20,1],[20,3],[20,3],[20,3],[18,2],[18,2],[18,2],[18,2],[18,2],[18,2],[18,2],[18,2],[18,2],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,3],[18,5],[18,4],[18,3]],performAction:function(a,p,t,d,wa,b,e){a=b.length-1;switch(wa){case 1:return this.$=d.addLocationDataFn(e[a],e[a])(new d.Block);\ncase 2:return this.$=b[a];case 3:this.$=d.addLocationDataFn(e[a],e[a])(d.Block.wrap([b[a]]));break;case 4:this.$=d.addLocationDataFn(e[a-2],e[a])(b[a-2].push(b[a]));break;case 5:this.$=b[a-1];break;case 6:case 7:case 8:case 9:case 10:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:case 23:case 24:case 25:case 26:case 35:case 40:case 42:case 56:case 57:case 58:case 59:case 60:case 61:case 71:case 72:case 82:case 83:case 84:case 85:case 90:case 91:case 94:case 98:case 104:case 163:case 187:case 188:case 190:case 220:case 221:case 239:case 245:this.$=\nb[a];break;case 11:this.$=d.addLocationDataFn(e[a],e[a])(new d.StatementLiteral(b[a]));break;case 27:this.$=d.addLocationDataFn(e[a],e[a])(new d.Op(b[a],new d.Value(new d.Literal(\"\"))));break;case 28:case 249:case 250:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(b[a-1],b[a]));break;case 29:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Op(b[a-2].concat(b[a-1]),b[a]));break;case 30:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Block);break;case 31:case 105:this.$=d.addLocationDataFn(e[a-2],e[a])(b[a-\n1]);break;case 32:this.$=d.addLocationDataFn(e[a],e[a])(new d.IdentifierLiteral(b[a]));break;case 33:this.$=d.addLocationDataFn(e[a],e[a])(new d.PropertyName(b[a]));break;case 34:this.$=d.addLocationDataFn(e[a],e[a])(new d.NumberLiteral(b[a]));break;case 36:this.$=d.addLocationDataFn(e[a],e[a])(new d.StringLiteral(b[a]));break;case 37:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.StringWithInterpolations(b[a-1]));break;case 38:this.$=d.addLocationDataFn(e[a],e[a])(new d.RegexLiteral(b[a]));break;\ncase 39:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.RegexWithInterpolations(b[a-1].args));break;case 41:this.$=d.addLocationDataFn(e[a],e[a])(new d.PassthroughLiteral(b[a]));break;case 43:this.$=d.addLocationDataFn(e[a],e[a])(new d.UndefinedLiteral);break;case 44:this.$=d.addLocationDataFn(e[a],e[a])(new d.NullLiteral);break;case 45:this.$=d.addLocationDataFn(e[a],e[a])(new d.BooleanLiteral(b[a]));break;case 46:this.$=d.addLocationDataFn(e[a],e[a])(new d.InfinityLiteral(b[a]));break;case 47:this.$=\nd.addLocationDataFn(e[a],e[a])(new d.NaNLiteral);break;case 48:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Assign(b[a-2],b[a]));break;case 49:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Assign(b[a-3],b[a]));break;case 50:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Assign(b[a-4],b[a-1]));break;case 51:case 87:case 92:case 93:case 95:case 96:case 97:case 222:case 223:this.$=d.addLocationDataFn(e[a],e[a])(new d.Value(b[a]));break;case 52:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Assign(d.addLocationDataFn(e[a-\n2])(new d.Value(b[a-2])),b[a],\"object\",{operatorToken:d.addLocationDataFn(e[a-1])(new d.Literal(b[a-1]))}));break;case 53:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Assign(d.addLocationDataFn(e[a-4])(new d.Value(b[a-4])),b[a-1],\"object\",{operatorToken:d.addLocationDataFn(e[a-3])(new d.Literal(b[a-3]))}));break;case 54:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Assign(d.addLocationDataFn(e[a-2])(new d.Value(b[a-2])),b[a],null,{operatorToken:d.addLocationDataFn(e[a-1])(new d.Literal(b[a-1]))}));\nbreak;case 55:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Assign(d.addLocationDataFn(e[a-4])(new d.Value(b[a-4])),b[a-1],null,{operatorToken:d.addLocationDataFn(e[a-3])(new d.Literal(b[a-3]))}));break;case 62:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Return(b[a]));break;case 63:this.$=d.addLocationDataFn(e[a],e[a])(new d.Return);break;case 64:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.YieldReturn(b[a]));break;case 65:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.YieldReturn);break;case 66:this.$=\nd.addLocationDataFn(e[a],e[a])(new d.Comment(b[a]));break;case 67:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Code(b[a-3],b[a],b[a-1]));break;case 68:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Code([],b[a],b[a-1]));break;case 69:this.$=d.addLocationDataFn(e[a],e[a])(\"func\");break;case 70:this.$=d.addLocationDataFn(e[a],e[a])(\"boundfunc\");break;case 73:case 110:this.$=d.addLocationDataFn(e[a],e[a])([]);break;case 74:case 111:case 130:case 150:case 182:case 224:this.$=d.addLocationDataFn(e[a],\ne[a])([b[a]]);break;case 75:case 112:case 131:case 151:case 183:this.$=d.addLocationDataFn(e[a-2],e[a])(b[a-2].concat(b[a]));break;case 76:case 113:case 132:case 152:case 184:this.$=d.addLocationDataFn(e[a-3],e[a])(b[a-3].concat(b[a]));break;case 77:case 114:case 134:case 154:case 186:this.$=d.addLocationDataFn(e[a-5],e[a])(b[a-5].concat(b[a-2]));break;case 78:this.$=d.addLocationDataFn(e[a],e[a])(new d.Param(b[a]));break;case 79:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Param(b[a-1],null,!0));\nbreak;case 80:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Param(b[a-2],b[a]));break;case 81:case 189:this.$=d.addLocationDataFn(e[a],e[a])(new d.Expansion);break;case 86:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Splat(b[a-1]));break;case 88:this.$=d.addLocationDataFn(e[a-1],e[a])(b[a-1].add(b[a]));break;case 89:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Value(b[a-1],[].concat(b[a])));break;case 99:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Access(b[a]));break;case 100:this.$=d.addLocationDataFn(e[a-\n1],e[a])(new d.Access(b[a],\"soak\"));break;case 101:this.$=d.addLocationDataFn(e[a-1],e[a])([d.addLocationDataFn(e[a-1])(new d.Access(new d.PropertyName(\"prototype\"))),d.addLocationDataFn(e[a])(new d.Access(b[a]))]);break;case 102:this.$=d.addLocationDataFn(e[a-1],e[a])([d.addLocationDataFn(e[a-1])(new d.Access(new d.PropertyName(\"prototype\"),\"soak\")),d.addLocationDataFn(e[a])(new d.Access(b[a]))]);break;case 103:this.$=d.addLocationDataFn(e[a],e[a])(new d.Access(new d.PropertyName(\"prototype\")));\nbreak;case 106:this.$=d.addLocationDataFn(e[a-1],e[a])(d.extend(b[a],{soak:!0}));break;case 107:this.$=d.addLocationDataFn(e[a],e[a])(new d.Index(b[a]));break;case 108:this.$=d.addLocationDataFn(e[a],e[a])(new d.Slice(b[a]));break;case 109:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Obj(b[a-2],b[a-3].generated));break;case 115:this.$=d.addLocationDataFn(e[a],e[a])(new d.Class);break;case 116:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Class(null,null,b[a]));break;case 117:this.$=d.addLocationDataFn(e[a-\n2],e[a])(new d.Class(null,b[a]));break;case 118:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Class(null,b[a-1],b[a]));break;case 119:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Class(b[a]));break;case 120:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Class(b[a-1],null,b[a]));break;case 121:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Class(b[a-2],b[a]));break;case 122:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Class(b[a-3],b[a-1],b[a]));break;case 123:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.ImportDeclaration(null,\nb[a]));break;case 124:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.ImportDeclaration(new d.ImportClause(b[a-2],null),b[a]));break;case 125:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.ImportDeclaration(new d.ImportClause(null,b[a-2]),b[a]));break;case 126:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.ImportDeclaration(new d.ImportClause(null,new d.ImportSpecifierList([])),b[a]));break;case 127:this.$=d.addLocationDataFn(e[a-6],e[a])(new d.ImportDeclaration(new d.ImportClause(null,new d.ImportSpecifierList(b[a-\n4])),b[a]));break;case 128:this.$=d.addLocationDataFn(e[a-5],e[a])(new d.ImportDeclaration(new d.ImportClause(b[a-4],b[a-2]),b[a]));break;case 129:this.$=d.addLocationDataFn(e[a-8],e[a])(new d.ImportDeclaration(new d.ImportClause(b[a-7],new d.ImportSpecifierList(b[a-4])),b[a]));break;case 133:case 153:case 169:case 185:this.$=d.addLocationDataFn(e[a-3],e[a])(b[a-2]);break;case 135:this.$=d.addLocationDataFn(e[a],e[a])(new d.ImportSpecifier(b[a]));break;case 136:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ImportSpecifier(b[a-\n2],b[a]));break;case 137:this.$=d.addLocationDataFn(e[a],e[a])(new d.ImportSpecifier(new d.Literal(b[a])));break;case 138:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ImportSpecifier(new d.Literal(b[a-2]),b[a]));break;case 139:this.$=d.addLocationDataFn(e[a],e[a])(new d.ImportDefaultSpecifier(b[a]));break;case 140:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ImportNamespaceSpecifier(new d.Literal(b[a-2]),b[a]));break;case 141:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ExportNamedDeclaration(new d.ExportSpecifierList([])));\nbreak;case 142:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.ExportNamedDeclaration(new d.ExportSpecifierList(b[a-2])));break;case 143:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.ExportNamedDeclaration(b[a]));break;case 144:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.ExportNamedDeclaration(new d.Assign(b[a-2],b[a],null,{moduleDeclaration:\"export\"})));break;case 145:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.ExportNamedDeclaration(new d.Assign(b[a-3],b[a],null,{moduleDeclaration:\"export\"})));\nbreak;case 146:this.$=d.addLocationDataFn(e[a-5],e[a])(new d.ExportNamedDeclaration(new d.Assign(b[a-4],b[a-1],null,{moduleDeclaration:\"export\"})));break;case 147:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ExportDefaultDeclaration(b[a]));break;case 148:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.ExportAllDeclaration(new d.Literal(b[a-2]),b[a]));break;case 149:this.$=d.addLocationDataFn(e[a-6],e[a])(new d.ExportNamedDeclaration(new d.ExportSpecifierList(b[a-4]),b[a]));break;case 155:this.$=d.addLocationDataFn(e[a],\ne[a])(new d.ExportSpecifier(b[a]));break;case 156:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ExportSpecifier(b[a-2],b[a]));break;case 157:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ExportSpecifier(b[a-2],new d.Literal(b[a])));break;case 158:this.$=d.addLocationDataFn(e[a],e[a])(new d.ExportSpecifier(new d.Literal(b[a])));break;case 159:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.ExportSpecifier(new d.Literal(b[a-2]),b[a]));break;case 160:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.TaggedTemplateCall(b[a-\n2],b[a],b[a-1]));break;case 161:case 162:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Call(b[a-2],b[a],b[a-1]));break;case 164:this.$=d.addLocationDataFn(e[a],e[a])(new d.SuperCall);break;case 165:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.SuperCall(b[a]));break;case 166:this.$=d.addLocationDataFn(e[a],e[a])(!1);break;case 167:this.$=d.addLocationDataFn(e[a],e[a])(!0);break;case 168:this.$=d.addLocationDataFn(e[a-1],e[a])([]);break;case 170:case 171:this.$=d.addLocationDataFn(e[a],e[a])(new d.Value(new d.ThisLiteral));\nbreak;case 172:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Value(d.addLocationDataFn(e[a-1])(new d.ThisLiteral),[d.addLocationDataFn(e[a])(new d.Access(b[a]))],\"this\"));break;case 173:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Arr([]));break;case 174:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Arr(b[a-2]));break;case 175:this.$=d.addLocationDataFn(e[a],e[a])(\"inclusive\");break;case 176:this.$=d.addLocationDataFn(e[a],e[a])(\"exclusive\");break;case 177:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Range(b[a-\n3],b[a-1],b[a-2]));break;case 178:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Range(b[a-2],b[a],b[a-1]));break;case 179:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Range(b[a-1],null,b[a]));break;case 180:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Range(null,b[a],b[a-1]));break;case 181:this.$=d.addLocationDataFn(e[a],e[a])(new d.Range(null,null,b[a]));break;case 191:this.$=d.addLocationDataFn(e[a-2],e[a])([].concat(b[a-2],b[a]));break;case 192:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Try(b[a]));\nbreak;case 193:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Try(b[a-1],b[a][0],b[a][1]));break;case 194:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Try(b[a-2],null,null,b[a]));break;case 195:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Try(b[a-3],b[a-2][0],b[a-2][1],b[a]));break;case 196:this.$=d.addLocationDataFn(e[a-2],e[a])([b[a-1],b[a]]);break;case 197:this.$=d.addLocationDataFn(e[a-2],e[a])([d.addLocationDataFn(e[a-1])(new d.Value(b[a-1])),b[a]]);break;case 198:this.$=d.addLocationDataFn(e[a-\n1],e[a])([null,b[a]]);break;case 199:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Throw(b[a]));break;case 200:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Parens(b[a-1]));break;case 201:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Parens(b[a-2]));break;case 202:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.While(b[a]));break;case 203:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.While(b[a-2],{guard:b[a]}));break;case 204:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.While(b[a],{invert:!0}));break;\ncase 205:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.While(b[a-2],{invert:!0,guard:b[a]}));break;case 206:this.$=d.addLocationDataFn(e[a-1],e[a])(b[a-1].addBody(b[a]));break;case 207:case 208:this.$=d.addLocationDataFn(e[a-1],e[a])(b[a].addBody(d.addLocationDataFn(e[a-1])(d.Block.wrap([b[a-1]]))));break;case 209:this.$=d.addLocationDataFn(e[a],e[a])(b[a]);break;case 210:this.$=d.addLocationDataFn(e[a-1],e[a])((new d.While(d.addLocationDataFn(e[a-1])(new d.BooleanLiteral(\"true\")))).addBody(b[a]));\nbreak;case 211:this.$=d.addLocationDataFn(e[a-1],e[a])((new d.While(d.addLocationDataFn(e[a-1])(new d.BooleanLiteral(\"true\")))).addBody(d.addLocationDataFn(e[a])(d.Block.wrap([b[a]]))));break;case 212:case 213:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.For(b[a-1],b[a]));break;case 214:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.For(b[a],b[a-1]));break;case 215:this.$=d.addLocationDataFn(e[a-1],e[a])({source:d.addLocationDataFn(e[a])(new d.Value(b[a]))});break;case 216:this.$=d.addLocationDataFn(e[a-\n3],e[a])({source:d.addLocationDataFn(e[a-2])(new d.Value(b[a-2])),step:b[a]});break;case 217:d=d.addLocationDataFn(e[a-1],e[a]);b[a].own=b[a-1].own;b[a].ownTag=b[a-1].ownTag;b[a].name=b[a-1][0];b[a].index=b[a-1][1];this.$=d(b[a]);break;case 218:this.$=d.addLocationDataFn(e[a-1],e[a])(b[a]);break;case 219:wa=d.addLocationDataFn(e[a-2],e[a]);b[a].own=!0;b[a].ownTag=d.addLocationDataFn(e[a-1])(new d.Literal(b[a-1]));this.$=wa(b[a]);break;case 225:this.$=d.addLocationDataFn(e[a-2],e[a])([b[a-2],b[a]]);\nbreak;case 226:this.$=d.addLocationDataFn(e[a-1],e[a])({source:b[a]});break;case 227:this.$=d.addLocationDataFn(e[a-1],e[a])({source:b[a],object:!0});break;case 228:this.$=d.addLocationDataFn(e[a-3],e[a])({source:b[a-2],guard:b[a]});break;case 229:this.$=d.addLocationDataFn(e[a-3],e[a])({source:b[a-2],guard:b[a],object:!0});break;case 230:this.$=d.addLocationDataFn(e[a-3],e[a])({source:b[a-2],step:b[a]});break;case 231:this.$=d.addLocationDataFn(e[a-5],e[a])({source:b[a-4],guard:b[a-2],step:b[a]});\nbreak;case 232:this.$=d.addLocationDataFn(e[a-5],e[a])({source:b[a-4],step:b[a-2],guard:b[a]});break;case 233:this.$=d.addLocationDataFn(e[a-1],e[a])({source:b[a],from:!0});break;case 234:this.$=d.addLocationDataFn(e[a-3],e[a])({source:b[a-2],guard:b[a],from:!0});break;case 235:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Switch(b[a-3],b[a-1]));break;case 236:this.$=d.addLocationDataFn(e[a-6],e[a])(new d.Switch(b[a-5],b[a-3],b[a-1]));break;case 237:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Switch(null,\nb[a-1]));break;case 238:this.$=d.addLocationDataFn(e[a-5],e[a])(new d.Switch(null,b[a-3],b[a-1]));break;case 240:this.$=d.addLocationDataFn(e[a-1],e[a])(b[a-1].concat(b[a]));break;case 241:this.$=d.addLocationDataFn(e[a-2],e[a])([[b[a-1],b[a]]]);break;case 242:this.$=d.addLocationDataFn(e[a-3],e[a])([[b[a-2],b[a-1]]]);break;case 243:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.If(b[a-1],b[a],{type:b[a-2]}));break;case 244:this.$=d.addLocationDataFn(e[a-4],e[a])(b[a-4].addElse(d.addLocationDataFn(e[a-\n2],e[a])(new d.If(b[a-1],b[a],{type:b[a-2]}))));break;case 246:this.$=d.addLocationDataFn(e[a-2],e[a])(b[a-2].addElse(b[a]));break;case 247:case 248:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.If(b[a],d.addLocationDataFn(e[a-2])(d.Block.wrap([b[a-2]])),{type:b[a-1],statement:!0}));break;case 251:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(\"-\",b[a]));break;case 252:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(\"+\",b[a]));break;case 253:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(\"--\",\nb[a]));break;case 254:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(\"++\",b[a]));break;case 255:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(\"--\",b[a-1],null,!0));break;case 256:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Op(\"++\",b[a-1],null,!0));break;case 257:this.$=d.addLocationDataFn(e[a-1],e[a])(new d.Existence(b[a-1]));break;case 258:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Op(\"+\",b[a-2],b[a]));break;case 259:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Op(\"-\",b[a-2],b[a]));break;\ncase 260:case 261:case 262:case 263:case 264:case 265:case 266:case 267:case 268:case 269:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Op(b[a-1],b[a-2],b[a]));break;case 270:e=d.addLocationDataFn(e[a-2],e[a]);b=\"!\"===b[a-1].charAt(0)?(new d.Op(b[a-1].slice(1),b[a-2],b[a])).invert():new d.Op(b[a-1],b[a-2],b[a]);this.$=e(b);break;case 271:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Assign(b[a-2],b[a],b[a-1]));break;case 272:this.$=d.addLocationDataFn(e[a-4],e[a])(new d.Assign(b[a-4],b[a-1],b[a-3]));\nbreak;case 273:this.$=d.addLocationDataFn(e[a-3],e[a])(new d.Assign(b[a-3],b[a],b[a-2]));break;case 274:this.$=d.addLocationDataFn(e[a-2],e[a])(new d.Extends(b[a-2],b[a]))}},table:[{1:[2,1],3:1,4:2,5:3,7:4,8:5,9:6,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:u,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,\n97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{1:[3]},{1:[2,2],6:qa},a(sa,[2,3]),a(sa,[2,6],{141:77,132:102,138:103,133:D,135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(sa,[2,7],{141:77,132:105,138:106,133:D,135:A,139:E,156:va}),a(sa,[2,8]),a(N,[2,14],{109:107,78:108,86:114,40:xa,41:xa,114:xa,82:ta,83:Na,\n84:Fa,85:Ga,87:Ca,90:Pa,113:Ia}),a(N,[2,15],{86:114,109:117,78:118,82:ta,83:Na,84:Fa,85:Ga,87:Ca,90:Pa,113:Ia,114:xa}),a(N,[2,16]),a(N,[2,17]),a(N,[2,18]),a(N,[2,19]),a(N,[2,20]),a(N,[2,21]),a(N,[2,22]),a(N,[2,23]),a(N,[2,24]),a(N,[2,25]),a(N,[2,26]),a(Ea,[2,9]),a(Ea,[2,10]),a(Ea,[2,11]),a(Ea,[2,12]),a(Ea,[2,13]),a([1,6,32,42,131,133,135,139,156,163,164,165,166,167,168,169,170,171,172,173,174],Va,{15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,10:20,11:21,13:23,14:24,54:26,\n47:27,79:28,80:29,81:30,111:31,66:33,77:40,154:41,132:43,136:44,138:45,74:53,75:54,37:55,43:57,33:70,60:71,141:77,39:80,7:120,8:122,12:b,28:ea,29:Ya,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,61:[1,119],62:z,63:l,67:c,68:w,92:m,95:k,97:K,105:P,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,137:q,149:ba,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M}),a(Ba,Ha,{55:[1,124]}),a(Ba,[2,95]),a(Ba,[2,96]),a(Ba,[2,97]),a(Ba,[2,98]),a(t,[2,163]),a([6,31,65,70],p,{64:125,71:126,72:127,33:129,60:130,\n74:131,75:132,34:g,73:d,92:m,118:wa,119:e}),{30:135,31:Da},{7:137,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,\n158:T,159:v,160:Y,161:S,162:M},{7:138,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},\n{7:139,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:140,8:122,10:20,11:21,12:b,\n13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{15:142,16:143,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,\n44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:144,60:71,74:53,75:54,77:141,79:28,80:29,81:30,92:m,111:31,112:L,117:V,118:X,119:G,130:W},{15:142,16:143,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:144,60:71,74:53,75:54,77:145,79:28,80:29,81:30,92:m,111:31,112:L,117:V,118:X,119:G,130:W},a(Ta,ua,{96:[1,149],161:[1,146],162:[1,147],175:[1,148]}),a(N,[2,245],{151:[1,150]}),{30:151,31:Da},{30:152,31:Da},a(N,[2,209]),{30:153,31:Da},{7:154,8:122,10:20,11:21,\n12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:[1,155],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Eb,[2,115],{47:27,79:28,80:29,81:30,111:31,\n74:53,75:54,37:55,43:57,33:70,60:71,39:80,15:142,16:143,54:144,30:156,77:158,31:Da,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,92:m,96:[1,157],112:L,117:V,118:X,119:G,130:W}),{7:159,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,\n111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Ea,Za,{15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,10:20,11:21,13:23,14:24,54:26,47:27,79:28,80:29,81:30,111:31,66:33,77:40,154:41,132:43,136:44,138:45,74:53,75:54,37:55,43:57,33:70,60:71,141:77,39:80,8:122,7:160,12:b,28:ea,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,61:R,62:z,63:l,67:c,68:w,\n92:m,95:k,97:K,105:P,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,137:q,149:ba,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M}),a([1,6,31,32,42,70,94,131,133,135,139,156],[2,66]),{33:165,34:g,39:161,40:r,41:n,92:[1,164],98:162,99:163,104:Fb},{25:168,33:169,34:g,92:[1,167],95:k,103:[1,170],107:[1,171]},a(Ta,[2,92]),a(Ta,[2,93]),a(Ba,[2,40]),a(Ba,[2,41]),a(Ba,[2,42]),a(Ba,[2,43]),a(Ba,[2,44]),a(Ba,[2,45]),a(Ba,[2,46]),a(Ba,[2,47]),{4:172,5:3,7:4,8:5,9:6,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,\n20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:u,31:[1,173],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:174,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,\n23:15,24:16,25:17,26:18,27:19,28:ea,31:$a,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Ua,74:53,75:54,76:179,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,116:176,117:V,118:X,119:G,120:Gb,123:177,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Ba,[2,170]),a(Ba,[2,171],{35:181,36:Oa}),a([1,6,31,32,42,46,65,70,73,82,\n83,84,85,87,89,90,94,113,115,120,122,131,133,134,135,139,140,156,159,160,163,164,165,166,167,168,169,170,171,172,173,174],[2,164],{110:183,114:sb}),{31:[2,69]},{31:[2,70]},a(La,[2,87]),a(La,[2,90]),{7:185,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,\n105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:186,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,\n119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:187,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,\n133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:189,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,30:188,31:Da,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,\n137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{33:194,34:g,60:195,74:196,75:197,80:190,92:m,118:wa,119:G,143:191,144:[1,192],145:193},{142:198,146:[1,199],147:[1,200],148:[1,201]},a([6,31,70,94],Hb,{39:80,93:202,56:203,57:204,59:205,11:206,37:207,33:208,35:209,60:210,34:g,36:Oa,38:h,40:r,41:n,62:z,118:wa}),a(Ib,[2,34]),a(Ib,[2,35]),a(Ba,[2,38]),{15:142,16:211,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:144,60:71,\n74:53,75:54,77:212,79:28,80:29,81:30,92:m,111:31,112:L,117:V,118:X,119:G,130:W},a([1,6,29,31,32,40,41,42,55,58,65,70,73,82,83,84,85,87,89,90,94,96,102,113,114,115,120,122,131,133,134,135,139,140,146,147,148,156,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175],[2,32]),a(Jb,[2,36]),{4:213,5:3,7:4,8:5,9:6,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:u,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,\n50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(sa,[2,5],{7:4,8:5,9:6,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,10:20,11:21,13:23,14:24,54:26,47:27,79:28,80:29,81:30,111:31,66:33,77:40,154:41,132:43,136:44,138:45,74:53,75:54,37:55,43:57,\n33:70,60:71,141:77,39:80,5:214,12:b,28:u,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,61:R,62:z,63:l,67:c,68:w,92:m,95:k,97:K,105:P,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,133:D,135:A,137:q,139:E,149:ba,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M}),a(N,[2,257]),{7:215,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,\n61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:216,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,\n74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:217,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,\n81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:218,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,\n112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:219,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,\n129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:220,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,\n136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:221,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,\n149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:222,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,\n159:v,160:Y,161:S,162:M},{7:223,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:224,\n8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:225,8:122,10:20,11:21,12:b,13:23,\n14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:226,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,\n20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:227,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,\n25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:228,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,\n34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(N,[2,208]),a(N,[2,213]),{7:229,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,\n37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(N,[2,207]),a(N,[2,212]),{39:230,40:r,41:n,110:231,114:sb},a(La,[2,88]),a(Kb,[2,167]),{35:232,36:Oa},{35:233,36:Oa},a(La,[2,103],{35:234,36:Oa}),{35:235,36:Oa},a(La,\n[2,104]),{7:237,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Lb,74:53,75:54,77:40,79:28,80:29,81:30,88:236,91:238,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,121:239,122:tb,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,\n161:S,162:M},{86:242,87:Ca,90:Pa},{110:243,114:sb},a(La,[2,89]),a(sa,[2,65],{15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,10:20,11:21,13:23,14:24,54:26,47:27,79:28,80:29,81:30,111:31,66:33,77:40,154:41,132:43,136:44,138:45,74:53,75:54,37:55,43:57,33:70,60:71,141:77,39:80,8:122,7:244,12:b,28:ea,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,61:R,62:z,63:l,67:c,68:w,92:m,95:k,97:K,105:P,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,133:Za,135:Za,139:Za,156:Za,\n137:q,149:ba,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M}),a(Ma,[2,28],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{7:245,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,\n111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{132:105,133:D,135:A,138:106,139:E,141:77,156:va},a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,163,164,165,166,167,168,169,170,171,172,173,174],Va,{15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,10:20,11:21,13:23,14:24,54:26,47:27,79:28,80:29,81:30,111:31,66:33,77:40,154:41,132:43,136:44,\n138:45,74:53,75:54,37:55,43:57,33:70,60:71,141:77,39:80,7:120,8:122,12:b,28:ea,29:Ya,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,61:R,62:z,63:l,67:c,68:w,92:m,95:k,97:K,105:P,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,137:q,149:ba,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M}),{6:[1,247],7:246,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:[1,248],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,\n49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a([6,31],Ja,{69:251,65:[1,249],70:Mb}),a(Sa,[2,74]),a(Sa,[2,78],{55:[1,253],73:[1,252]}),a(Sa,[2,81]),a(fb,[2,82]),a(fb,[2,83]),a(fb,[2,84]),a(fb,[2,85]),{35:181,36:Oa},{7:254,8:122,10:20,11:21,12:b,13:23,14:24,15:7,\n16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:$a,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Ua,74:53,75:54,76:179,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,116:176,117:V,118:X,119:G,120:Gb,123:177,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(N,[2,68]),{4:256,5:3,7:4,8:5,9:6,\n10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:u,32:[1,255],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a([1,6,31,32,42,65,70,73,89,94,\n115,120,122,131,133,134,135,139,140,156,159,160,164,165,166,167,168,169,170,171,172,173,174],[2,249],{141:77,132:102,138:103,163:fa}),a(ab,[2,250],{141:77,132:102,138:103,163:fa,165:ga}),a(ab,[2,251],{141:77,132:102,138:103,163:fa,165:ga}),a(ab,[2,252],{141:77,132:102,138:103,163:fa,165:ga}),a(N,[2,253],{40:ua,41:ua,82:ua,83:ua,84:ua,85:ua,87:ua,90:ua,113:ua,114:ua}),a(Kb,xa,{109:107,78:108,86:114,82:ta,83:Na,84:Fa,85:Ga,87:Ca,90:Pa,113:Ia}),{78:118,82:ta,83:Na,84:Fa,85:Ga,86:114,87:Ca,90:Pa,109:117,\n113:Ia,114:xa},a(Nb,Ha),a(N,[2,254],{40:ua,41:ua,82:ua,83:ua,84:ua,85:ua,87:ua,90:ua,113:ua,114:ua}),a(N,[2,255]),a(N,[2,256]),{6:[1,259],7:257,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:[1,258],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,\n130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:260,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,\n137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{30:261,31:Da,155:[1,262]},a(N,[2,192],{126:263,127:[1,264],128:[1,265]}),a(N,[2,206]),a(N,[2,214]),{31:[1,266],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},{150:267,152:268,153:gb},a(N,[2,116]),{7:270,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,\n33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Eb,[2,119],{30:271,31:Da,40:ua,41:ua,82:ua,83:ua,84:ua,85:ua,87:ua,90:ua,113:ua,114:ua,96:[1,272]}),a(Ma,[2,199],{141:77,132:102,138:103,159:ma,160:Z,\n163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ea,bb,{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ea,[2,123]),{29:[1,273],70:[1,274]},{29:[1,275]},{31:hb,33:280,34:g,94:[1,276],100:277,101:278,103:Wa},a([29,70],[2,139]),{102:[1,282]},{31:ub,33:287,34:g,94:[1,283],103:cb,106:284,108:285},a(Ea,[2,143]),{55:[1,289]},{7:290,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,\n20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{29:[1,291]},{6:qa,131:[1,292]},{4:293,5:3,7:4,8:5,9:6,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,\n18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:u,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a([6,31,70,120],Ob,{141:77,132:102,138:103,121:294,73:[1,295],122:tb,133:D,135:A,139:E,\n156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(vb,[2,173]),a([6,31,120],Ja,{69:296,70:ib}),a(Qa,[2,182]),{7:254,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:$a,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Ua,74:53,75:54,76:179,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,\n112:L,116:298,117:V,118:X,119:G,123:177,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Qa,[2,188]),a(Qa,[2,189]),a(Pb,[2,172]),a(Pb,[2,33]),a(t,[2,165]),{7:254,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:$a,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Ua,\n74:53,75:54,76:179,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,115:[1,299],116:300,117:V,118:X,119:G,123:177,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{30:301,31:Da,132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},a(Qb,[2,202],{141:77,132:102,138:103,133:D,134:[1,302],135:A,139:E,159:ma,160:Z,163:fa,164:ia,\n165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Qb,[2,204],{141:77,132:102,138:103,133:D,134:[1,303],135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(N,[2,210]),a(Xa,[2,211],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,156,159,160,163,164,165,166,167,168,\n169,170,171,172,173,174],[2,215],{140:[1,304]}),a(jb,[2,218]),{33:194,34:g,60:195,74:196,75:197,92:m,118:wa,119:e,143:305,145:193},a(jb,[2,224],{70:[1,306]}),a(kb,[2,220]),a(kb,[2,221]),a(kb,[2,222]),a(kb,[2,223]),a(N,[2,217]),{7:307,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,\n79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:308,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,\n97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:309,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,\n118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(lb,Ja,{69:310,70:Rb}),a(Aa,[2,111]),a(Aa,[2,51],{58:[1,312]}),a(Sb,[2,60],{55:[1,313]}),a(Aa,[2,56]),a(Sb,[2,61]),a(wb,[2,57]),a(wb,[2,58]),a(wb,[2,59]),{46:[1,314],78:118,82:ta,83:Na,84:Fa,85:Ga,86:114,87:Ca,90:Pa,109:117,113:Ia,114:xa},a(Nb,ua),{6:qa,42:[1,315]},a(sa,[2,4]),a(Tb,[2,258],{141:77,132:102,138:103,163:fa,164:ia,165:ga}),a(Tb,[2,259],{141:77,\n132:102,138:103,163:fa,164:ia,165:ga}),a(ab,[2,260],{141:77,132:102,138:103,163:fa,165:ga}),a(ab,[2,261],{141:77,132:102,138:103,163:fa,165:ga}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,166,167,168,169,170,171,172,173,174],[2,262],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,167,168,169,170,171,172,173],[2,263],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,174:da}),\na([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,168,169,170,171,172,173],[2,264],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,169,170,171,172,173],[2,265],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,170,171,172,173],[2,266],{141:77,132:102,138:103,159:ma,160:Z,\n163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,171,172,173],[2,267],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,172,173],[2,268],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,\n135,139,140,156,173],[2,269],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,174:da}),a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,134,135,139,140,156,167,168,169,170,171,172,173,174],[2,270],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja}),a(Xa,[2,248],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Xa,[2,247],{141:77,132:102,\n138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(t,[2,160]),a(t,[2,161]),a(La,[2,99]),a(La,[2,100]),a(La,[2,101]),a(La,[2,102]),{89:[1,316]},{73:Lb,89:[2,107],121:317,122:tb,132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},{89:[2,108]},{7:318,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,\n24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,89:[2,181],92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Ub,[2,175]),a(Ub,Vb),a(La,[2,106]),a(t,[2,162]),a(sa,[2,64],{141:77,132:102,138:103,133:bb,135:bb,139:bb,156:bb,\n159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ma,[2,29],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ma,[2,48],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{7:319,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,\n37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:320,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,\n44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{66:321,67:c,68:w},a(Ra,db,{72:127,33:129,60:130,74:131,75:132,71:322,34:g,73:d,92:m,118:wa,119:e}),{6:Wb,31:Xb},a(Sa,[2,79]),{7:325,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,\n20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Qa,Ob,{141:77,132:102,138:103,73:[1,326],133:D,135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,\n166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Yb,[2,30]),{6:qa,32:[1,327]},a(Ma,[2,271],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{7:328,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,\n79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:329,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,\n97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Ma,[2,274],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(N,[2,246]),{7:330,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,\n48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(N,[2,193],{127:[1,331]}),{30:332,31:Da},{30:335,31:Da,33:333,34:g,75:334,92:m},{150:336,152:268,153:gb},{32:[1,337],151:[1,338],152:339,153:gb},a(mb,[2,239]),{7:341,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,\n17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,124:340,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Zb,[2,117],{141:77,132:102,138:103,30:342,31:Da,133:D,135:A,139:E,159:ma,\n160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(N,[2,120]),{7:343,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,\n139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{39:344,40:r,41:n},{92:[1,346],99:345,104:Fb},{39:347,40:r,41:n},{29:[1,348]},a(lb,Ja,{69:349,70:nb}),a(Aa,[2,130]),{31:hb,33:280,34:g,100:351,101:278,103:Wa},a(Aa,[2,135],{102:[1,352]}),a(Aa,[2,137],{102:[1,353]}),{33:354,34:g},a(Ea,[2,141]),a(lb,Ja,{69:355,70:xb}),a(Aa,[2,150]),{31:ub,33:287,34:g,103:cb,106:357,108:285},a(Aa,[2,155],{102:[1,358]}),a(Aa,[2,158],{102:[1,359]}),{6:[1,361],7:360,8:122,10:20,11:21,12:b,13:23,14:24,\n15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:[1,362],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(yb,[2,147],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,\n160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{39:363,40:r,41:n},a(Ba,[2,200]),{6:qa,32:[1,364]},{7:365,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,\n132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a([12,28,34,38,40,41,44,45,48,49,50,51,52,53,61,62,63,67,68,92,95,97,105,112,117,118,119,125,129,130,133,135,137,139,149,155,157,158,159,160,161,162],Vb,{6:eb,31:eb,70:eb,120:eb}),{6:ob,31:pb,120:[1,366]},a([6,31,32,115,120],db,{15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,10:20,11:21,13:23,14:24,54:26,47:27,79:28,80:29,81:30,111:31,66:33,77:40,154:41,132:43,\n136:44,138:45,74:53,75:54,37:55,43:57,33:70,60:71,141:77,39:80,8:122,76:179,7:254,123:369,12:b,28:ea,34:g,38:h,40:r,41:n,44:B,45:H,48:I,49:F,50:Q,51:x,52:J,53:O,61:R,62:z,63:l,67:c,68:w,73:Ua,92:m,95:k,97:K,105:P,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,133:D,135:A,137:q,139:E,149:ba,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M}),a(Ra,Ja,{69:370,70:ib}),a(t,[2,168]),a([6,31,115],Ja,{69:371,70:ib}),a($b,[2,243]),{7:372,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,\n23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:373,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,\n28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:374,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,\n39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(jb,[2,219]),{33:194,34:g,60:195,74:196,75:197,92:m,118:wa,119:e,145:375},a([1,6,31,32,42,65,70,73,89,94,115,120,122,131,133,135,139,156],[2,226],{141:77,132:102,138:103,134:[1,\n376],140:[1,377],159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(zb,[2,227],{141:77,132:102,138:103,134:[1,378],159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(zb,[2,233],{141:77,132:102,138:103,134:[1,379],159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{6:ac,31:bc,94:[1,380]},a(Ab,db,{39:80,57:204,59:205,11:206,37:207,33:208,35:209,60:210,56:383,\n34:g,36:Oa,38:h,40:r,41:n,62:z,118:wa}),{7:384,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:[1,385],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,\n160:Y,161:S,162:M},{7:386,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:[1,387],33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},\na(Ba,[2,39]),a(Jb,[2,37]),a(La,[2,105]),{7:388,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,89:[2,179],92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,\n160:Y,161:S,162:M},{89:[2,180],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},a(Ma,[2,49],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{32:[1,389],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},{30:390,31:Da},a(Sa,[2,75]),{33:129,\n34:g,60:130,71:391,72:127,73:d,74:131,75:132,92:m,118:wa,119:e},a(cc,p,{71:126,72:127,33:129,60:130,74:131,75:132,64:392,34:g,73:d,92:m,118:wa,119:e}),a(Sa,[2,80],{141:77,132:102,138:103,133:D,135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Qa,eb),a(Yb,[2,31]),{32:[1,393],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},a(Ma,[2,273],\n{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{30:394,31:Da,132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},{30:395,31:Da},a(N,[2,194]),{30:396,31:Da},{30:397,31:Da},a(Bb,[2,198]),{32:[1,398],151:[1,399],152:339,153:gb},a(N,[2,237]),{30:400,31:Da},a(mb,[2,240]),{30:401,31:Da,70:[1,402]},a(dc,[2,190],{141:77,132:102,138:103,133:D,\n135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(N,[2,118]),a(Zb,[2,121],{141:77,132:102,138:103,30:403,31:Da,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ea,[2,124]),{29:[1,404]},{31:hb,33:280,34:g,100:405,101:278,103:Wa},a(Ea,[2,125]),{39:406,40:r,41:n},{6:qb,31:rb,94:[1,407]},a(Ab,db,{33:280,101:410,34:g,103:Wa}),a(Ra,Ja,{69:411,70:nb}),{33:412,34:g},\n{33:413,34:g},{29:[2,140]},{6:Cb,31:Db,94:[1,414]},a(Ab,db,{33:287,108:417,34:g,103:cb}),a(Ra,Ja,{69:418,70:xb}),{33:419,34:g,103:[1,420]},{33:421,34:g},a(yb,[2,144],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{7:422,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,\n51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:423,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,\n62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Ea,[2,148]),{131:[1,424]},{120:[1,425],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},a(vb,[2,174]),{7:254,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,\n18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Ua,74:53,75:54,76:179,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,123:426,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:254,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,\n20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,31:$a,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,73:Ua,74:53,75:54,76:179,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,116:427,117:V,118:X,119:G,123:177,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Qa,[2,183]),{6:ob,31:pb,32:[1,428]},{6:ob,31:pb,115:[1,429]},\na(Xa,[2,203],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Xa,[2,205],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Xa,[2,216],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(jb,[2,225]),{7:430,8:122,10:20,11:21,\n12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:431,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,\n18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:432,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,\n23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:433,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,\n28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(vb,[2,109]),{11:206,33:208,34:g,35:209,36:Oa,37:207,38:h,39:80,40:r,41:n,56:434,57:204,59:205,60:210,62:z,118:wa},a(cc,Hb,{39:80,56:203,57:204,\n59:205,11:206,37:207,33:208,35:209,60:210,93:435,34:g,36:Oa,38:h,40:r,41:n,62:z,118:wa}),a(Aa,[2,112]),a(Aa,[2,52],{141:77,132:102,138:103,133:D,135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{7:436,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,\n66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(Aa,[2,54],{141:77,132:102,138:103,133:D,135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{7:437,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,\n27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{89:[2,178],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,\n173:ra,174:da},a(N,[2,50]),a(N,[2,67]),a(Sa,[2,76]),a(Ra,Ja,{69:438,70:Mb}),a(N,[2,272]),a($b,[2,244]),a(N,[2,195]),a(Bb,[2,196]),a(Bb,[2,197]),a(N,[2,235]),{30:439,31:Da},{32:[1,440]},a(mb,[2,241],{6:[1,441]}),{7:442,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,\n92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},a(N,[2,122]),{39:443,40:r,41:n},a(lb,Ja,{69:444,70:nb}),a(Ea,[2,126]),{29:[1,445]},{33:280,34:g,101:446,103:Wa},{31:hb,33:280,34:g,100:447,101:278,103:Wa},a(Aa,[2,131]),{6:qb,31:rb,32:[1,448]},a(Aa,[2,136]),a(Aa,[2,138]),a(Ea,[2,142],{29:[1,449]}),{33:287,34:g,103:cb,108:450},{31:ub,33:287,34:g,103:cb,106:451,108:285},\na(Aa,[2,151]),{6:Cb,31:Db,32:[1,452]},a(Aa,[2,156]),a(Aa,[2,157]),a(Aa,[2,159]),a(yb,[2,145],{141:77,132:102,138:103,133:D,135:A,139:E,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),{32:[1,453],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},a(Ba,[2,201]),a(Ba,[2,177]),a(Qa,[2,184]),a(Ra,Ja,{69:454,70:ib}),a(Qa,[2,185]),a(t,[2,169]),a([1,6,31,32,42,\n65,70,73,89,94,115,120,122,131,133,134,135,139,156],[2,228],{141:77,132:102,138:103,140:[1,455],159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(zb,[2,230],{141:77,132:102,138:103,134:[1,456],159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ma,[2,229],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ma,[2,234],{141:77,132:102,\n138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Aa,[2,113]),a(Ra,Ja,{69:457,70:Rb}),{32:[1,458],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},{32:[1,459],132:102,133:D,135:A,138:103,139:E,141:77,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da},{6:Wb,31:Xb,32:[1,460]},{32:[1,461]},a(N,\n[2,238]),a(mb,[2,242]),a(dc,[2,191],{141:77,132:102,138:103,133:D,135:A,139:E,156:za,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ea,[2,128]),{6:qb,31:rb,94:[1,462]},{39:463,40:r,41:n},a(Aa,[2,132]),a(Ra,Ja,{69:464,70:nb}),a(Aa,[2,133]),{39:465,40:r,41:n},a(Aa,[2,152]),a(Ra,Ja,{69:466,70:xb}),a(Aa,[2,153]),a(Ea,[2,146]),{6:ob,31:pb,32:[1,467]},{7:468,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,\n25:17,26:18,27:19,28:ea,33:70,34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{7:469,8:122,10:20,11:21,12:b,13:23,14:24,15:7,16:8,17:9,18:10,19:11,20:12,21:13,22:14,23:15,24:16,25:17,26:18,27:19,28:ea,33:70,\n34:g,37:55,38:h,39:80,40:r,41:n,43:57,44:B,45:H,47:27,48:I,49:F,50:Q,51:x,52:J,53:O,54:26,60:71,61:R,62:z,63:l,66:33,67:c,68:w,74:53,75:54,77:40,79:28,80:29,81:30,92:m,95:k,97:K,105:P,111:31,112:L,117:V,118:X,119:G,125:aa,129:U,130:W,132:43,133:D,135:A,136:44,137:q,138:45,139:E,141:77,149:ba,154:41,155:ca,157:C,158:T,159:v,160:Y,161:S,162:M},{6:ac,31:bc,32:[1,470]},a(Aa,[2,53]),a(Aa,[2,55]),a(Sa,[2,77]),a(N,[2,236]),{29:[1,471]},a(Ea,[2,127]),{6:qb,31:rb,32:[1,472]},a(Ea,[2,149]),{6:Cb,31:Db,32:[1,\n473]},a(Qa,[2,186]),a(Ma,[2,231],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Ma,[2,232],{141:77,132:102,138:103,159:ma,160:Z,163:fa,164:ia,165:ga,166:ja,167:la,168:oa,169:pa,170:ha,171:ka,172:na,173:ra,174:da}),a(Aa,[2,114]),{39:474,40:r,41:n},a(Aa,[2,134]),a(Aa,[2,154]),a(Ea,[2,129])],defaultActions:{68:[2,69],69:[2,70],238:[2,108],354:[2,140]},parseError:function(a,d){if(d.recoverable)this.trace(a);else{var e=function(a,\nd){this.message=a;this.hash=d};e.prototype=Error;throw new e(a,d);}},parse:function(a){var d=[0],e=[null],b=[],p=this.table,t=\"\",wa=0,c=0,g=0,Da=b.slice.call(arguments,1),k=Object.create(this.lexer),h={};for(f in this.yy)Object.prototype.hasOwnProperty.call(this.yy,f)&&(h[f]=this.yy[f]);k.setInput(a,h);h.lexer=k;h.parser=this;\"undefined\"==typeof k.yylloc&&(k.yylloc={});var f=k.yylloc;b.push(f);var l=k.options&&k.options.ranges;this.parseError=\"function\"===typeof h.parseError?h.parseError:Object.getPrototypeOf(this).parseError;\nfor(var m,Ta,Ha,n,ua={},y,w;;){Ha=d[d.length-1];if(this.defaultActions[Ha])n=this.defaultActions[Ha];else{if(null===m||\"undefined\"==typeof m)m=k.lex()||1,\"number\"!==typeof m&&(m=this.symbols_[m]||m);n=p[Ha]&&p[Ha][m]}if(\"undefined\"===typeof n||!n.length||!n[0]){w=[];for(y in p[Ha])this.terminals_[y]&&2<y&&w.push(\"'\"+this.terminals_[y]+\"'\");var q=k.showPosition?\"Parse error on line \"+(wa+1)+\":\\n\"+k.showPosition()+\"\\nExpecting \"+w.join(\", \")+\", got '\"+(this.terminals_[m]||m)+\"'\":\"Parse error on line \"+\n(wa+1)+\": Unexpected \"+(1==m?\"end of input\":\"'\"+(this.terminals_[m]||m)+\"'\");this.parseError(q,{text:k.match,token:this.terminals_[m]||m,line:k.yylineno,loc:f,expected:w})}if(n[0]instanceof Array&&1<n.length)throw Error(\"Parse Error: multiple actions possible at state: \"+Ha+\", token: \"+m);switch(n[0]){case 1:d.push(m);e.push(k.yytext);b.push(k.yylloc);d.push(n[1]);m=null;Ta?(m=Ta,Ta=null):(c=k.yyleng,t=k.yytext,wa=k.yylineno,f=k.yylloc,0<g&&g--);break;case 2:w=this.productions_[n[1]][1];ua.$=e[e.length-\nw];ua._$={first_line:b[b.length-(w||1)].first_line,last_line:b[b.length-1].last_line,first_column:b[b.length-(w||1)].first_column,last_column:b[b.length-1].last_column};l&&(ua._$.range=[b[b.length-(w||1)].range[0],b[b.length-1].range[1]]);Ha=this.performAction.apply(ua,[t,c,wa,h,n[1],e,b].concat(Da));if(\"undefined\"!==typeof Ha)return Ha;w&&(d=d.slice(0,-2*w),e=e.slice(0,-1*w),b=b.slice(0,-1*w));d.push(this.productions_[n[1]][0]);e.push(ua.$);b.push(ua._$);n=p[d[d.length-2]][d[d.length-1]];d.push(n);\nbreak;case 3:return!0}}}};f.prototype=ec;ec.Parser=f;return new f}();\"undefined\"!==typeof u&&\"undefined\"!==typeof f&&(f.parser=q,f.Parser=q.Parser,f.parse=function(){return q.parse.apply(q,arguments)},f.main=function(y){y[1]||(console.log(\"Usage: \"+y[0]+\" FILE\"),process.exit(1));var a=\"\",b=u(\"fs\");\"undefined\"!==typeof b&&null!==b&&(a=b.readFileSync(u(\"path\").normalize(y[1]),\"utf8\"));return f.parser.parse(a)},\"undefined\"!==typeof qa&&u.main===qa&&f.main(process.argv.slice(1)));return qa.exports}();\nu[\"./scope\"]=function(){var f={};(function(){var u=[].indexOf||function(f){for(var y=0,a=this.length;y<a;y++)if(y in this&&this[y]===f)return y;return-1};f.Scope=function(){function f(f,a,b,q){var g,h;this.parent=f;this.expressions=a;this.method=b;this.referencedVars=q;this.variables=[{name:\"arguments\",type:\"arguments\"}];this.positions={};this.parent||(this.utilities={});this.root=null!=(g=null!=(h=this.parent)?h.root:void 0)?g:this}f.prototype.add=function(f,a,b){return this.shared&&!b?this.parent.add(f,\na,b):Object.prototype.hasOwnProperty.call(this.positions,f)?this.variables[this.positions[f]].type=a:this.positions[f]=this.variables.push({name:f,type:a})-1};f.prototype.namedMethod=function(){var f;return null!=(f=this.method)&&f.name||!this.parent?this.method:this.parent.namedMethod()};f.prototype.find=function(f,a){null==a&&(a=\"var\");if(this.check(f))return!0;this.add(f,a);return!1};f.prototype.parameter=function(f){if(!this.shared||!this.parent.check(f,!0))return this.add(f,\"param\")};f.prototype.check=\nfunction(f){var a;return!!(this.type(f)||null!=(a=this.parent)&&a.check(f))};f.prototype.temporary=function(f,a,b){null==b&&(b=!1);return b?(b=f.charCodeAt(0),f=122-b,b=String.fromCharCode(b+a%(f+1)),a=Math.floor(a/(f+1)),\"\"+b+(a||\"\")):\"\"+f+(a||\"\")};f.prototype.type=function(f){var a;var b=this.variables;var q=0;for(a=b.length;q<a;q++){var g=b[q];if(g.name===f)return g.type}return null};f.prototype.freeVariable=function(f,a){var b,q;null==a&&(a={});for(b=0;;){var g=this.temporary(f,b,a.single);if(!(this.check(g)||\n0<=u.call(this.root.referencedVars,g)))break;b++}(null!=(q=a.reserve)?q:1)&&this.add(g,\"var\",!0);return g};f.prototype.assign=function(f,a){this.add(f,{value:a,assigned:!0},!0);return this.hasAssignments=!0};f.prototype.hasDeclarations=function(){return!!this.declaredVariables().length};f.prototype.declaredVariables=function(){var f;var a=this.variables;var b=[];var q=0;for(f=a.length;q<f;q++){var g=a[q];\"var\"===g.type&&b.push(g.name)}return b.sort()};f.prototype.assignedVariables=function(){var f;\nvar a=this.variables;var b=[];var q=0;for(f=a.length;q<f;q++){var g=a[q];g.type.assigned&&b.push(g.name+\" \\x3d \"+g.type.value)}return b};return f}()}).call(this);return f}();u[\"./nodes\"]=function(){var f={};(function(){var qa,q,y,a,b,ya,g,h,r,n,B,H,I,F,Q,x,J,O,R,z,l,c,w,m,k,K,P,L,V,X,G,aa,U,W,D,A,va,E,ba,ca,C,T,v=function(a,b){function p(){this.constructor=a}for(var d in b)Y.call(b,d)&&(a[d]=b[d]);p.prototype=b.prototype;a.prototype=new p;a.__super__=b.prototype;return a},Y={}.hasOwnProperty,S=[].indexOf||\nfunction(a){for(var b=0,p=this.length;b<p;b++)if(b in this&&this[b]===a)return b;return-1},M=[].slice;Error.stackTraceLimit=Infinity;var xa=u(\"./scope\").Scope;var sa=u(\"./lexer\");var za=sa.isUnassignable;var ma=sa.JS_FORBIDDEN;var Z=u(\"./helpers\");var fa=Z.compact;var ia=Z.flatten;var ga=Z.extend;var ja=Z.merge;var la=Z.del;sa=Z.addLocationDataFn;var oa=Z.locationDataToString;var pa=Z.throwSyntaxError;f.extend=ga;f.addLocationDataFn=sa;var ha=function(){return!0};var ka=function(){return!1};var na=\nfunction(){return this};var ra=function(){this.negated=!this.negated;return this};f.CodeFragment=r=function(){function a(a,b){var d;this.code=\"\"+b;this.locationData=null!=a?a.locationData:void 0;this.type=(null!=a?null!=(d=a.constructor)?d.name:void 0:void 0)||\"unknown\"}a.prototype.toString=function(){return\"\"+this.code+(this.locationData?\": \"+oa(this.locationData):\"\")};return a}();var da=function(a){var b;var p=[];var d=0;for(b=a.length;d<b;d++){var wa=a[d];p.push(wa.code)}return p.join(\"\")};f.Base=\nsa=function(){function b(){}b.prototype.compile=function(a,b){return da(this.compileToFragments(a,b))};b.prototype.compileToFragments=function(a,b){a=ga({},a);b&&(a.level=b);b=this.unfoldSoak(a)||this;b.tab=a.indent;return a.level!==N&&b.isStatement(a)?b.compileClosure(a):b.compileNode(a)};b.prototype.compileClosure=function(b){var p,d,t;(d=this.jumps())&&d.error(\"cannot use a pure statement in an expression\");b.sharedScope=!0;d=new h([],a.wrap([this]));var e=[];if((p=this.contains(Va))||this.contains(ea))e=\n[new E],p?(p=\"apply\",e.push(new x(\"arguments\"))):p=\"call\",d=new C(d,[new qa(new L(p))]);b=(new ya(d,e)).compileNode(b);if(d.isGenerator||null!=(t=d.base)&&t.isGenerator)b.unshift(this.makeCode(\"(yield* \")),b.push(this.makeCode(\")\"));return b};b.prototype.cache=function(a,b,d){if(null!=d?d(this):this.isComplex()){d=new x(a.scope.freeVariable(\"ref\"));var p=new y(d,this);return b?[p.compileToFragments(a,b),[this.makeCode(d.value)]]:[p,d]}d=b?this.compileToFragments(a,b):this;return[d,d]};b.prototype.cacheToCodeFragments=\nfunction(a){return[da(a[0]),da(a[1])]};b.prototype.makeReturn=function(a){var b=this.unwrapAll();return a?new ya(new z(a+\".push\"),[b]):new G(b)};b.prototype.contains=function(a){var b=void 0;this.traverseChildren(!1,function(d){if(a(d))return b=d,!1});return b};b.prototype.lastNonComment=function(a){var b;for(b=a.length;b--;)if(!(a[b]instanceof n))return a[b];return null};b.prototype.toString=function(a,b){null==a&&(a=\"\");null==b&&(b=this.constructor.name);var d=\"\\n\"+a+b;this.soak&&(d+=\"?\");this.eachChild(function(b){return d+=\nb.toString(a+Ca)});return d};b.prototype.eachChild=function(a){var b,d;if(!this.children)return this;var t=this.children;var e=0;for(b=t.length;e<b;e++){var c=t[e];if(this[c]){var f=ia([this[c]]);var g=0;for(d=f.length;g<d;g++)if(c=f[g],!1===a(c))return this}}return this};b.prototype.traverseChildren=function(a,b){return this.eachChild(function(d){if(!1!==b(d))return d.traverseChildren(a,b)})};b.prototype.invert=function(){return new k(\"!\",this)};b.prototype.unwrapAll=function(){var a;for(a=this;a!==\n(a=a.unwrap()););return a};b.prototype.children=[];b.prototype.isStatement=ka;b.prototype.jumps=ka;b.prototype.isComplex=ha;b.prototype.isChainable=ka;b.prototype.isAssignable=ka;b.prototype.isNumber=ka;b.prototype.unwrap=na;b.prototype.unfoldSoak=ka;b.prototype.assigns=ka;b.prototype.updateLocationDataIfMissing=function(a){if(this.locationData)return this;this.locationData=a;return this.eachChild(function(b){return b.updateLocationDataIfMissing(a)})};b.prototype.error=function(a){return pa(a,this.locationData)};\nb.prototype.makeCode=function(a){return new r(this,a)};b.prototype.wrapInBraces=function(a){return[].concat(this.makeCode(\"(\"),a,this.makeCode(\")\"))};b.prototype.joinFragmentArrays=function(a,b){var d,p;var e=[];var t=d=0;for(p=a.length;d<p;t=++d){var c=a[t];t&&e.push(this.makeCode(b));e=e.concat(c)}return e};return b}();f.Block=a=function(a){function b(a){this.expressions=fa(ia(a||[]))}v(b,a);b.prototype.children=[\"expressions\"];b.prototype.push=function(a){this.expressions.push(a);return this};\nb.prototype.pop=function(){return this.expressions.pop()};b.prototype.unshift=function(a){this.expressions.unshift(a);return this};b.prototype.unwrap=function(){return 1===this.expressions.length?this.expressions[0]:this};b.prototype.isEmpty=function(){return!this.expressions.length};b.prototype.isStatement=function(a){var d;var b=this.expressions;var e=0;for(d=b.length;e<d;e++){var p=b[e];if(p.isStatement(a))return!0}return!1};b.prototype.jumps=function(a){var d;var b=this.expressions;var e=0;for(d=\nb.length;e<d;e++){var p=b[e];if(p=p.jumps(a))return p}};b.prototype.makeReturn=function(a){var d;for(d=this.expressions.length;d--;){var b=this.expressions[d];if(!(b instanceof n)){this.expressions[d]=b.makeReturn(a);b instanceof G&&!b.expression&&this.expressions.splice(d,1);break}}return this};b.prototype.compileToFragments=function(a,d){null==a&&(a={});return a.scope?b.__super__.compileToFragments.call(this,a,d):this.compileRoot(a)};b.prototype.compileNode=function(a){var d,p;this.tab=a.indent;\nvar e=a.level===N;var t=[];var c=this.expressions;var f=d=0;for(p=c.length;d<p;f=++d){var g=c[f];g=g.unwrapAll();g=g.unfoldSoak(a)||g;g instanceof b?t.push(g.compileNode(a)):e?(g.front=!0,f=g.compileToFragments(a),g.isStatement(a)||(f.unshift(this.makeCode(\"\"+this.tab)),f.push(this.makeCode(\";\"))),t.push(f)):t.push(g.compileToFragments(a,ta))}if(e)return this.spaced?[].concat(this.joinFragmentArrays(t,\"\\n\\n\"),this.makeCode(\"\\n\")):this.joinFragmentArrays(t,\"\\n\");d=t.length?this.joinFragmentArrays(t,\n\", \"):[this.makeCode(\"void 0\")];return 1<t.length&&a.level>=ta?this.wrapInBraces(d):d};b.prototype.compileRoot=function(a){var d,b;a.indent=a.bare?\"\":Ca;a.level=N;this.spaced=!0;a.scope=new xa(null,this,null,null!=(b=a.referencedVars)?b:[]);var e=a.locals||[];b=0;for(d=e.length;b<d;b++){var p=e[b];a.scope.parameter(p)}b=[];if(!a.bare){var t=this.expressions;d=[];var c=p=0;for(e=t.length;p<e;c=++p){c=t[c];if(!(c.unwrap()instanceof n))break;d.push(c)}p=this.expressions.slice(d.length);this.expressions=\nd;d.length&&(b=this.compileNode(ja(a,{indent:\"\"})),b.push(this.makeCode(\"\\n\")));this.expressions=p}d=this.compileWithDeclarations(a);return a.bare?d:[].concat(b,this.makeCode(\"(function() {\\n\"),d,this.makeCode(\"\\n}).call(this);\\n\"))};b.prototype.compileWithDeclarations=function(a){var d,b;var e=[];var p=this.expressions;var t=b=0;for(d=p.length;b<d;t=++b){var c=p[t];c=c.unwrap();if(!(c instanceof n||c instanceof z))break}a=ja(a,{level:N});t&&(c=this.expressions.splice(t,9E9),e=[this.spaced,!1],b=\ne[0],this.spaced=e[1],b=[this.compileNode(a),b],e=b[0],this.spaced=b[1],this.expressions=c);c=this.compileNode(a);b=a.scope;b.expressions===this&&(d=a.scope.hasDeclarations(),a=b.hasAssignments,d||a?(t&&e.push(this.makeCode(\"\\n\")),e.push(this.makeCode(this.tab+\"var \")),d&&e.push(this.makeCode(b.declaredVariables().join(\", \"))),a&&(d&&e.push(this.makeCode(\",\\n\"+(this.tab+Ca))),e.push(this.makeCode(b.assignedVariables().join(\",\\n\"+(this.tab+Ca))))),e.push(this.makeCode(\";\\n\"+(this.spaced?\"\\n\":\"\")))):\ne.length&&c.length&&e.push(this.makeCode(\"\\n\")));return e.concat(c)};b.wrap=function(a){return 1===a.length&&a[0]instanceof b?a[0]:new b(a)};return b}(sa);f.Literal=z=function(a){function b(a){this.value=a}v(b,a);b.prototype.isComplex=ka;b.prototype.assigns=function(a){return a===this.value};b.prototype.compileNode=function(a){return[this.makeCode(this.value)]};b.prototype.toString=function(){return\" \"+(this.isStatement()?b.__super__.toString.apply(this,arguments):this.constructor.name)+\": \"+this.value};\nreturn b}(sa);f.NumberLiteral=w=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(z);f.InfinityLiteral=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.compileNode=function(){return[this.makeCode(\"2e308\")]};return b}(w);f.NaNLiteral=function(a){function b(){b.__super__.constructor.call(this,\"NaN\")}v(b,a);b.prototype.compileNode=function(a){var d=[this.makeCode(\"0/0\")];return a.level>=Fa?this.wrapInBraces(d):\nd};return b}(w);f.StringLiteral=D=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(z);f.RegexLiteral=X=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(z);f.PassthroughLiteral=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(z);f.IdentifierLiteral=x=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.isAssignable=ha;\nreturn b}(z);f.PropertyName=L=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.isAssignable=ha;return b}(z);f.StatementLiteral=W=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.isStatement=ha;b.prototype.makeReturn=na;b.prototype.jumps=function(a){if(\"break\"===this.value&&!(null!=a&&a.loop||null!=a&&a.block)||\"continue\"===this.value&&(null==a||!a.loop))return this};b.prototype.compileNode=function(a){return[this.makeCode(\"\"+\nthis.tab+this.value+\";\")]};return b}(z);f.ThisLiteral=E=function(a){function b(){b.__super__.constructor.call(this,\"this\")}v(b,a);b.prototype.compileNode=function(a){var d;a=null!=(d=a.scope.method)&&d.bound?a.scope.method.context:this.value;return[this.makeCode(a)]};return b}(z);f.UndefinedLiteral=ca=function(a){function b(){b.__super__.constructor.call(this,\"undefined\")}v(b,a);b.prototype.compileNode=function(a){return[this.makeCode(a.level>=Ga?\"(void 0)\":\"void 0\")]};return b}(z);f.NullLiteral=\nc=function(a){function b(){b.__super__.constructor.call(this,\"null\")}v(b,a);return b}(z);f.BooleanLiteral=b=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(z);f.Return=G=function(a){function b(a){this.expression=a}v(b,a);b.prototype.children=[\"expression\"];b.prototype.isStatement=ha;b.prototype.makeReturn=na;b.prototype.jumps=na;b.prototype.compileToFragments=function(a,d){var p;var e=null!=(p=this.expression)?p.makeReturn():void 0;return!e||e instanceof\nb?b.__super__.compileToFragments.call(this,a,d):e.compileToFragments(a,d)};b.prototype.compileNode=function(a){var b=[];b.push(this.makeCode(this.tab+(\"return\"+(this.expression?\" \":\"\"))));this.expression&&(b=b.concat(this.expression.compileToFragments(a,Ka)));b.push(this.makeCode(\";\"));return b};return b}(sa);f.YieldReturn=T=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.compileNode=function(a){null==a.scope.parent&&this.error(\"yield can only occur inside functions\");\nreturn b.__super__.compileNode.apply(this,arguments)};return b}(G);f.Value=C=function(a){function t(a,b,wa){if(!b&&a instanceof t)return a;this.base=a;this.properties=b||[];wa&&(this[wa]=!0);return this}v(t,a);t.prototype.children=[\"base\",\"properties\"];t.prototype.add=function(a){this.properties=this.properties.concat(a);return this};t.prototype.hasProperties=function(){return!!this.properties.length};t.prototype.bareLiteral=function(a){return!this.properties.length&&this.base instanceof a};t.prototype.isArray=\nfunction(){return this.bareLiteral(q)};t.prototype.isRange=function(){return this.bareLiteral(V)};t.prototype.isComplex=function(){return this.hasProperties()||this.base.isComplex()};t.prototype.isAssignable=function(){return this.hasProperties()||this.base.isAssignable()};t.prototype.isNumber=function(){return this.bareLiteral(w)};t.prototype.isString=function(){return this.bareLiteral(D)};t.prototype.isRegex=function(){return this.bareLiteral(X)};t.prototype.isUndefined=function(){return this.bareLiteral(ca)};\nt.prototype.isNull=function(){return this.bareLiteral(c)};t.prototype.isBoolean=function(){return this.bareLiteral(b)};t.prototype.isAtomic=function(){var a;var b=this.properties.concat(this.base);var wa=0;for(a=b.length;wa<a;wa++){var e=b[wa];if(e.soak||e instanceof ya)return!1}return!0};t.prototype.isNotCallable=function(){return this.isNumber()||this.isString()||this.isRegex()||this.isArray()||this.isRange()||this.isSplice()||this.isObject()||this.isUndefined()||this.isNull()||this.isBoolean()};\nt.prototype.isStatement=function(a){return!this.properties.length&&this.base.isStatement(a)};t.prototype.assigns=function(a){return!this.properties.length&&this.base.assigns(a)};t.prototype.jumps=function(a){return!this.properties.length&&this.base.jumps(a)};t.prototype.isObject=function(a){return this.properties.length?!1:this.base instanceof m&&(!a||this.base.generated)};t.prototype.isSplice=function(){var a=this.properties;return a[a.length-1]instanceof aa};t.prototype.looksStatic=function(a){var b;\nreturn this.base.value===a&&1===this.properties.length&&\"prototype\"!==(null!=(b=this.properties[0].name)?b.value:void 0)};t.prototype.unwrap=function(){return this.properties.length?this:this.base};t.prototype.cacheReference=function(a){var b=this.properties;var p=b[b.length-1];if(2>this.properties.length&&!this.base.isComplex()&&(null==p||!p.isComplex()))return[this,this];b=new t(this.base,this.properties.slice(0,-1));if(b.isComplex()){var e=new x(a.scope.freeVariable(\"base\"));b=new t(new P(new y(e,\nb)))}if(!p)return[b,e];if(p.isComplex()){var c=new x(a.scope.freeVariable(\"name\"));p=new R(new y(c,p.index));c=new R(c)}return[b.add(p),new t(e||b.base,[c||p])]};t.prototype.compileNode=function(a){var b;this.base.front=this.front;var p=this.properties;var e=this.base.compileToFragments(a,p.length?Ga:null);p.length&&Pa.test(da(e))&&e.push(this.makeCode(\".\"));var t=0;for(b=p.length;t<b;t++){var c=p[t];e.push.apply(e,c.compileToFragments(a))}return e};t.prototype.unfoldSoak=function(a){return null!=\nthis.unfoldedSoak?this.unfoldedSoak:this.unfoldedSoak=function(b){return function(){var d,e,p;if(e=b.base.unfoldSoak(a))return(d=e.body.properties).push.apply(d,b.properties),e;var c=b.properties;e=d=0;for(p=c.length;d<p;e=++d){var f=c[e];if(f.soak)return f.soak=!1,d=new t(b.base,b.properties.slice(0,e)),p=new t(b.base,b.properties.slice(e)),d.isComplex()&&(e=new x(a.scope.freeVariable(\"ref\")),d=new P(new y(e,d)),p.base=e),new J(new B(d),p,{soak:!0})}return!1}}(this)()};return t}(sa);f.Comment=n=\nfunction(a){function b(a){this.comment=a}v(b,a);b.prototype.isStatement=ha;b.prototype.makeReturn=na;b.prototype.compileNode=function(a,b){var d=this.comment.replace(/^(\\s*)#(?=\\s)/gm,\"$1 *\");d=\"/*\"+Ea(d,this.tab)+(0<=S.call(d,\"\\n\")?\"\\n\"+this.tab:\"\")+\" */\";(b||a.level)===N&&(d=a.indent+d);return[this.makeCode(\"\\n\"),this.makeCode(d)]};return b}(sa);f.Call=ya=function(a){function b(a,b,c){this.variable=a;this.args=null!=b?b:[];this.soak=c;this.isNew=!1;this.variable instanceof C&&this.variable.isNotCallable()&&\nthis.variable.error(\"literal is not a function\")}v(b,a);b.prototype.children=[\"variable\",\"args\"];b.prototype.updateLocationDataIfMissing=function(a){var d;if(this.locationData&&this.needsUpdatedStartLocation){this.locationData.first_line=a.first_line;this.locationData.first_column=a.first_column;var p=(null!=(d=this.variable)?d.base:void 0)||this.variable;p.needsUpdatedStartLocation&&(this.variable.locationData.first_line=a.first_line,this.variable.locationData.first_column=a.first_column,p.updateLocationDataIfMissing(a));\ndelete this.needsUpdatedStartLocation}return b.__super__.updateLocationDataIfMissing.apply(this,arguments)};b.prototype.newInstance=function(){var a;var d=(null!=(a=this.variable)?a.base:void 0)||this.variable;d instanceof b&&!d.isNew?d.newInstance():this.isNew=!0;this.needsUpdatedStartLocation=!0;return this};b.prototype.unfoldSoak=function(a){var d,p;if(this.soak){if(this instanceof va){var e=new z(this.superReference(a));var c=new C(e)}else{if(c=Ba(a,this,\"variable\"))return c;c=(new C(this.variable)).cacheReference(a);\ne=c[0];c=c[1]}c=new b(c,this.args);c.isNew=this.isNew;e=new z(\"typeof \"+e.compile(a)+' \\x3d\\x3d\\x3d \"function\"');return new J(e,new C(c),{soak:!0})}e=this;for(d=[];;)if(e.variable instanceof b)d.push(e),e=e.variable;else{if(!(e.variable instanceof C))break;d.push(e);if(!((e=e.variable.base)instanceof b))break}var t=d.reverse();d=0;for(p=t.length;d<p;d++)e=t[d],c&&(e.variable instanceof b?e.variable=c:e.variable.base=c),c=Ba(a,e,\"variable\");return c};b.prototype.compileNode=function(a){var b,p,e;null!=\n(b=this.variable)&&(b.front=this.front);b=U.compileSplattedArray(a,this.args,!0);if(b.length)return this.compileSplat(a,b);b=[];var c=this.args;var t=p=0;for(e=c.length;p<e;t=++p){var f=c[t];t&&b.push(this.makeCode(\", \"));b.push.apply(b,f.compileToFragments(a,ta))}f=[];this instanceof va?(a=this.superReference(a)+(\".call(\"+this.superThis(a)),b.length&&(a+=\", \"),f.push(this.makeCode(a))):(this.isNew&&f.push(this.makeCode(\"new \")),f.push.apply(f,this.variable.compileToFragments(a,Ga)),f.push(this.makeCode(\"(\")));\nf.push.apply(f,b);f.push(this.makeCode(\")\"));return f};b.prototype.compileSplat=function(a,b){var d;if(this instanceof va)return[].concat(this.makeCode(this.superReference(a)+\".apply(\"+this.superThis(a)+\", \"),b,this.makeCode(\")\"));if(this.isNew){var e=this.tab+Ca;return[].concat(this.makeCode(\"(function(func, args, ctor) {\\n\"+e+\"ctor.prototype \\x3d func.prototype;\\n\"+e+\"var child \\x3d new ctor, result \\x3d func.apply(child, args);\\n\"+e+\"return Object(result) \\x3d\\x3d\\x3d result ? result : child;\\n\"+\nthis.tab+\"})(\"),this.variable.compileToFragments(a,ta),this.makeCode(\", \"),b,this.makeCode(\", function(){})\"))}e=[];var p=new C(this.variable);if((d=p.properties.pop())&&p.isComplex()){var c=a.scope.freeVariable(\"ref\");e=e.concat(this.makeCode(\"(\"+c+\" \\x3d \"),p.compileToFragments(a,ta),this.makeCode(\")\"),d.compileToFragments(a))}else p=p.compileToFragments(a,Ga),Pa.test(da(p))&&(p=this.wrapInBraces(p)),d?(c=da(p),p.push.apply(p,d.compileToFragments(a))):c=\"null\",e=e.concat(p);return e.concat(this.makeCode(\".apply(\"+\nc+\", \"),b,this.makeCode(\")\"))};return b}(sa);f.SuperCall=va=function(a){function b(a){b.__super__.constructor.call(this,null,null!=a?a:[new U(new x(\"arguments\"))]);this.isBare=null!=a}v(b,a);b.prototype.superReference=function(a){var b=a.scope.namedMethod();if(null!=b&&b.klass){var p=b.klass;var e=b.name;var c=b.variable;if(p.isComplex()){var t=new x(a.scope.parent.freeVariable(\"base\"));var f=new C(new P(new y(t,p)));c.base=f;c.properties.splice(0,p.properties.length)}if(e.isComplex()||e instanceof\nR&&e.index.isAssignable()){var g=new x(a.scope.parent.freeVariable(\"name\"));e=new R(new y(g,e.index));c.properties.pop();c.properties.push(e)}f=[new qa(new L(\"__super__\"))];b[\"static\"]&&f.push(new qa(new L(\"constructor\")));f.push(null!=g?new R(g):e);return(new C(null!=t?t:p,f)).compile(a)}return null!=b&&b.ctor?b.name+\".__super__.constructor\":this.error(\"cannot call super outside of an instance method.\")};b.prototype.superThis=function(a){return(a=a.scope.method)&&!a.klass&&a.context||\"this\"};return b}(ya);\nf.RegexWithInterpolations=function(a){function b(a){null==a&&(a=[]);b.__super__.constructor.call(this,new C(new x(\"RegExp\")),a,!1)}v(b,a);return b}(ya);f.TaggedTemplateCall=function(b){function c(b,d,t){d instanceof D&&(d=new A(a.wrap([new C(d)])));c.__super__.constructor.call(this,b,[d],t)}v(c,b);c.prototype.compileNode=function(a){a.inTaggedTemplateCall=!0;return this.variable.compileToFragments(a,Ga).concat(this.args[0].compileToFragments(a,ta))};return c}(ya);f.Extends=F=function(a){function b(a,\nb){this.child=a;this.parent=b}v(b,a);b.prototype.children=[\"child\",\"parent\"];b.prototype.compileToFragments=function(a){return(new ya(new C(new z(Ia(\"extend\",a))),[this.child,this.parent])).compileToFragments(a)};return b}(sa);f.Access=qa=function(a){function b(a,b){this.name=a;this.soak=\"soak\"===b}v(b,a);b.prototype.children=[\"name\"];b.prototype.compileToFragments=function(a){var b;a=this.name.compileToFragments(a);var p=this.name.unwrap();return p instanceof L?(b=p.value,0<=S.call(ma,b))?[this.makeCode('[\"')].concat(M.call(a),\n[this.makeCode('\"]')]):[this.makeCode(\".\")].concat(M.call(a)):[this.makeCode(\"[\")].concat(M.call(a),[this.makeCode(\"]\")])};b.prototype.isComplex=ka;return b}(sa);f.Index=R=function(a){function b(a){this.index=a}v(b,a);b.prototype.children=[\"index\"];b.prototype.compileToFragments=function(a){return[].concat(this.makeCode(\"[\"),this.index.compileToFragments(a,Ka),this.makeCode(\"]\"))};b.prototype.isComplex=function(){return this.index.isComplex()};return b}(sa);f.Range=V=function(a){function b(a,b,c){this.from=\na;this.to=b;this.equals=(this.exclusive=\"exclusive\"===c)?\"\":\"\\x3d\"}v(b,a);b.prototype.children=[\"from\",\"to\"];b.prototype.compileVariables=function(a){a=ja(a,{top:!0});var b=la(a,\"isComplex\");var p=this.cacheToCodeFragments(this.from.cache(a,ta,b));this.fromC=p[0];this.fromVar=p[1];p=this.cacheToCodeFragments(this.to.cache(a,ta,b));this.toC=p[0];this.toVar=p[1];if(p=la(a,\"step\"))a=this.cacheToCodeFragments(p.cache(a,ta,b)),this.step=a[0],this.stepVar=a[1];this.fromNum=this.from.isNumber()?Number(this.fromVar):\nnull;this.toNum=this.to.isNumber()?Number(this.toVar):null;return this.stepNum=null!=p&&p.isNumber()?Number(this.stepVar):null};b.prototype.compileNode=function(a){var b,p,e,c;this.fromVar||this.compileVariables(a);if(!a.index)return this.compileArray(a);var t=null!=this.fromNum&&null!=this.toNum;var f=la(a,\"index\");var g=(a=la(a,\"name\"))&&a!==f;var k=f+\" \\x3d \"+this.fromC;this.toC!==this.toVar&&(k+=\", \"+this.toC);this.step!==this.stepVar&&(k+=\", \"+this.step);var h=[f+\" \\x3c\"+this.equals,f+\" \\x3e\"+\nthis.equals];var m=h[0];h=h[1];m=null!=this.stepNum?0<this.stepNum?m+\" \"+this.toVar:h+\" \"+this.toVar:t?(e=[this.fromNum,this.toNum],p=e[0],c=e[1],e,p<=c?m+\" \"+c:h+\" \"+c):(b=this.stepVar?this.stepVar+\" \\x3e 0\":this.fromVar+\" \\x3c\\x3d \"+this.toVar,b+\" ? \"+m+\" \"+this.toVar+\" : \"+h+\" \"+this.toVar);b=this.stepVar?f+\" +\\x3d \"+this.stepVar:t?g?p<=c?\"++\"+f:\"--\"+f:p<=c?f+\"++\":f+\"--\":g?b+\" ? ++\"+f+\" : --\"+f:b+\" ? \"+f+\"++ : \"+f+\"--\";g&&(k=a+\" \\x3d \"+k);g&&(b=a+\" \\x3d \"+b);return[this.makeCode(k+\"; \"+m+\"; \"+\nb)]};b.prototype.compileArray=function(a){var b,p,e;if((b=null!=this.fromNum&&null!=this.toNum)&&20>=Math.abs(this.fromNum-this.toNum)){var c=function(){e=[];for(var a=p=this.fromNum,b=this.toNum;p<=b?a<=b:a>=b;p<=b?a++:a--)e.push(a);return e}.apply(this);this.exclusive&&c.pop();return[this.makeCode(\"[\"+c.join(\", \")+\"]\")]}var t=this.tab+Ca;var f=a.scope.freeVariable(\"i\",{single:!0});var g=a.scope.freeVariable(\"results\");var k=\"\\n\"+t+g+\" \\x3d [];\";if(b)a.index=f,b=da(this.compileNode(a));else{var h=\nf+\" \\x3d \"+this.fromC+(this.toC!==this.toVar?\", \"+this.toC:\"\");b=this.fromVar+\" \\x3c\\x3d \"+this.toVar;b=\"var \"+h+\"; \"+b+\" ? \"+f+\" \\x3c\"+this.equals+\" \"+this.toVar+\" : \"+f+\" \\x3e\"+this.equals+\" \"+this.toVar+\"; \"+b+\" ? \"+f+\"++ : \"+f+\"--\"}f=\"{ \"+g+\".push(\"+f+\"); }\\n\"+t+\"return \"+g+\";\\n\"+a.indent;a=function(a){return null!=a?a.contains(Va):void 0};if(a(this.from)||a(this.to))c=\", arguments\";return[this.makeCode(\"(function() {\"+k+\"\\n\"+t+\"for (\"+b+\")\"+f+\"}).apply(this\"+(null!=c?c:\"\")+\")\")]};return b}(sa);\nf.Slice=aa=function(a){function b(a){this.range=a;b.__super__.constructor.call(this)}v(b,a);b.prototype.children=[\"range\"];b.prototype.compileNode=function(a){var b=this.range;var p=b.to;var e=(b=b.from)&&b.compileToFragments(a,Ka)||[this.makeCode(\"0\")];if(p){b=p.compileToFragments(a,Ka);var c=da(b);if(this.range.exclusive||-1!==+c)var t=\", \"+(this.range.exclusive?c:p.isNumber()?\"\"+(+c+1):(b=p.compileToFragments(a,Ga),\"+\"+da(b)+\" + 1 || 9e9\"))}return[this.makeCode(\".slice(\"+da(e)+(t||\"\")+\")\")]};return b}(sa);\nf.Obj=m=function(a){function b(a,b){this.generated=null!=b?b:!1;this.objects=this.properties=a||[]}v(b,a);b.prototype.children=[\"properties\"];b.prototype.compileNode=function(a){var b,p,e;var c=this.properties;if(this.generated){var t=0;for(b=c.length;t<b;t++){var f=c[t];f instanceof C&&f.error(\"cannot have an implicit value in an implicit object\")}}t=b=0;for(f=c.length;b<f;t=++b){var g=c[t];if((g.variable||g).base instanceof P)break}f=t<c.length;var k=a.indent+=Ca;var h=this.lastNonComment(this.properties);\nb=[];if(f){var m=a.scope.freeVariable(\"obj\");b.push(this.makeCode(\"(\\n\"+k+m+\" \\x3d \"))}b.push(this.makeCode(\"{\"+(0===c.length||0===t?\"}\":\"\\n\")));var l=p=0;for(e=c.length;p<e;l=++p){g=c[l];l===t&&(0!==l&&b.push(this.makeCode(\"\\n\"+k+\"}\")),b.push(this.makeCode(\",\\n\")));var w=l===c.length-1||l===t-1?\"\":g===h||g instanceof n?\"\\n\":\",\\n\";var q=g instanceof n?\"\":k;f&&l<t&&(q+=Ca);g instanceof y&&(\"object\"!==g.context&&g.operatorToken.error(\"unexpected \"+g.operatorToken.value),g.variable instanceof C&&g.variable.hasProperties()&&\ng.variable.error(\"invalid object key\"));g instanceof C&&g[\"this\"]&&(g=new y(g.properties[0].name,g,\"object\"));g instanceof n||(l<t?g instanceof y||(g=new y(g,g,\"object\")):(g instanceof y?(l=g.variable,g=g.value):(g=g.base.cache(a),l=g[0],g=g[1],l instanceof x&&(l=new L(l.value))),g=new y(new C(new x(m),[new qa(l)]),g)));q&&b.push(this.makeCode(q));b.push.apply(b,g.compileToFragments(a,N));w&&b.push(this.makeCode(w))}f?b.push(this.makeCode(\",\\n\"+k+m+\"\\n\"+this.tab+\")\")):0!==c.length&&b.push(this.makeCode(\"\\n\"+\nthis.tab+\"}\"));return this.front&&!f?this.wrapInBraces(b):b};b.prototype.assigns=function(a){var b;var p=this.properties;var e=0;for(b=p.length;e<b;e++){var c=p[e];if(c.assigns(a))return!0}return!1};return b}(sa);f.Arr=q=function(a){function b(a){this.objects=a||[]}v(b,a);b.prototype.children=[\"objects\"];b.prototype.compileNode=function(a){var b;if(!this.objects.length)return[this.makeCode(\"[]\")];a.indent+=Ca;var p=U.compileSplattedArray(a,this.objects);if(p.length)return p;p=[];var e=this.objects;\nvar c=[];var t=0;for(b=e.length;t<b;t++){var f=e[t];c.push(f.compileToFragments(a,ta))}t=b=0;for(e=c.length;b<e;t=++b)f=c[t],t&&p.push(this.makeCode(\", \")),p.push.apply(p,f);0<=da(p).indexOf(\"\\n\")?(p.unshift(this.makeCode(\"[\\n\"+a.indent)),p.push(this.makeCode(\"\\n\"+this.tab+\"]\"))):(p.unshift(this.makeCode(\"[\")),p.push(this.makeCode(\"]\")));return p};b.prototype.assigns=function(a){var b;var p=this.objects;var e=0;for(b=p.length;e<b;e++){var c=p[e];if(c.assigns(a))return!0}return!1};return b}(sa);f.Class=\ng=function(b){function c(b,d,c){this.variable=b;this.parent=d;this.body=null!=c?c:new a;this.boundFuncs=[];this.body.classBody=!0}v(c,b);c.prototype.children=[\"variable\",\"parent\",\"body\"];c.prototype.defaultClassVariableName=\"_Class\";c.prototype.determineName=function(){var a;if(!this.variable)return this.defaultClassVariableName;var b=this.variable.properties;b=(a=b[b.length-1])?a instanceof qa&&a.name:this.variable.base;if(!(b instanceof x||b instanceof L))return this.defaultClassVariableName;b=\nb.value;a||(a=za(b))&&this.variable.error(a);return 0<=S.call(ma,b)?\"_\"+b:b};c.prototype.setContext=function(a){return this.body.traverseChildren(!1,function(b){if(b.classBody)return!1;if(b instanceof E)return b.value=a;if(b instanceof h&&b.bound)return b.context=a})};c.prototype.addBoundFunctions=function(a){var b;var p=this.boundFuncs;var e=0;for(b=p.length;e<b;e++){var c=p[e];c=(new C(new E,[new qa(c)])).compile(a);this.ctor.body.unshift(new z(c+\" \\x3d \"+Ia(\"bind\",a)+\"(\"+c+\", this)\"))}};c.prototype.addProperties=\nfunction(a,b,c){var d;var p=a.base.properties.slice(0);var f;for(f=[];d=p.shift();){if(d instanceof y){var t=d.variable.base;delete d.context;var g=d.value;\"constructor\"===t.value?(this.ctor&&d.error(\"cannot define more than one constructor in a class\"),g.bound&&d.error(\"cannot define a constructor as a bound function\"),g instanceof h?d=this.ctor=g:(this.externalCtor=c.classScope.freeVariable(\"ctor\"),d=new y(new x(this.externalCtor),g))):d.variable[\"this\"]?g[\"static\"]=!0:(a=t.isComplex()?new R(t):\nnew qa(t),d.variable=new C(new x(b),[new qa(new L(\"prototype\")),a]),g instanceof h&&g.bound&&(this.boundFuncs.push(t),g.bound=!1))}f.push(d)}return fa(f)};c.prototype.walkBody=function(b,d){return this.traverseChildren(!1,function(p){return function(e){var f,t,g;var wa=!0;if(e instanceof c)return!1;if(e instanceof a){var k=f=e.expressions;var h=t=0;for(g=k.length;t<g;h=++t){var m=k[h];m instanceof y&&m.variable.looksStatic(b)?m.value[\"static\"]=!0:m instanceof C&&m.isObject(!0)&&(wa=!1,f[h]=p.addProperties(m,\nb,d))}e.expressions=ia(f)}return wa&&!(e instanceof c)}}(this))};c.prototype.hoistDirectivePrologue=function(){var a,b;var c=0;for(a=this.body.expressions;(b=a[c])&&b instanceof n||b instanceof C&&b.isString();)++c;return this.directives=a.splice(0,c)};c.prototype.ensureConstructor=function(a){this.ctor||(this.ctor=new h,this.externalCtor?this.ctor.body.push(new z(this.externalCtor+\".apply(this, arguments)\")):this.parent&&this.ctor.body.push(new z(a+\".__super__.constructor.apply(this, arguments)\")),\nthis.ctor.body.makeReturn(),this.body.expressions.unshift(this.ctor));this.ctor.ctor=this.ctor.name=a;this.ctor.klass=null;return this.ctor.noReturn=!0};c.prototype.compileNode=function(b){var d,c,e;(c=this.body.jumps())&&c.error(\"Class bodies cannot contain pure statements\");(d=this.body.contains(Va))&&d.error(\"Class bodies shouldn't reference arguments\");var p=this.determineName();var f=new x(p);c=new h([],a.wrap([this.body]));d=[];b.classScope=c.makeScope(b.scope);this.hoistDirectivePrologue();\nthis.setContext(p);this.walkBody(p,b);this.ensureConstructor(p);this.addBoundFunctions(b);this.body.spaced=!0;this.body.expressions.push(f);this.parent&&(p=new x(b.classScope.freeVariable(\"superClass\",{reserve:!1})),this.body.expressions.unshift(new F(f,p)),c.params.push(new K(p)),d.push(this.parent));(e=this.body.expressions).unshift.apply(e,this.directives);e=new P(new ya(c,d));this.variable&&(e=new y(this.variable,e,null,{moduleDeclaration:this.moduleDeclaration}));return e.compileToFragments(b)};\nreturn c}(sa);f.ModuleDeclaration=Z=function(a){function b(a,b){this.clause=a;this.source=b;this.checkSource()}v(b,a);b.prototype.children=[\"clause\",\"source\"];b.prototype.isStatement=ha;b.prototype.jumps=na;b.prototype.makeReturn=na;b.prototype.checkSource=function(){if(null!=this.source&&this.source instanceof A)return this.source.error(\"the name of the module to be imported from must be an uninterpolated string\")};b.prototype.checkScope=function(a,b){if(0!==a.indent.length)return this.error(b+\" statements must be at top-level scope\")};\nreturn b}(sa);f.ImportDeclaration=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.compileNode=function(a){var b;this.checkScope(a,\"import\");a.importedSymbols=[];var c=[];c.push(this.makeCode(this.tab+\"import \"));null!=this.clause&&c.push.apply(c,this.clause.compileNode(a));null!=(null!=(b=this.source)?b.value:void 0)&&(null!==this.clause&&c.push(this.makeCode(\" from \")),c.push(this.makeCode(this.source.value)));c.push(this.makeCode(\";\"));return c};\nreturn b}(Z);f.ImportClause=function(a){function b(a,b){this.defaultBinding=a;this.namedImports=b}v(b,a);b.prototype.children=[\"defaultBinding\",\"namedImports\"];b.prototype.compileNode=function(a){var b=[];null!=this.defaultBinding&&(b.push.apply(b,this.defaultBinding.compileNode(a)),null!=this.namedImports&&b.push(this.makeCode(\", \")));null!=this.namedImports&&b.push.apply(b,this.namedImports.compileNode(a));return b};return b}(sa);f.ExportDeclaration=Z=function(b){function c(){return c.__super__.constructor.apply(this,\narguments)}v(c,b);c.prototype.compileNode=function(b){var d;this.checkScope(b,\"export\");var c=[];c.push(this.makeCode(this.tab+\"export \"));this instanceof I&&c.push(this.makeCode(\"default \"));this instanceof I||!(this.clause instanceof y||this.clause instanceof g)||(this.clause instanceof g&&!this.clause.variable&&this.clause.error(\"anonymous classes cannot be exported\"),c.push(this.makeCode(\"var \")),this.clause.moduleDeclaration=\"export\");c=null!=this.clause.body&&this.clause.body instanceof a?c.concat(this.clause.compileToFragments(b,\nN)):c.concat(this.clause.compileNode(b));null!=(null!=(d=this.source)?d.value:void 0)&&c.push(this.makeCode(\" from \"+this.source.value));c.push(this.makeCode(\";\"));return c};return c}(Z);f.ExportNamedDeclaration=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(Z);f.ExportDefaultDeclaration=I=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(Z);f.ExportAllDeclaration=function(a){function b(){return b.__super__.constructor.apply(this,\narguments)}v(b,a);return b}(Z);f.ModuleSpecifierList=Z=function(a){function b(a){this.specifiers=a}v(b,a);b.prototype.children=[\"specifiers\"];b.prototype.compileNode=function(a){var b;var c=[];a.indent+=Ca;var e=this.specifiers;var p=[];var f=0;for(b=e.length;f<b;f++){var g=e[f];p.push(g.compileToFragments(a,ta))}if(0!==this.specifiers.length){c.push(this.makeCode(\"{\\n\"+a.indent));f=b=0;for(e=p.length;b<e;f=++b)g=p[f],f&&c.push(this.makeCode(\",\\n\"+a.indent)),c.push.apply(c,g);c.push(this.makeCode(\"\\n}\"))}else c.push(this.makeCode(\"{}\"));\nreturn c};return b}(sa);f.ImportSpecifierList=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(Z);f.ExportSpecifierList=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(Z);f.ModuleSpecifier=l=function(a){function b(a,b,c){this.original=a;this.alias=b;this.moduleDeclarationType=c;this.identifier=null!=this.alias?this.alias.value:this.original.value}v(b,a);b.prototype.children=[\"original\",\"alias\"];b.prototype.compileNode=\nfunction(a){a.scope.find(this.identifier,this.moduleDeclarationType);a=[];a.push(this.makeCode(this.original.value));null!=this.alias&&a.push(this.makeCode(\" as \"+this.alias.value));return a};return b}(sa);f.ImportSpecifier=Z=function(a){function b(a,d){b.__super__.constructor.call(this,a,d,\"import\")}v(b,a);b.prototype.compileNode=function(a){var d;(d=this.identifier,0<=S.call(a.importedSymbols,d))||a.scope.check(this.identifier)?this.error(\"'\"+this.identifier+\"' has already been declared\"):a.importedSymbols.push(this.identifier);\nreturn b.__super__.compileNode.call(this,a)};return b}(l);f.ImportDefaultSpecifier=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(Z);f.ImportNamespaceSpecifier=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);return b}(Z);f.ExportSpecifier=function(a){function b(a,d){b.__super__.constructor.call(this,a,d,\"export\")}v(b,a);return b}(l);f.Assign=y=function(a){function b(a,b,c,e){this.variable=a;this.value=b;this.context=\nc;null==e&&(e={});this.param=e.param;this.subpattern=e.subpattern;this.operatorToken=e.operatorToken;this.moduleDeclaration=e.moduleDeclaration}v(b,a);b.prototype.children=[\"variable\",\"value\"];b.prototype.isStatement=function(a){return(null!=a?a.level:void 0)===N&&null!=this.context&&(this.moduleDeclaration||0<=S.call(this.context,\"?\"))};b.prototype.checkAssignability=function(a,b){if(Object.prototype.hasOwnProperty.call(a.scope.positions,b.value)&&\"import\"===a.scope.variables[a.scope.positions[b.value]].type)return b.error(\"'\"+\nb.value+\"' is read-only\")};b.prototype.assigns=function(a){return this[\"object\"===this.context?\"value\":\"variable\"].assigns(a)};b.prototype.unfoldSoak=function(a){return Ba(a,this,\"variable\")};b.prototype.compileNode=function(a){var b,c,e,p,f,g,k;if(c=this.variable instanceof C){if(this.variable.isArray()||this.variable.isObject())return this.compilePatternMatch(a);if(this.variable.isSplice())return this.compileSplice(a);if(\"||\\x3d\"===(p=this.context)||\"\\x26\\x26\\x3d\"===p||\"?\\x3d\"===p)return this.compileConditional(a);\nif(\"**\\x3d\"===(f=this.context)||\"//\\x3d\"===f||\"%%\\x3d\"===f)return this.compileSpecialMath(a)}this.value instanceof h&&(this.value[\"static\"]?(this.value.klass=this.variable.base,this.value.name=this.variable.properties[0],this.value.variable=this.variable):2<=(null!=(g=this.variable.properties)?g.length:void 0)&&(g=this.variable.properties,p=3<=g.length?M.call(g,0,e=g.length-2):(e=0,[]),f=g[e++],e=g[e++],\"prototype\"===(null!=(k=f.name)?k.value:void 0)&&(this.value.klass=new C(this.variable.base,p),\nthis.value.name=e,this.value.variable=this.variable)));this.context||(k=this.variable.unwrapAll(),k.isAssignable()||this.variable.error(\"'\"+this.variable.compile(a)+\"' can't be assigned\"),\"function\"===typeof k.hasProperties&&k.hasProperties()||(this.moduleDeclaration?(this.checkAssignability(a,k),a.scope.add(k.value,this.moduleDeclaration)):this.param?a.scope.add(k.value,\"var\"):(this.checkAssignability(a,k),a.scope.find(k.value))));k=this.value.compileToFragments(a,ta);c&&this.variable.base instanceof\nm&&(this.variable.front=!0);c=this.variable.compileToFragments(a,ta);if(\"object\"===this.context){if(b=da(c),0<=S.call(ma,b))c.unshift(this.makeCode('\"')),c.push(this.makeCode('\"'));return c.concat(this.makeCode(\": \"),k)}b=c.concat(this.makeCode(\" \"+(this.context||\"\\x3d\")+\" \"),k);return a.level<=ta?b:this.wrapInBraces(b)};b.prototype.compilePatternMatch=function(a){var d,c,e;var p=a.level===N;var f=this.value;var g=this.variable.base.objects;if(!(e=g.length)){var t=f.compileToFragments(a);return a.level>=\nFa?this.wrapInBraces(t):t}var h=g[0];1===e&&h instanceof H&&h.error(\"Destructuring assignment has no target\");var m=this.variable.isObject();if(p&&1===e&&!(h instanceof U)){var l=null;if(h instanceof b&&\"object\"===h.context){t=h;var n=t.variable;var q=n.base;h=t.value;h instanceof b&&(l=h.value,h=h.variable)}else h instanceof b&&(l=h.value,h=h.variable),q=m?h[\"this\"]?h.properties[0].name:new L(h.unwrap().value):new w(0);var r=q.unwrap()instanceof L;f=new C(f);f.properties.push(new (r?qa:R)(q));(c=\nza(h.unwrap().value))&&h.error(c);l&&(f=new k(\"?\",f,l));return(new b(h,f,null,{param:this.param})).compileToFragments(a,N)}var v=f.compileToFragments(a,ta);var y=da(v);t=[];n=!1;f.unwrap()instanceof x&&!this.variable.assigns(y)||(t.push([this.makeCode((l=a.scope.freeVariable(\"ref\"))+\" \\x3d \")].concat(M.call(v))),v=[this.makeCode(l)],y=l);l=f=0;for(d=g.length;f<d;l=++f){h=g[l];q=l;if(!n&&h instanceof U){c=h.name.unwrap().value;h=h.unwrap();q=e+\" \\x3c\\x3d \"+y+\".length ? \"+Ia(\"slice\",a)+\".call(\"+y+\", \"+\nl;if(r=e-l-1){var u=a.scope.freeVariable(\"i\",{single:!0});q+=\", \"+u+\" \\x3d \"+y+\".length - \"+r+\") : (\"+u+\" \\x3d \"+l+\", [])\"}else q+=\") : []\";q=new z(q);n=u+\"++\"}else if(!n&&h instanceof H){if(r=e-l-1)1===r?n=y+\".length - 1\":(u=a.scope.freeVariable(\"i\",{single:!0}),q=new z(u+\" \\x3d \"+y+\".length - \"+r),n=u+\"++\",t.push(q.compileToFragments(a,ta)));continue}else(h instanceof U||h instanceof H)&&h.error(\"multiple splats/expansions are disallowed in an assignment\"),l=null,h instanceof b&&\"object\"===h.context?\n(q=h.variable,q=q.base,h=h.value,h instanceof b&&(l=h.value,h=h.variable)):(h instanceof b&&(l=h.value,h=h.variable),q=m?h[\"this\"]?h.properties[0].name:new L(h.unwrap().value):new z(n||q)),c=h.unwrap().value,r=q.unwrap()instanceof L,q=new C(new z(y),[new (r?qa:R)(q)]),l&&(q=new k(\"?\",q,l));null!=c&&(c=za(c))&&h.error(c);t.push((new b(h,q,null,{param:this.param,subpattern:!0})).compileToFragments(a,ta))}p||this.subpattern||t.push(v);t=this.joinFragmentArrays(t,\", \");return a.level<ta?t:this.wrapInBraces(t)};\nb.prototype.compileConditional=function(a){var d=this.variable.cacheReference(a);var c=d[0];d=d[1];c.properties.length||!(c.base instanceof z)||c.base instanceof E||a.scope.check(c.base.value)||this.variable.error('the variable \"'+c.base.value+\"\\\" can't be assigned with \"+this.context+\" because it has not been declared before\");if(0<=S.call(this.context,\"?\"))return a.isExistentialEquals=!0,(new J(new B(c),d,{type:\"if\"})).addElse(new b(d,this.value,\"\\x3d\")).compileToFragments(a);c=(new k(this.context.slice(0,\n-1),c,new b(d,this.value,\"\\x3d\"))).compileToFragments(a);return a.level<=ta?c:this.wrapInBraces(c)};b.prototype.compileSpecialMath=function(a){var d=this.variable.cacheReference(a);var c=d[0];d=d[1];return(new b(c,new k(this.context.slice(0,-1),d,this.value))).compileToFragments(a)};b.prototype.compileSplice=function(a){var b=this.variable.properties.pop().range;var c=b.from;var e=b.to;var p=b.exclusive;var f=this.variable.compile(a);if(c){var g=this.cacheToCodeFragments(c.cache(a,Fa));b=g[0];g=g[1]}else b=\ng=\"0\";e?null!=c&&c.isNumber()&&e.isNumber()?(e=e.compile(a)-g,p||(e+=1)):(e=e.compile(a,Ga)+\" - \"+g,p||(e+=\" + 1\")):e=\"9e9\";p=this.value.cache(a,ta);c=p[0];p=p[1];e=[].concat(this.makeCode(\"[].splice.apply(\"+f+\", [\"+b+\", \"+e+\"].concat(\"),c,this.makeCode(\")), \"),p);return a.level>N?this.wrapInBraces(e):e};return b}(sa);f.Code=h=function(b){function c(b,d,c){this.params=b||[];this.body=d||new a;this.bound=\"boundfunc\"===c;this.isGenerator=!!this.body.contains(function(a){return a instanceof k&&a.isYield()||\na instanceof T})}v(c,b);c.prototype.children=[\"params\",\"body\"];c.prototype.isStatement=function(){return!!this.ctor};c.prototype.jumps=ka;c.prototype.makeScope=function(a){return new xa(a,this.body,this)};c.prototype.compileNode=function(b){var d,f,e,g;this.bound&&null!=(d=b.scope.method)&&d.bound&&(this.context=b.scope.method.context);if(this.bound&&!this.context)return this.context=\"_this\",d=new c([new K(new x(this.context))],new a([this])),d=new ya(d,[new E]),d.updateLocationDataIfMissing(this.locationData),\nd.compileNode(b);b.scope=la(b,\"classScope\")||this.makeScope(b.scope);b.scope.shared=la(b,\"sharedScope\");b.indent+=Ca;delete b.bare;delete b.isExistentialEquals;d=[];var p=[];var h=this.params;var t=0;for(e=h.length;t<e;t++){var l=h[t];l instanceof H||b.scope.parameter(l.asReference(b))}h=this.params;t=0;for(e=h.length;t<e;t++)if(l=h[t],l.splat||l instanceof H){t=this.params;var m=0;for(l=t.length;m<l;m++){var n=t[m];n instanceof H||!n.name.value||b.scope.add(n.name.value,\"var\",!0)}m=new y(new C(new q(function(){var a;\nvar d=this.params;var e=[];var c=0;for(a=d.length;c<a;c++)n=d[c],e.push(n.asReference(b));return e}.call(this))),new C(new x(\"arguments\")));break}var w=this.params;h=0;for(t=w.length;h<t;h++){l=w[h];if(l.isComplex()){var r=g=l.asReference(b);l.value&&(r=new k(\"?\",g,l.value));p.push(new y(new C(l.name),r,\"\\x3d\",{param:!0}))}else g=l,l.value&&(e=new z(g.name.value+\" \\x3d\\x3d null\"),r=new y(new C(l.name),l.value,\"\\x3d\"),p.push(new J(e,r)));m||d.push(g)}l=this.body.isEmpty();m&&p.unshift(m);p.length&&\n(f=this.body.expressions).unshift.apply(f,p);f=m=0;for(p=d.length;m<p;f=++m)n=d[f],d[f]=n.compileToFragments(b),b.scope.parameter(da(d[f]));var v=[];this.eachParamName(function(a,b){0<=S.call(v,a)&&b.error(\"multiple parameters named \"+a);return v.push(a)});l||this.noReturn||this.body.makeReturn();f=\"function\";this.isGenerator&&(f+=\"*\");this.ctor&&(f+=\" \"+this.name);p=[this.makeCode(f+\"(\")];f=l=0;for(m=d.length;l<m;f=++l)n=d[f],f&&p.push(this.makeCode(\", \")),p.push.apply(p,n);p.push(this.makeCode(\") {\"));\nthis.body.isEmpty()||(p=p.concat(this.makeCode(\"\\n\"),this.body.compileWithDeclarations(b),this.makeCode(\"\\n\"+this.tab)));p.push(this.makeCode(\"}\"));return this.ctor?[this.makeCode(this.tab)].concat(M.call(p)):this.front||b.level>=Ga?this.wrapInBraces(p):p};c.prototype.eachParamName=function(a){var b;var c=this.params;var e=[];var f=0;for(b=c.length;f<b;f++){var p=c[f];e.push(p.eachName(a))}return e};c.prototype.traverseChildren=function(a,b){if(a)return c.__super__.traverseChildren.call(this,a,b)};\nreturn c}(sa);f.Param=K=function(a){function b(a,b,c){this.name=a;this.value=b;this.splat=c;(a=za(this.name.unwrapAll().value))&&this.name.error(a);this.name instanceof m&&this.name.generated&&(a=this.name.objects[0].operatorToken,a.error(\"unexpected \"+a.value))}v(b,a);b.prototype.children=[\"name\",\"value\"];b.prototype.compileToFragments=function(a){return this.name.compileToFragments(a,ta)};b.prototype.asReference=function(a){if(this.reference)return this.reference;var b=this.name;b[\"this\"]?(b=b.properties[0].name.value,\n0<=S.call(ma,b)&&(b=\"_\"+b),b=new x(a.scope.freeVariable(b))):b.isComplex()&&(b=new x(a.scope.freeVariable(\"arg\")));b=new C(b);this.splat&&(b=new U(b));b.updateLocationDataIfMissing(this.locationData);return this.reference=b};b.prototype.isComplex=function(){return this.name.isComplex()};b.prototype.eachName=function(a,b){var d,e;null==b&&(b=this.name);var c=function(b){return a(\"@\"+b.properties[0].name.value,b)};if(b instanceof z)return a(b.value,b);if(b instanceof C)return c(b);b=null!=(d=b.objects)?\nd:[];d=0;for(e=b.length;d<e;d++){var f=b[d];f instanceof y&&null==f.context&&(f=f.variable);f instanceof y?(f.value instanceof y&&(f=f.value),this.eachName(a,f.value.unwrap())):f instanceof U?(f=f.name.unwrap(),a(f.value,f)):f instanceof C?f.isArray()||f.isObject()?this.eachName(a,f.base):f[\"this\"]?c(f):a(f.base.value,f.base):f instanceof H||f.error(\"illegal parameter \"+f.compile())}};return b}(sa);f.Splat=U=function(a){function b(a){this.name=a.compile?a:new z(a)}v(b,a);b.prototype.children=[\"name\"];\nb.prototype.isAssignable=ha;b.prototype.assigns=function(a){return this.name.assigns(a)};b.prototype.compileToFragments=function(a){return this.name.compileToFragments(a)};b.prototype.unwrap=function(){return this.name};b.compileSplattedArray=function(a,d,c){var e,f,g,p;for(f=-1;(e=d[++f])&&!(e instanceof b););if(f>=d.length)return[];if(1===d.length)return e=d[0],d=e.compileToFragments(a,ta),c?d:[].concat(e.makeCode(Ia(\"slice\",a)+\".call(\"),d,e.makeCode(\")\"));c=d.slice(f);var h=g=0;for(p=c.length;g<\np;h=++g){e=c[h];var k=e.compileToFragments(a,ta);c[h]=e instanceof b?[].concat(e.makeCode(Ia(\"slice\",a)+\".call(\"),k,e.makeCode(\")\")):[].concat(e.makeCode(\"[\"),k,e.makeCode(\"]\"))}if(0===f)return e=d[0],a=e.joinFragmentArrays(c.slice(1),\", \"),c[0].concat(e.makeCode(\".concat(\"),a,e.makeCode(\")\"));g=d.slice(0,f);p=[];k=0;for(h=g.length;k<h;k++)e=g[k],p.push(e.compileToFragments(a,ta));e=d[0].joinFragmentArrays(p,\", \");a=d[f].joinFragmentArrays(c,\", \");c=d[d.length-1];return[].concat(d[0].makeCode(\"[\"),\ne,d[f].makeCode(\"].concat(\"),a,c.makeCode(\")\"))};return b}(sa);f.Expansion=H=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.isComplex=ka;b.prototype.compileNode=function(a){return this.error(\"Expansion must be used inside a destructuring assignment or parameter list\")};b.prototype.asReference=function(a){return this};b.prototype.eachName=function(a){};return b}(sa);f.While=Z=function(b){function c(a,b){this.condition=null!=b&&b.invert?a.invert():a;\nthis.guard=null!=b?b.guard:void 0}v(c,b);c.prototype.children=[\"condition\",\"guard\",\"body\"];c.prototype.isStatement=ha;c.prototype.makeReturn=function(a){if(a)return c.__super__.makeReturn.apply(this,arguments);this.returns=!this.jumps({loop:!0});return this};c.prototype.addBody=function(a){this.body=a;return this};c.prototype.jumps=function(){var a;var b=this.body.expressions;if(!b.length)return!1;var c=0;for(a=b.length;c<a;c++){var e=b[c];if(e=e.jumps({loop:!0}))return e}return!1};c.prototype.compileNode=\nfunction(b){var d;b.indent+=Ca;var c=\"\";var e=this.body;e.isEmpty()?e=this.makeCode(\"\"):(this.returns&&(e.makeReturn(d=b.scope.freeVariable(\"results\")),c=\"\"+this.tab+d+\" \\x3d [];\\n\"),this.guard&&(1<e.expressions.length?e.expressions.unshift(new J((new P(this.guard)).invert(),new W(\"continue\"))):this.guard&&(e=a.wrap([new J(this.guard,e)]))),e=[].concat(this.makeCode(\"\\n\"),e.compileToFragments(b,N),this.makeCode(\"\\n\"+this.tab)));b=[].concat(this.makeCode(c+this.tab+\"while (\"),this.condition.compileToFragments(b,\nKa),this.makeCode(\") {\"),e,this.makeCode(\"}\"));this.returns&&b.push(this.makeCode(\"\\n\"+this.tab+\"return \"+d+\";\"));return b};return c}(sa);f.Op=k=function(a){function b(a,b,d,f){if(\"in\"===a)return new O(b,d);if(\"do\"===a)return this.generateDo(b);if(\"new\"===a){if(b instanceof ya&&!b[\"do\"]&&!b.isNew)return b.newInstance();if(b instanceof h&&b.bound||b[\"do\"])b=new P(b)}this.operator=c[a]||a;this.first=b;this.second=d;this.flip=!!f;return this}v(b,a);var c={\"\\x3d\\x3d\":\"\\x3d\\x3d\\x3d\",\"!\\x3d\":\"!\\x3d\\x3d\",\nof:\"in\",yieldfrom:\"yield*\"};var d={\"!\\x3d\\x3d\":\"\\x3d\\x3d\\x3d\",\"\\x3d\\x3d\\x3d\":\"!\\x3d\\x3d\"};b.prototype.children=[\"first\",\"second\"];b.prototype.isNumber=function(){var a;return this.isUnary()&&(\"+\"===(a=this.operator)||\"-\"===a)&&this.first instanceof C&&this.first.isNumber()};b.prototype.isYield=function(){var a;return\"yield\"===(a=this.operator)||\"yield*\"===a};b.prototype.isUnary=function(){return!this.second};b.prototype.isComplex=function(){return!this.isNumber()};b.prototype.isChainable=function(){var a;\nreturn\"\\x3c\"===(a=this.operator)||\"\\x3e\"===a||\"\\x3e\\x3d\"===a||\"\\x3c\\x3d\"===a||\"\\x3d\\x3d\\x3d\"===a||\"!\\x3d\\x3d\"===a};b.prototype.invert=function(){var a,e;if(this.isChainable()&&this.first.isChainable()){var c=!0;for(a=this;a&&a.operator;)c&&(c=a.operator in d),a=a.first;if(!c)return(new P(this)).invert();for(a=this;a&&a.operator;)a.invert=!a.invert,a.operator=d[a.operator],a=a.first;return this}return(a=d[this.operator])?(this.operator=a,this.first.unwrap()instanceof b&&this.first.invert(),this):this.second?\n(new P(this)).invert():\"!\"===this.operator&&(c=this.first.unwrap())instanceof b&&(\"!\"===(e=c.operator)||\"in\"===e||\"instanceof\"===e)?c:new b(\"!\",this)};b.prototype.unfoldSoak=function(a){var b;return(\"++\"===(b=this.operator)||\"--\"===b||\"delete\"===b)&&Ba(a,this,\"first\")};b.prototype.generateDo=function(a){var b,d;var c=[];var f=(a instanceof y&&(b=a.value.unwrap())instanceof h?b:a).params||[];b=0;for(d=f.length;b<d;b++){var g=f[b];g.value?(c.push(g.value),delete g.value):c.push(g)}a=new ya(a,c);a[\"do\"]=\n!0;return a};b.prototype.compileNode=function(a){var b;var d=this.isChainable()&&this.first.isChainable();d||(this.first.front=this.front);\"delete\"===this.operator&&a.scope.check(this.first.unwrapAll().value)&&this.error(\"delete operand may not be argument or var\");(\"--\"===(b=this.operator)||\"++\"===b)&&(b=za(this.first.unwrapAll().value))&&this.first.error(b);if(this.isYield())return this.compileYield(a);if(this.isUnary())return this.compileUnary(a);if(d)return this.compileChain(a);switch(this.operator){case \"?\":return this.compileExistence(a);\ncase \"**\":return this.compilePower(a);case \"//\":return this.compileFloorDivision(a);case \"%%\":return this.compileModulo(a);default:return d=this.first.compileToFragments(a,Fa),b=this.second.compileToFragments(a,Fa),d=[].concat(d,this.makeCode(\" \"+this.operator+\" \"),b),a.level<=Fa?d:this.wrapInBraces(d)}};b.prototype.compileChain=function(a){var b=this.first.second.cache(a);this.first.second=b[0];b=b[1];a=this.first.compileToFragments(a,Fa).concat(this.makeCode(\" \"+(this.invert?\"\\x26\\x26\":\"||\")+\" \"),\nb.compileToFragments(a),this.makeCode(\" \"+this.operator+\" \"),this.second.compileToFragments(a,Fa));return this.wrapInBraces(a)};b.prototype.compileExistence=function(a){if(this.first.isComplex()){var b=new x(a.scope.freeVariable(\"ref\"));var d=new P(new y(b,this.first))}else b=d=this.first;return(new J(new B(d),b,{type:\"if\"})).addElse(this.second).compileToFragments(a)};b.prototype.compileUnary=function(a){var d=[];var c=this.operator;d.push([this.makeCode(c)]);if(\"!\"===c&&this.first instanceof B)return this.first.negated=\n!this.first.negated,this.first.compileToFragments(a);if(a.level>=Ga)return(new P(this)).compileToFragments(a);var f=\"+\"===c||\"-\"===c;(\"new\"===c||\"typeof\"===c||\"delete\"===c||f&&this.first instanceof b&&this.first.operator===c)&&d.push([this.makeCode(\" \")]);if(f&&this.first instanceof b||\"new\"===c&&this.first.isStatement(a))this.first=new P(this.first);d.push(this.first.compileToFragments(a,Fa));this.flip&&d.reverse();return this.joinFragmentArrays(d,\"\")};b.prototype.compileYield=function(a){var b;\nvar d=[];var c=this.operator;null==a.scope.parent&&this.error(\"yield can only occur inside functions\");0<=S.call(Object.keys(this.first),\"expression\")&&!(this.first instanceof ba)?null!=this.first.expression&&d.push(this.first.expression.compileToFragments(a,Fa)):(a.level>=Ka&&d.push([this.makeCode(\"(\")]),d.push([this.makeCode(c)]),\"\"!==(null!=(b=this.first.base)?b.value:void 0)&&d.push([this.makeCode(\" \")]),d.push(this.first.compileToFragments(a,Fa)),a.level>=Ka&&d.push([this.makeCode(\")\")]));return this.joinFragmentArrays(d,\n\"\")};b.prototype.compilePower=function(a){var b=new C(new x(\"Math\"),[new qa(new L(\"pow\"))]);return(new ya(b,[this.first,this.second])).compileToFragments(a)};b.prototype.compileFloorDivision=function(a){var d=new C(new x(\"Math\"),[new qa(new L(\"floor\"))]);var c=this.second.isComplex()?new P(this.second):this.second;c=new b(\"/\",this.first,c);return(new ya(d,[c])).compileToFragments(a)};b.prototype.compileModulo=function(a){var b=new C(new z(Ia(\"modulo\",a)));return(new ya(b,[this.first,this.second])).compileToFragments(a)};\nb.prototype.toString=function(a){return b.__super__.toString.call(this,a,this.constructor.name+\" \"+this.operator)};return b}(sa);f.In=O=function(a){function b(a,b){this.object=a;this.array=b}v(b,a);b.prototype.children=[\"object\",\"array\"];b.prototype.invert=ra;b.prototype.compileNode=function(a){var b;if(this.array instanceof C&&this.array.isArray()&&this.array.base.objects.length){var c=this.array.base.objects;var e=0;for(b=c.length;e<b;e++){var f=c[e];if(f instanceof U){var g=!0;break}}if(!g)return this.compileOrTest(a)}return this.compileLoopTest(a)};\nb.prototype.compileOrTest=function(a){var b,c;var e=this.object.cache(a,Fa);var f=e[0];var g=e[1];var h=this.negated?[\" !\\x3d\\x3d \",\" \\x26\\x26 \"]:[\" \\x3d\\x3d\\x3d \",\" || \"];e=h[0];h=h[1];var p=[];var k=this.array.base.objects;var l=b=0;for(c=k.length;b<c;l=++b){var m=k[l];l&&p.push(this.makeCode(h));p=p.concat(l?g:f,this.makeCode(e),m.compileToFragments(a,Ga))}return a.level<Fa?p:this.wrapInBraces(p)};b.prototype.compileLoopTest=function(a){var b=this.object.cache(a,ta);var c=b[0];var e=b[1];b=[].concat(this.makeCode(Ia(\"indexOf\",\na)+\".call(\"),this.array.compileToFragments(a,ta),this.makeCode(\", \"),e,this.makeCode(\") \"+(this.negated?\"\\x3c 0\":\"\\x3e\\x3d 0\")));if(da(c)===da(e))return b;b=c.concat(this.makeCode(\", \"),b);return a.level<ta?b:this.wrapInBraces(b)};b.prototype.toString=function(a){return b.__super__.toString.call(this,a,this.constructor.name+(this.negated?\"!\":\"\"))};return b}(sa);f.Try=function(a){function b(a,b,c,e){this.attempt=a;this.errorVariable=b;this.recovery=c;this.ensure=e}v(b,a);b.prototype.children=[\"attempt\",\n\"recovery\",\"ensure\"];b.prototype.isStatement=ha;b.prototype.jumps=function(a){var b;return this.attempt.jumps(a)||(null!=(b=this.recovery)?b.jumps(a):void 0)};b.prototype.makeReturn=function(a){this.attempt&&(this.attempt=this.attempt.makeReturn(a));this.recovery&&(this.recovery=this.recovery.makeReturn(a));return this};b.prototype.compileNode=function(a){var b,c,e;a.indent+=Ca;var f=this.attempt.compileToFragments(a,N);var g=this.recovery?(b=a.scope.freeVariable(\"error\",{reserve:!1}),e=new x(b),\nthis.errorVariable?(c=za(this.errorVariable.unwrapAll().value),c?this.errorVariable.error(c):void 0,this.recovery.unshift(new y(this.errorVariable,e))):void 0,[].concat(this.makeCode(\" catch (\"),e.compileToFragments(a),this.makeCode(\") {\\n\"),this.recovery.compileToFragments(a,N),this.makeCode(\"\\n\"+this.tab+\"}\"))):this.ensure||this.recovery?[]:(b=a.scope.freeVariable(\"error\",{reserve:!1}),[this.makeCode(\" catch (\"+b+\") {}\")]);a=this.ensure?[].concat(this.makeCode(\" finally {\\n\"),this.ensure.compileToFragments(a,\nN),this.makeCode(\"\\n\"+this.tab+\"}\")):[];return[].concat(this.makeCode(this.tab+\"try {\\n\"),f,this.makeCode(\"\\n\"+this.tab+\"}\"),g,a)};return b}(sa);f.Throw=ba=function(a){function b(a){this.expression=a}v(b,a);b.prototype.children=[\"expression\"];b.prototype.isStatement=ha;b.prototype.jumps=ka;b.prototype.makeReturn=na;b.prototype.compileNode=function(a){return[].concat(this.makeCode(this.tab+\"throw \"),this.expression.compileToFragments(a),this.makeCode(\";\"))};return b}(sa);f.Existence=B=function(a){function b(a){this.expression=\na}v(b,a);b.prototype.children=[\"expression\"];b.prototype.invert=ra;b.prototype.compileNode=function(a){this.expression.front=this.front;var b=this.expression.compile(a,Fa);if(this.expression.unwrap()instanceof x&&!a.scope.check(b)){var c=this.negated?[\"\\x3d\\x3d\\x3d\",\"||\"]:[\"!\\x3d\\x3d\",\"\\x26\\x26\"];var e=c[0];c=c[1];b=\"typeof \"+b+\" \"+e+' \"undefined\" '+c+\" \"+b+\" \"+e+\" null\"}else b=b+\" \"+(this.negated?\"\\x3d\\x3d\":\"!\\x3d\")+\" null\";return[this.makeCode(a.level<=Na?b:\"(\"+b+\")\")]};return b}(sa);f.Parens=P=\nfunction(a){function b(a){this.body=a}v(b,a);b.prototype.children=[\"body\"];b.prototype.unwrap=function(){return this.body};b.prototype.isComplex=function(){return this.body.isComplex()};b.prototype.compileNode=function(a){var b=this.body.unwrap();if(b instanceof C&&b.isAtomic())return b.front=this.front,b.compileToFragments(a);var c=b.compileToFragments(a,Ka);return a.level<Fa&&(b instanceof k||b instanceof ya||b instanceof Q&&b.returns)&&(a.level<Na||3>=c.length)?c:this.wrapInBraces(c)};return b}(sa);\nf.StringWithInterpolations=A=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}v(b,a);b.prototype.compileNode=function(a){var d;if(!a.inTaggedTemplateCall)return b.__super__.compileNode.apply(this,arguments);var c=this.body.unwrap();var e=[];c.traverseChildren(!1,function(a){if(a instanceof D)e.push(a);else if(a instanceof P)return e.push(a),!1;return!0});c=[];c.push(this.makeCode(\"`\"));var f=0;for(d=e.length;f<d;f++){var g=e[f];g instanceof D?(g=g.value.slice(1,-1),g=\ng.replace(/(\\\\*)(`|\\$\\{)/g,function(a,b,d){return 0===b.length%2?b+\"\\\\\"+d:a}),c.push(this.makeCode(g))):(c.push(this.makeCode(\"${\")),c.push.apply(c,g.compileToFragments(a,Ka)),c.push(this.makeCode(\"}\")))}c.push(this.makeCode(\"`\"));return c};return b}(P);f.For=Q=function(b){function c(b,d){this.source=d.source;this.guard=d.guard;this.step=d.step;this.name=d.name;this.index=d.index;this.body=a.wrap([b]);this.own=!!d.own;this.object=!!d.object;(this.from=!!d.from)&&this.index&&this.index.error(\"cannot use index with for-from\");\nthis.own&&!this.object&&d.ownTag.error(\"cannot use own with for-\"+(this.from?\"from\":\"in\"));this.object&&(b=[this.index,this.name],this.name=b[0],this.index=b[1]);this.index instanceof C&&!this.index.isAssignable()&&this.index.error(\"index cannot be a pattern matching expression\");this.range=this.source instanceof C&&this.source.base instanceof V&&!this.source.properties.length&&!this.from;this.pattern=this.name instanceof C;this.range&&this.index&&this.index.error(\"indexes do not apply to range loops\");\nthis.range&&this.pattern&&this.name.error(\"cannot pattern match over range loops\");this.returns=!1}v(c,b);c.prototype.children=[\"body\",\"source\",\"guard\",\"step\"];c.prototype.compileNode=function(b){var d,c,e,f,g,h,k;var l=a.wrap([this.body]);var p=l.expressions;p=p[p.length-1];(null!=p?p.jumps():void 0)instanceof G&&(this.returns=!1);var m=this.range?this.source.base:this.source;var n=b.scope;this.pattern||(e=this.name&&this.name.compile(b,ta));p=this.index&&this.index.compile(b,ta);e&&!this.pattern&&\nn.find(e);!p||this.index instanceof C||n.find(p);this.returns&&(c=n.freeVariable(\"results\"));this.from?this.pattern&&(f=n.freeVariable(\"x\",{single:!0})):f=this.object&&p||n.freeVariable(\"i\",{single:!0});var q=(this.range||this.from)&&e||p||f;var t=q!==f?q+\" \\x3d \":\"\";if(this.step&&!this.range){p=this.cacheToCodeFragments(this.step.cache(b,ta,Ya));var w=p[0];var r=p[1];this.step.isNumber()&&(h=Number(r))}this.pattern&&(e=f);var v=p=k=\"\";var u=this.tab+Ca;if(this.range)var K=m.compileToFragments(ja(b,\n{index:f,name:e,step:this.step,isComplex:Ya}));else{var A=this.source.compile(b,ta);!e&&!this.own||this.source.unwrap()instanceof x||(v+=\"\"+this.tab+(m=n.freeVariable(\"ref\"))+\" \\x3d \"+A+\";\\n\",A=m);!e||this.pattern||this.from||(g=e+\" \\x3d \"+A+\"[\"+q+\"]\");this.object||this.from||(w!==r&&(v+=\"\"+this.tab+w+\";\\n\"),e=0>h,this.step&&null!=h&&e||(d=n.freeVariable(\"len\")),K=\"\"+t+f+\" \\x3d 0, \"+d+\" \\x3d \"+A+\".length\",w=\"\"+t+f+\" \\x3d \"+A+\".length - 1\",d=f+\" \\x3c \"+d,n=f+\" \\x3e\\x3d 0\",this.step?(null!=h?e&&(d=\nn,K=w):(d=r+\" \\x3e 0 ? \"+d+\" : \"+n,K=\"(\"+r+\" \\x3e 0 ? (\"+K+\") : \"+w+\")\"),f=f+\" +\\x3d \"+r):f=\"\"+(q!==f?\"++\"+f:f+\"++\"),K=[this.makeCode(K+\"; \"+d+\"; \"+t+f)])}if(this.returns){var B=\"\"+this.tab+c+\" \\x3d [];\\n\";var V=\"\\n\"+this.tab+\"return \"+c+\";\";l.makeReturn(c)}this.guard&&(1<l.expressions.length?l.expressions.unshift(new J((new P(this.guard)).invert(),new W(\"continue\"))):this.guard&&(l=a.wrap([new J(this.guard,l)])));this.pattern&&l.expressions.unshift(new y(this.name,this.from?new x(q):new z(A+\"[\"+\nq+\"]\")));c=[].concat(this.makeCode(v),this.pluckDirectCall(b,l));g&&(k=\"\\n\"+u+g+\";\");this.object?(K=[this.makeCode(q+\" in \"+A)],this.own&&(p=\"\\n\"+u+\"if (!\"+Ia(\"hasProp\",b)+\".call(\"+A+\", \"+q+\")) continue;\")):this.from&&(K=[this.makeCode(q+\" of \"+A)]);(b=l.compileToFragments(ja(b,{indent:u}),N))&&0<b.length&&(b=[].concat(this.makeCode(\"\\n\"),b,this.makeCode(\"\\n\")));return[].concat(c,this.makeCode(\"\"+(B||\"\")+this.tab+\"for (\"),K,this.makeCode(\") {\"+p+k),b,this.makeCode(this.tab+\"}\"+(V||\"\")))};c.prototype.pluckDirectCall=\nfunction(a,b){var d,c,f,g,k,l,p;var m=[];var n=b.expressions;var q=d=0;for(c=n.length;d<c;q=++d){var w=n[q];w=w.unwrapAll();if(w instanceof ya){var t=null!=(f=w.variable)?f.unwrapAll():void 0;if(t instanceof h||t instanceof C&&(null!=(g=t.base)?g.unwrapAll():void 0)instanceof h&&1===t.properties.length&&(\"call\"===(k=null!=(l=t.properties[0].name)?l.value:void 0)||\"apply\"===k)){var r=(null!=(p=t.base)?p.unwrapAll():void 0)||t;var v=new x(a.scope.freeVariable(\"fn\"));var u=new C(v);t.base&&(u=[u,t],\nt.base=u[0],u=u[1]);b.expressions[q]=new ya(u,w.args);m=m.concat(this.makeCode(this.tab),(new y(v,r)).compileToFragments(a,N),this.makeCode(\";\\n\"))}}}return m};return c}(Z);f.Switch=function(b){function c(a,b,c){this.subject=a;this.cases=b;this.otherwise=c}v(c,b);c.prototype.children=[\"subject\",\"cases\",\"otherwise\"];c.prototype.isStatement=ha;c.prototype.jumps=function(a){var b,c;null==a&&(a={block:!0});var e=this.cases;var f=0;for(b=e.length;f<b;f++){var g=e[f];g=g[1];if(g=g.jumps(a))return g}return null!=\n(c=this.otherwise)?c.jumps(a):void 0};c.prototype.makeReturn=function(b){var c,f;var e=this.cases;var g=0;for(c=e.length;g<c;g++){var h=e[g];h[1].makeReturn(b)}b&&(this.otherwise||(this.otherwise=new a([new z(\"void 0\")])));null!=(f=this.otherwise)&&f.makeReturn(b);return this};c.prototype.compileNode=function(a){var b,c,e,f;var g=a.indent+Ca;var h=a.indent=g+Ca;var k=[].concat(this.makeCode(this.tab+\"switch (\"),this.subject?this.subject.compileToFragments(a,Ka):this.makeCode(\"false\"),this.makeCode(\") {\\n\"));\nvar l=this.cases;var m=c=0;for(e=l.length;c<e;m=++c){var p=l[m];var n=p[0];p=p[1];var q=ia([n]);n=0;for(f=q.length;n<f;n++){var w=q[n];this.subject||(w=w.invert());k=k.concat(this.makeCode(g+\"case \"),w.compileToFragments(a,Ka),this.makeCode(\":\\n\"))}0<(b=p.compileToFragments(a,N)).length&&(k=k.concat(b,this.makeCode(\"\\n\")));if(m===this.cases.length-1&&!this.otherwise)break;m=this.lastNonComment(p.expressions);m instanceof G||m instanceof z&&m.jumps()&&\"debugger\"!==m.value||k.push(w.makeCode(h+\"break;\\n\"))}this.otherwise&&\nthis.otherwise.expressions.length&&k.push.apply(k,[this.makeCode(g+\"default:\\n\")].concat(M.call(this.otherwise.compileToFragments(a,N)),[this.makeCode(\"\\n\")]));k.push(this.makeCode(this.tab+\"}\"));return k};return c}(sa);f.If=J=function(b){function c(a,b,c){this.body=b;null==c&&(c={});this.condition=\"unless\"===c.type?a.invert():a;this.elseBody=null;this.isChain=!1;this.soak=c.soak}v(c,b);c.prototype.children=[\"condition\",\"body\",\"elseBody\"];c.prototype.bodyNode=function(){var a;return null!=(a=this.body)?\na.unwrap():void 0};c.prototype.elseBodyNode=function(){var a;return null!=(a=this.elseBody)?a.unwrap():void 0};c.prototype.addElse=function(a){this.isChain?this.elseBodyNode().addElse(a):(this.isChain=a instanceof c,this.elseBody=this.ensureBlock(a),this.elseBody.updateLocationDataIfMissing(a.locationData));return this};c.prototype.isStatement=function(a){var b;return(null!=a?a.level:void 0)===N||this.bodyNode().isStatement(a)||(null!=(b=this.elseBodyNode())?b.isStatement(a):void 0)};c.prototype.jumps=\nfunction(a){var b;return this.body.jumps(a)||(null!=(b=this.elseBody)?b.jumps(a):void 0)};c.prototype.compileNode=function(a){return this.isStatement(a)?this.compileStatement(a):this.compileExpression(a)};c.prototype.makeReturn=function(b){b&&(this.elseBody||(this.elseBody=new a([new z(\"void 0\")])));this.body&&(this.body=new a([this.body.makeReturn(b)]));this.elseBody&&(this.elseBody=new a([this.elseBody.makeReturn(b)]));return this};c.prototype.ensureBlock=function(b){return b instanceof a?b:new a([b])};\nc.prototype.compileStatement=function(a){var b=la(a,\"chainChild\");if(la(a,\"isExistentialEquals\"))return(new c(this.condition.invert(),this.elseBodyNode(),{type:\"if\"})).compileToFragments(a);var f=a.indent+Ca;var e=this.condition.compileToFragments(a,Ka);var g=this.ensureBlock(this.body).compileToFragments(ja(a,{indent:f}));g=[].concat(this.makeCode(\"if (\"),e,this.makeCode(\") {\\n\"),g,this.makeCode(\"\\n\"+this.tab+\"}\"));b||g.unshift(this.makeCode(this.tab));if(!this.elseBody)return g;b=g.concat(this.makeCode(\" else \"));\nthis.isChain?(a.chainChild=!0,b=b.concat(this.elseBody.unwrap().compileToFragments(a,N))):b=b.concat(this.makeCode(\"{\\n\"),this.elseBody.compileToFragments(ja(a,{indent:f}),N),this.makeCode(\"\\n\"+this.tab+\"}\"));return b};c.prototype.compileExpression=function(a){var b=this.condition.compileToFragments(a,Na);var c=this.bodyNode().compileToFragments(a,ta);var e=this.elseBodyNode()?this.elseBodyNode().compileToFragments(a,ta):[this.makeCode(\"void 0\")];e=b.concat(this.makeCode(\" ? \"),c,this.makeCode(\" : \"),\ne);return a.level>=Na?this.wrapInBraces(e):e};c.prototype.unfoldSoak=function(){return this.soak&&this};return c}(sa);var gc={extend:function(a){return\"function(child, parent) { for (var key in parent) { if (\"+Ia(\"hasProp\",a)+\".call(parent, key)) child[key] \\x3d parent[key]; } function ctor() { this.constructor \\x3d child; } ctor.prototype \\x3d parent.prototype; child.prototype \\x3d new ctor(); child.__super__ \\x3d parent.prototype; return child; }\"},bind:function(){return\"function(fn, me){ return function(){ return fn.apply(me, arguments); }; }\"},\nindexOf:function(){return\"[].indexOf || function(item) { for (var i \\x3d 0, l \\x3d this.length; i \\x3c l; i++) { if (i in this \\x26\\x26 this[i] \\x3d\\x3d\\x3d item) return i; } return -1; }\"},modulo:function(){return\"function(a, b) { return (+a % (b \\x3d +b) + b) % b; }\"},hasProp:function(){return\"{}.hasOwnProperty\"},slice:function(){return\"[].slice\"}};var N=1;var Ka=2;var ta=3;var Na=4;var Fa=5;var Ga=6;var Ca=\"  \";var Pa=/^[+-]?\\d+$/;var Ia=function(a,b){var c=b.scope.root;if(a in c.utilities)return c.utilities[a];\nvar d=c.freeVariable(a);c.assign(d,gc[a](b));return c.utilities[a]=d};var Ea=function(a,b){a=a.replace(/\\n/g,\"$\\x26\"+b);return a.replace(/\\s+$/,\"\")};var Va=function(a){return a instanceof x&&\"arguments\"===a.value};var ea=function(a){return a instanceof E||a instanceof h&&a.bound||a instanceof va};var Ya=function(a){return a.isComplex()||(\"function\"===typeof a.isAssignable?a.isAssignable():void 0)};var Ba=function(a,b,c){if(a=b[c].unfoldSoak(a))return b[c]=a.body,a.body=new C(b),a}}).call(this);return f}();\nu[\"./sourcemap\"]=function(){var f={};(function(){var u=function(){function f(f){this.line=f;this.columns=[]}f.prototype.add=function(f,a,b){var q=a[0];a=a[1];null==b&&(b={});if(!this.columns[f]||!b.noReplace)return this.columns[f]={line:this.line,column:f,sourceLine:q,sourceColumn:a}};f.prototype.sourceLocation=function(f){for(var a;!((a=this.columns[f])||0>=f);)f--;return a&&[a.sourceLine,a.sourceColumn]};return f}();f=function(){function f(){this.lines=[]}f.prototype.add=function(f,a,b){var q;null==\nb&&(b={});var g=a[0];a=a[1];return((q=this.lines)[g]||(q[g]=new u(g))).add(a,f,b)};f.prototype.sourceLocation=function(f){var a;var b=f[0];for(f=f[1];!((a=this.lines[b])||0>=b);)b--;return a&&a.sourceLocation(f)};f.prototype.generate=function(f,a){var b,q,g,h,r,n,u;null==f&&(f={});null==a&&(a=null);var y=g=q=u=0;var I=!1;var F=\"\";var Q=this.lines;var x=b=0;for(h=Q.length;b<h;x=++b)if(x=Q[x]){var J=x.columns;x=0;for(r=J.length;x<r;x++)if(n=J[x]){for(;u<n.line;)q=0,I=!1,F+=\";\",u++;I&&(F+=\",\");F+=this.encodeVlq(n.column-\nq);q=n.column;F+=this.encodeVlq(0);F+=this.encodeVlq(n.sourceLine-g);g=n.sourceLine;F+=this.encodeVlq(n.sourceColumn-y);y=n.sourceColumn;I=!0}}F={version:3,file:f.generatedFile||\"\",sourceRoot:f.sourceRoot||\"\",sources:f.sourceFiles||[\"\"],names:[],mappings:F};f.inlineMap&&(F.sourcesContent=[a]);return F};f.prototype.encodeVlq=function(f){var a;var b=\"\";for(a=(Math.abs(f)<<1)+(0>f?1:0);a||!b;)f=a&31,(a>>=5)&&(f|=32),b+=this.encodeBase64(f);return b};f.prototype.encodeBase64=function(f){var a;if(!(a=\n\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"[f]))throw Error(\"Cannot Base64 encode value: \"+f);return a};return f}()}).call(this);return f}();u[\"./coffee-script\"]=function(){var f={};(function(){var qa,q,y={}.hasOwnProperty;var a=u(\"fs\");var b=u(\"vm\");var ya=u(\"path\");var g=u(\"./lexer\").Lexer;var h=u(\"./parser\").parser;var r=u(\"./helpers\");var n=u(\"./sourcemap\");var B=u(\"../../package.json\");f.VERSION=B.version;f.FILE_EXTENSIONS=[\".coffee\",\".litcoffee\",\".coffee.md\"];f.helpers=\nr;var H=function(a){switch(!1){case \"function\"!==typeof Buffer:return(new Buffer(a)).toString(\"base64\");case \"function\"!==typeof btoa:return btoa(encodeURIComponent(a).replace(/%([0-9A-F]{2})/g,function(a,b){return String.fromCharCode(\"0x\"+b)}));default:throw Error(\"Unable to base64 encode inline sourcemap.\");}};B=function(a){return function(b,f){null==f&&(f={});try{return a.call(this,b,f)}catch(m){if(\"string\"!==typeof b)throw m;throw r.updateSyntaxError(m,b,f.filename);}}};var I={};var F={};f.compile=\nqa=B(function(a,b){var c,f,g,l;var q=r.extend;b=q({},b);var u=b.sourceMap||b.inlineMap||null==b.filename;q=b.filename||\"\\x3canonymous\\x3e\";I[q]=a;u&&(g=new n);var x=O.tokenize(a,b);var y=b;var G=[];var z=0;for(c=x.length;z<c;z++){var B=x[z];\"IDENTIFIER\"===B[0]&&G.push(B[1])}y.referencedVars=G;if(null==b.bare||!0!==b.bare)for(y=0,z=x.length;y<z;y++)if(B=x[y],\"IMPORT\"===(f=B[0])||\"EXPORT\"===f){b.bare=!0;break}z=h.parse(x).compileToFragments(b);x=0;b.header&&(x+=1);b.shiftLine&&(x+=1);B=0;f=\"\";c=0;for(G=\nz.length;c<G;c++){y=z[c];if(u){y.locationData&&!/^[;\\s]*$/.test(y.code)&&g.add([y.locationData.first_line,y.locationData.first_column],[x,B],{noReplace:!0});var J=r.count(y.code,\"\\n\");x+=J;B=J?y.code.length-(y.code.lastIndexOf(\"\\n\")+1):B+y.code.length}f+=y.code}b.header&&(B=\"Generated by CoffeeScript \"+this.VERSION,f=\"// \"+B+\"\\n\"+f);if(u){var D=g.generate(b,a);F[q]=g}b.inlineMap&&(a=H(JSON.stringify(D)),q=\"//# sourceURL\\x3d\"+(null!=(l=b.filename)?l:\"coffeescript\"),f=f+\"\\n\"+(\"//# sourceMappingURL\\x3ddata:application/json;base64,\"+\na)+\"\\n\"+q);return b.sourceMap?{js:f,sourceMap:g,v3SourceMap:JSON.stringify(D,null,2)}:f});f.tokens=B(function(a,b){return O.tokenize(a,b)});f.nodes=B(function(a,b){return\"string\"===typeof a?h.parse(O.tokenize(a,b)):h.parse(a)});f.run=function(b,c){var f;null==c&&(c={});var g=u.main;g.filename=process.argv[1]=c.filename?a.realpathSync(c.filename):\"\\x3canonymous\\x3e\";g.moduleCache&&(g.moduleCache={});var h=null!=c.filename?ya.dirname(a.realpathSync(c.filename)):a.realpathSync(\".\");g.paths=u(\"module\")._nodeModulePaths(h);\nif(!r.isCoffee(g.filename)||u.extensions)b=qa(b,c),b=null!=(f=b.js)?f:b;return g._compile(b,g.filename)};f.eval=function(a,c){var f,g,h,l,n;null==c&&(c={});if(a=a.trim()){var q=null!=(h=b.Script.createContext)?h:b.createContext;h=null!=(g=b.isContext)?g:function(a){return c.sandbox instanceof q().constructor};if(q){if(null!=c.sandbox){if(h(c.sandbox))var r=c.sandbox;else for(l in r=q(),h=c.sandbox,h)y.call(h,l)&&(g=h[l],r[l]=g);r.global=r.root=r.GLOBAL=r}else r=global;r.__filename=c.filename||\"eval\";\nr.__dirname=ya.dirname(r.__filename);if(r===global&&!r.module&&!r.require){var x=u(\"module\");r.module=f=new x(c.modulename||\"eval\");r.require=g=function(a){return x._load(a,f,!0)};f.filename=r.__filename;var B=Object.getOwnPropertyNames(u);h=0;for(n=B.length;h<n;h++){var z=B[h];\"paths\"!==z&&\"arguments\"!==z&&\"caller\"!==z&&(g[z]=u[z])}g.paths=f.paths=x._nodeModulePaths(process.cwd());g.resolve=function(a){return x._resolveFilename(a,f)}}}h={};for(l in c)y.call(c,l)&&(g=c[l],h[l]=g);h.bare=!0;a=qa(a,\nh);return r===global?b.runInThisContext(a):b.runInContext(a,r)}};f.register=function(){return u(\"./register\")};if(u.extensions){var Q=this.FILE_EXTENSIONS;var x=function(a){var b;return null!=(b=u.extensions)[a]?b[a]:b[a]=function(){throw Error(\"Use CoffeeScript.register() or require the coffee-script/register module to require \"+a+\" files.\");}};var J=0;for(q=Q.length;J<q;J++)B=Q[J],x(B)}f._compileFile=function(b,c,f){null==c&&(c=!1);null==f&&(f=!1);var g=a.readFileSync(b,\"utf8\");g=65279===g.charCodeAt(0)?\ng.substring(1):g;try{var h=qa(g,{filename:b,sourceMap:c,inlineMap:f,sourceFiles:[b],literate:r.isLiterate(b)})}catch(K){throw r.updateSyntaxError(K,g,b);}return h};var O=new g;h.lexer={lex:function(){var a;if(a=h.tokens[this.pos++]){var b=a[0];this.yytext=a[1];this.yylloc=a[2];h.errorToken=a.origin||a;this.yylineno=this.yylloc.first_line}else b=\"\";return b},setInput:function(a){h.tokens=a;return this.pos=0},upcomingInput:function(){return\"\"}};h.yy=u(\"./nodes\");h.yy.parseError=function(a,b){var c=\nh.errorToken;var f=h.tokens;var g=c[0];var l=c[1];a=c[2];l=function(){switch(!1){case c!==f[f.length-1]:return\"end of input\";case \"INDENT\"!==g&&\"OUTDENT\"!==g:return\"indentation\";case \"IDENTIFIER\"!==g&&\"NUMBER\"!==g&&\"INFINITY\"!==g&&\"STRING\"!==g&&\"STRING_START\"!==g&&\"REGEX\"!==g&&\"REGEX_START\"!==g:return g.replace(/_START$/,\"\").toLowerCase();default:return r.nameWhitespaceCharacter(l)}}();return r.throwSyntaxError(\"unexpected \"+l,a)};var R=function(a,b){var c;if(a.isNative())var f=\"native\";else{a.isEval()?\n(c=a.getScriptNameOrSourceURL())||a.getEvalOrigin():c=a.getFileName();c||(c=\"\\x3canonymous\\x3e\");var g=a.getLineNumber();f=a.getColumnNumber();f=(b=b(c,g,f))?c+\":\"+b[0]+\":\"+b[1]:c+\":\"+g+\":\"+f}c=a.getFunctionName();g=a.isConstructor();if(a.isToplevel()||g)return g?\"new \"+(c||\"\\x3canonymous\\x3e\")+\" (\"+f+\")\":c?c+\" (\"+f+\")\":f;g=a.getMethodName();var h=a.getTypeName();return c?(b=a=\"\",h&&c.indexOf(h)&&(b=h+\".\"),g&&c.indexOf(\".\"+g)!==c.length-g.length-1&&(a=\" [as \"+g+\"]\"),\"\"+b+c+a+\" (\"+f+\")\"):h+\".\"+(g||\n\"\\x3canonymous\\x3e\")+\" (\"+f+\")\"};var z=function(a){return null!=F[a]?F[a]:null!=F[\"\\x3canonymous\\x3e\"]?F[\"\\x3canonymous\\x3e\"]:null!=I[a]?(a=qa(I[a],{filename:a,sourceMap:!0,literate:r.isLiterate(a)}),a.sourceMap):null};Error.prepareStackTrace=function(a,b){var c;var g=function(a,b,c){var f;a=z(a);null!=a&&(f=a.sourceLocation([b-1,c-1]));return null!=f?[f[0]+1,f[1]+1]:null};var h=function(){var a;var h=[];var k=0;for(a=b.length;k<a;k++){c=b[k];if(c.getFunction()===f.run)break;h.push(\"    at \"+R(c,\ng))}return h}();return a.toString()+\"\\n\"+h.join(\"\\n\")+\"\\n\"}}).call(this);return f}();u[\"./browser\"]=function(){(function(){var f=[].indexOf||function(a){for(var b=0,f=this.length;b<f;b++)if(b in this&&this[b]===a)return b;return-1};var qa=u(\"./coffee-script\");qa.require=u;var q=qa.compile;qa.eval=function(a,b){null==b&&(b={});null==b.bare&&(b.bare=!0);return eval(q(a,b))};qa.run=function(a,b){null==b&&(b={});b.bare=!0;b.shiftLine=!0;return Function(q(a,b))()};if(\"undefined\"!==typeof window&&null!==\nwindow){\"undefined\"!==typeof btoa&&null!==btoa&&\"undefined\"!==typeof JSON&&null!==JSON&&(q=function(a,b){null==b&&(b={});b.inlineMap=!0;return qa.compile(a,b)});qa.load=function(a,b,f,g){null==f&&(f={});null==g&&(g=!1);f.sourceFiles=[a];var h=window.ActiveXObject?new window.ActiveXObject(\"Microsoft.XMLHTTP\"):new window.XMLHttpRequest;h.open(\"GET\",a,!0);\"overrideMimeType\"in h&&h.overrideMimeType(\"text/plain\");h.onreadystatechange=function(){var q;if(4===h.readyState){if(0===(q=h.status)||200===q)q=\n[h.responseText,f],g||qa.run.apply(qa,q);else throw Error(\"Could not load \"+a);if(b)return b(q)}};return h.send(null)};var y=function(){var a,b,q;var g=window.document.getElementsByTagName(\"script\");var h=[\"text/coffeescript\",\"text/literate-coffeescript\"];var r=function(){var a,b;var n=[];var r=0;for(a=g.length;r<a;r++)q=g[r],(b=q.type,0<=f.call(h,b))&&n.push(q);return n}();var n=0;var u=function(){var a=r[n];if(a instanceof Array)return qa.run.apply(qa,a),n++,u()};var y=function(a,b){var f;var g=\n{literate:a.type===h[1]};if(f=a.src||a.getAttribute(\"data-src\"))return qa.load(f,function(a){r[b]=a;return u()},g,!0);g.sourceFiles=[\"embedded\"];return r[b]=[a.innerHTML,g]};var I=a=0;for(b=r.length;a<b;I=++a){var F=r[I];y(F,I)}return u()};window.addEventListener?window.addEventListener(\"DOMContentLoaded\",y,!1):window.attachEvent(\"onload\",y)}}).call(this);return{}}();return u[\"./coffee-script\"]}();\"function\"===typeof define&&define.amd?define(function(){return xa}):u.CoffeeScript=xa})(this);"
  },
  {
    "path": "tools/coffee/coffee.cmd",
    "content": "::For convenience\r\n@cscript //nologo \"%~dp0coffee.wsf\" %*\r\n"
  },
  {
    "path": "tools/coffee/coffee.wsf",
    "content": "<job>\r\n<!-- https://github.com/jashkenas/coffee-script/raw/master/extras/coffee-script.js -->\r\n<script src=\"coffee-script.js\" language=\"JScript\" />\r\n<script language=\"JScript\">\r\n(function() {\r\n\r\n    var args = [];\r\n    for (var i = 0; i < WScript.Arguments.Length; i++) {\r\n        args.push(WScript.Arguments.Item(i));\r\n    }\r\n\r\n    // FileSystemObject: http://msdn.microsoft.com/en-us/library/bkx696eh.aspx\r\n    var fso = new ActiveXObject(\"Scripting.FileSystemObject\");\r\n\r\n    var isfolder = (args[0] && fso.folderExists(args[0]));\r\n\r\n    if (isfolder) {\r\n        f = fso.getFolder(args[0]);\r\n        e = new Enumerator(f.files);\r\n        for (; !e.atEnd(); e.moveNext()) {\r\n            if (e.item().path.toLowerCase().lastIndexOf('.coffee') != -1) {\r\n                convert(e.item(), args[1]);\r\n            }\r\n        }\r\n    }\r\n    else {\r\n        convert(args[0], args[1])\r\n    }\r\n\r\n})();\r\n\r\n\r\nfunction convert(input, output) {\r\n\r\n    var fso = new ActiveXObject(\"Scripting.FileSystemObject\");\r\n\r\n    if (output) {\r\n        // if output specifies a folder name, output filename is same as input filename with .coffee extension\r\n        if (fso.folderExists(output)) {\r\n            output = output + '\\\\' + fso.getFile(input).name.replace('\\.coffee', '.js')\r\n        }\r\n    }\r\n\r\n    var coffee;\r\n    if (!input) {\r\n        // Read all input data from STDIN\r\n        var chunks = [];\r\n        while (!WScript.StdIn.AtEndOfStream)\r\n            chunks.push(WScript.StdIn.ReadAll());\r\n        coffee = chunks.join('');\r\n    }\r\n    else {\r\n        coffee = readUtf8(input);\r\n    }\r\n\r\n    try {\r\n        if(!Object.create)\r\n            Object.create = function(proto)\r\n            {\r\n                function f(){}\r\n                f.prototype = proto;\r\n                return new f;\r\n            }\r\n\r\n        var js = CoffeeScript.compile(coffee, {filename: \"temp.coffee\"});\r\n        if (!output) {\r\n            WScript.StdOut.Write(js);\r\n        }\r\n        else {\r\n            writeUtf8(output, js);\r\n        }\r\n    }\r\n    catch (err) {\r\n        WScript.StdErr.WriteLine(err.message);\r\n        WScript.Quit(1);\r\n    }\r\n}\r\n\r\nfunction readUtf8(filename) {\r\n    var stream = new ActiveXObject(\"ADODB.Stream\");\r\n    stream.Open();\r\n    stream.Type = 2; // Text\r\n    stream.Charset = 'utf-8';\r\n    stream.LoadFromFile(filename);\r\n    var text = stream.ReadText();\r\n    stream.Close();\r\n    return text;\r\n}\r\n\r\nfunction writeUtf8(filename, text) {\r\n    var stream = new ActiveXObject(\"ADODB.Stream\");\r\n    stream.Type = 2; // Text\r\n    stream.Charset = \"utf-8\";\r\n    stream.Open();\r\n    stream.WriteText(text);\r\n\r\n    stream.Position = 0;\r\n    stream.Type = 1; // Binary\r\n    stream.Position = 3;\r\n    var binary = stream.Read();\r\n    stream.Close();\r\n\r\n    stream.Open();\r\n    stream.Write(binary);\r\n    stream.SaveToFile(filename, 2);\r\n    stream.Close();\r\n}\r\n</script>\r\n</job>"
  },
  {
    "path": "update.py",
    "content": "import os\nimport sys\nimport json\nimport re\nimport shutil\n\n\ndef update():\n    from Config import config\n    config.parse(silent=True)\n\n    if getattr(sys, 'source_update_dir', False):\n        if not os.path.isdir(sys.source_update_dir):\n            os.makedirs(sys.source_update_dir)\n        source_path = sys.source_update_dir.rstrip(\"/\")\n    else:\n        source_path = os.getcwd().rstrip(\"/\")\n\n    if config.dist_type.startswith(\"bundle_linux\"):\n        runtime_path = os.path.normpath(os.path.dirname(sys.executable) + \"/../..\")\n    else:\n        runtime_path = os.path.dirname(sys.executable)\n\n    updatesite_path = config.data_dir + \"/\" + config.updatesite\n\n    sites_json = json.load(open(config.data_dir + \"/sites.json\"))\n    updatesite_bad_files = sites_json.get(config.updatesite, {}).get(\"cache\", {}).get(\"bad_files\", {})\n    print(\n        \"Update site path: %s, bad_files: %s, source path: %s, runtime path: %s, dist type: %s\" %\n        (updatesite_path, len(updatesite_bad_files), source_path, runtime_path, config.dist_type)\n    )\n\n    updatesite_content_json = json.load(open(updatesite_path + \"/content.json\"))\n    inner_paths = list(updatesite_content_json.get(\"files\", {}).keys())\n    inner_paths += list(updatesite_content_json.get(\"files_optional\", {}).keys())\n\n    # Keep file only in ZeroNet directory\n    inner_paths = [inner_path for inner_path in inner_paths if re.match(\"^(core|bundle)\", inner_path)]\n\n    # Checking plugins\n    plugins_enabled = []\n    plugins_disabled = []\n    if os.path.isdir(\"%s/plugins\" % source_path):\n        for dir in os.listdir(\"%s/plugins\" % source_path):\n            if dir.startswith(\"disabled-\"):\n                plugins_disabled.append(dir.replace(\"disabled-\", \"\"))\n            else:\n                plugins_enabled.append(dir)\n        print(\"Plugins enabled:\", plugins_enabled, \"disabled:\", plugins_disabled)\n\n    update_paths = {}\n\n    for inner_path in inner_paths:\n        if \"..\" in inner_path:\n            continue\n        inner_path = inner_path.replace(\"\\\\\", \"/\").strip(\"/\")  # Make sure we have unix path\n        print(\".\", end=\" \")\n        if inner_path.startswith(\"core\"):\n            dest_path = source_path + \"/\" + re.sub(\"^core/\", \"\", inner_path)\n        elif inner_path.startswith(config.dist_type):\n            dest_path = runtime_path + \"/\" + re.sub(\"^bundle[^/]+/\", \"\", inner_path)\n        else:\n            continue\n\n        if not dest_path:\n            continue\n\n        # Keep plugin disabled/enabled status\n        match = re.match(re.escape(source_path) + \"/plugins/([^/]+)\", dest_path)\n        if match:\n            plugin_name = match.group(1).replace(\"disabled-\", \"\")\n            if plugin_name in plugins_enabled:  # Plugin was enabled\n                dest_path = dest_path.replace(\"plugins/disabled-\" + plugin_name, \"plugins/\" + plugin_name)\n            elif plugin_name in plugins_disabled:  # Plugin was disabled\n                dest_path = dest_path.replace(\"plugins/\" + plugin_name, \"plugins/disabled-\" + plugin_name)\n            print(\"P\", end=\" \")\n\n        dest_dir = os.path.dirname(dest_path)\n        if dest_dir and not os.path.isdir(dest_dir):\n            os.makedirs(dest_dir)\n\n        if dest_dir != dest_path.strip(\"/\"):\n            update_paths[updatesite_path + \"/\" + inner_path] = dest_path\n\n    num_ok = 0\n    num_rename = 0\n    num_error = 0\n    for path_from, path_to in update_paths.items():\n        print(\"-\", path_from, \"->\", path_to)\n        if not os.path.isfile(path_from):\n            print(\"Missing file\")\n            continue\n\n        data = open(path_from, \"rb\").read()\n\n        try:\n            open(path_to, 'wb').write(data)\n            num_ok += 1\n        except Exception as err:\n            try:\n                print(\"Error writing: %s. Renaming old file as workaround...\" % err)\n                path_to_tmp = path_to + \"-old\"\n                if os.path.isfile(path_to_tmp):\n                    os.unlink(path_to_tmp)\n                os.rename(path_to, path_to_tmp)\n                num_rename += 1\n                open(path_to, 'wb').write(data)\n                shutil.copymode(path_to_tmp, path_to)  # Copy permissions\n                print(\"Write done after rename!\")\n                num_ok += 1\n            except Exception as err:\n                print(\"Write error after rename: %s\" % err)\n                num_error += 1\n    print(\"* Updated files: %s, renamed: %s, error: %s\" % (num_ok, num_rename, num_error))\n\n\nif __name__ == \"__main__\":\n    sys.path.insert(0, os.path.join(os.path.dirname(__file__), \"src\"))  # Imports relative to src\n\n    update()\n"
  },
  {
    "path": "zeronet.py",
    "content": "#!/usr/bin/env python3\nimport os\nimport sys\n\n\ndef main():\n    if sys.version_info.major < 3:\n        print(\"Error: Python 3.x is required\")\n        sys.exit(0)\n\n    if \"--silent\" not in sys.argv:\n        print(\"- Starting ZeroNet...\")\n\n    main = None\n    try:\n        import main\n        main.start()\n    except Exception as err:  # Prevent closing\n        import traceback\n        try:\n            import logging\n            logging.exception(\"Unhandled exception: %s\" % err)\n        except Exception as log_err:\n            print(\"Failed to log error:\", log_err)\n            traceback.print_exc()\n        from Config import config\n        error_log_path = config.log_dir + \"/error.log\"\n        traceback.print_exc(file=open(error_log_path, \"w\"))\n        print(\"---\")\n        print(\"Please report it: https://github.com/HelloZeroNet/ZeroNet/issues/new?assignees=&labels=&template=bug-report.md\")\n        if sys.platform.startswith(\"win\") and \"python.exe\" not in sys.executable:\n            displayErrorMessage(err, error_log_path)\n\n    if main and (main.update_after_shutdown or main.restart_after_shutdown):  # Updater\n        if main.update_after_shutdown:\n            print(\"Shutting down...\")\n            prepareShutdown()\n            import update\n            print(\"Updating...\")\n            update.update()\n            if main.restart_after_shutdown:\n                print(\"Restarting...\")\n                restart()\n        else:\n            print(\"Shutting down...\")\n            prepareShutdown()\n            print(\"Restarting...\")\n            restart()\n\n\ndef displayErrorMessage(err, error_log_path):\n    import ctypes\n    import urllib.parse\n    import subprocess\n\n    MB_YESNOCANCEL = 0x3\n    MB_ICONEXCLAIMATION = 0x30\n\n    ID_YES = 0x6\n    ID_NO = 0x7\n    ID_CANCEL = 0x2\n\n    err_message = \"%s: %s\" % (type(err).__name__, err)\n    err_title = \"Unhandled exception: %s\\nReport error?\" % err_message\n\n    res = ctypes.windll.user32.MessageBoxW(0, err_title, \"ZeroNet error\", MB_YESNOCANCEL | MB_ICONEXCLAIMATION)\n    if res == ID_YES:\n        import webbrowser\n        report_url = \"https://github.com/HelloZeroNet/ZeroNet/issues/new?assignees=&labels=&template=bug-report.md&title=%s\"\n        webbrowser.open(report_url % urllib.parse.quote(\"Unhandled exception: %s\" % err_message))\n    if res in [ID_YES, ID_NO]:\n        subprocess.Popen(['notepad.exe', error_log_path])\n\ndef prepareShutdown():\n    import atexit\n    atexit._run_exitfuncs()\n\n    # Close log files\n    if \"main\" in sys.modules:\n        logger = sys.modules[\"main\"].logging.getLogger()\n\n        for handler in logger.handlers[:]:\n            handler.flush()\n            handler.close()\n            logger.removeHandler(handler)\n\n    import time\n    time.sleep(1)  # Wait for files to close\n\ndef restart():\n    args = sys.argv[:]\n\n    sys.executable = sys.executable.replace(\".pkg\", \"\")  # Frozen mac fix\n\n    if not getattr(sys, 'frozen', False):\n        args.insert(0, sys.executable)\n\n    # Don't open browser after restart\n    if \"--open_browser\" in args:\n        del args[args.index(\"--open_browser\") + 1]  # argument value\n        del args[args.index(\"--open_browser\")]  # argument key\n\n    if getattr(sys, 'frozen', False):\n        pos_first_arg = 1  # Only the executable\n    else:\n        pos_first_arg = 2  # Interpter, .py file path\n\n    args.insert(pos_first_arg, \"--open_browser\")\n    args.insert(pos_first_arg + 1, \"False\")\n\n    if sys.platform == 'win32':\n        args = ['\"%s\"' % arg for arg in args]\n\n    try:\n        print(\"Executing %s %s\" % (sys.executable, args))\n        os.execv(sys.executable, args)\n    except Exception as err:\n        print(\"Execv error: %s\" % err)\n    print(\"Bye.\")\n\n\ndef start():\n    app_dir = os.path.dirname(os.path.abspath(__file__))\n    os.chdir(app_dir)  # Change working dir to zeronet.py dir\n    sys.path.insert(0, os.path.join(app_dir, \"src/lib\"))  # External liblary directory\n    sys.path.insert(0, os.path.join(app_dir, \"src\"))  # Imports relative to src\n\n    if \"--update\" in sys.argv:\n        sys.argv.remove(\"--update\")\n        print(\"Updating...\")\n        import update\n        update.update()\n    else:\n        main()\n\n\nif __name__ == '__main__':\n    start()\n"
  }
]