[
  {
    "path": ".bowerrc",
    "content": "{\n    \"directory\": \"app/bower_components\"\n}\n"
  },
  {
    "path": ".codeclimate.yml",
    "content": "engines:\n    eslint:\n        enabled: true\n    duplication:\n        enabled: true\n        config :\n            languages:\n                - javascript\n\nlanguages:\n    JavaScript: true\n\nratings:\n    paths:\n        - app/scripts/**\n\nexclude_paths:\n    - test/**\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# editorconfig.org\n\nroot = true\n\n\n[*]\n\n# Change these settings to your own preference\nindent_style = space\nindent_size = 4\n\n# We recommend you to keep these unchanged\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "* text=auto"
  },
  {
    "path": ".gitignore",
    "content": "\n# Cordova build configs and keys\nbuild.json\n**/*.keystore\n\n# Compiled files\napp/styles/**/*.css\ndist/\ncordova/\nrelease/\n.tmp/\n\n# Bower\napp/bower_components\ntest/bower_components\n.bower-cache\n.bower-registry\n.bower-tmp\n\n# Test reports (Nightwatch)\n.reports\ntest/visual/screenshots\n\n# Old files that should not appear again if someone pushes them\napp/scripts/libs/remotestorage.js\napp/scripts/apps/settings/show/encryptView.js\napp/scripts/apps/settings/show/encryptTemplate.html\napp/scripts/helpers/syncStatus.js\n\n# Intellij\n.idea\n\n# Created by https://www.gitignore.io/api/node,vim,osx,linux,windows\n\n### Node ###\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\n\n### Vim ###\n# swap\n[._]*.s[a-w][a-z]\n[._]s[a-w][a-z]\n# session\nSession.vim\n# temporary\n.netrwhist\n*~\n# auto-generated tag files\ntags\n\n\n### OSX ###\n*.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\r\r\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n\n### Linux ###\n*~\n\n# temporary files which can be created if a process still has a handle open of a deleted file\n.fuse_hidden*\n\n# KDE directory preferences\n.directory\n\n# Linux trash folder which might appear on any partition or disk\n.Trash-*\n\n\n### Windows ###\n# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".jshintrc",
    "content": "{\n    \"node\": true,\n    \"browser\": true,\n    \"esnext\": true,\n    \"bitwise\": true,\n    \"camelcase\": true,\n    \"curly\": true,\n    \"eqeqeq\": true,\n    \"immed\": true,\n    \"indent\": 4,\n    \"latedef\": true,\n    \"newcap\": true,\n    \"noarg\": true,\n    \"quotmark\": \"single\",\n    \"regexp\": true,\n    \"undef\": true,\n    \"unused\": true,\n    \"strict\": true,\n    \"trailing\": true,\n    \"smarttabs\": true,\n    \"jquery\": true,\n    \"mocha\": true,\n    \"globals\": {\n        \"expect\": false,\n        \"define\": false\n    }\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n    - 6\n    - 'stable'\nbefore_install:\n    - npm install -g gulp bower jshint\ninstall:\n    - npm install\n    - bower install\n    - cd test && bower install && cd ../\nscript:\n    - gulp build\n    - gulp --root dist &\n    - set -e\n    - '[ \"${TRAVIS_PULL_REQUEST}\" = \"false\" ] && gulp nightwatch --env ci; echo $? || false'\n    - sleep 1\n    - killall gulp\nafter_failure:\n    - killall gulp\naddons:\n    sauce_connect:\n        username:\n            secure: \"F2waNLSycvKfroVB0wIExO9dimxFCrdoKdhj3k8NzcB8a1OFfmu9JqGf+NIHUOXqW3r6LEOmtH522II3AsEAz2a5En+rzl83CVBYiYylYzvnnGZHgTMttemYWRqbH8pEYGA+o/m47NDJo4AbAJ3A5lQvHPKylbPVuuHMGgHwrdXgGAktw2mq15mo65LEhSVKRm08b6tB+AD1OesaZ//dzf/uhhmB6ukvXVI5CaGCJV13OVXHf1vIkutTqx3JrDwi2ErGhwTg/wc1ZKnWG7+EAKxsf0lfgNFCkAL3xS8309LyvrFDIoDCY0T3VeR0WDQtr+ryRDIcOROoo3Q2LUqqERD1W4a1Tsn3yxeEbyHzOPMo2o14AK04y3Alel+5en4V0hNEhDbSgV/pyzkrzZ1KVHNyNkZA5i1aCWG5rfO6oeVhPtUI/oAwom1Xa8FwvK96osbUmv5TDeax/dtMxYLtOZDJo6AZx6aVN7wqjDtLuYg0oZ7/+JAIaHfngxYgKE400sied6v2VNxYdqP+Duf2ZaiuOXfnnQB74/zy29uQtbztmAKuLF36/Wi1PWTBnezIo9vRLTzB8JVZ2echeJYbOpk5dqfnjAE2xwOiHzbqRfRvd699C+x9WCXgNZmySog7MEY5SJ31JqVfUzrhhSgo7xbpWfig9vNdExAcaDJpHSo=\"\n        access_key:\n            secure: \"yDTvc3TpWhwDLtF6LnVduEtfdi1lmA265XAAiirV2Hn/2NonOS4M5eW7j5RB9JY/psLpd9/l5SZZTIYxCxURrMBlxnHyOYlrNnA4aTXGVPulDECPmQFdXuMHJclnCx4GL/YOg5EXhsjD8EFB/ihavDaFr2740tuYet8KNqqB0RI5pkmp7RP8G3x3AiTu2zv72e5C0TukKBdCUpIhSX34ofsmvQ8KQ/DOAUK1MjNYxb55Mhf7MCgskC+bypzWZDuuOvM0w48OzwDuF6LBnHQCxvUmcUwuxRTuJNJZUFoOIEEP44r546fi0vzt9/1gLI2Qsc/YtuvSsPKquMb+iV1Rn8F77vGUN0woiYlcAAtrCIe+fk2Kl6FVNPY9KlldQZPwogLLwGpPN6h+kGVOgNBl0WSmiekDVbFDqo0TcNhlwkkpR8Iuy+WfWZI5l+pmZrBCnQIIFNd+SlLXlftp1aiaghBs6654tp+jULfUzjeL8yxBJpcsosi8+erpx4u7ldbtWi94z1LCstbU65nSPlEaKEmjGPWppFOL0nzORTT2trSil0WrQoAkBmsFkzxI7KOhFPoVuk26E4bY/o7Jz1Y2OGPYKdsZppB3pDDH6BxUYW/u5YLveEU64Kz6ixgDkOi3rHFrDiq81v7OKAINytqik6FV4N1zpsurQ2JUV9aPmf8=\"\n        tunnel_domains: localhost\n"
  },
  {
    "path": "CONTRIBUTE.md",
    "content": "Contribution\n================\nNote, all contributions should be done on `dev` branch.\n\n\n### Localizations\n----------------\n1. Copy ./app/locales/en and rename it to locale name - ./app/locales/[localeName]\n2. Open ./app/locales/[localeName]/translation.json\n3. Replace the values\n4. Make a *pull request*\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in \n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "README.md",
    "content": "# Laverna - note taking web app\n\n[![Join the chat at https://gitter.im/Laverna/laverna](https://badges.gitter.im/Laverna/laverna.svg)](https://gitter.im/Laverna/laverna?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n\n[![Build Status](https://travis-ci.org/Laverna/laverna.svg?branch=dev)](https://travis-ci.org/Laverna/laverna) [![devDependency Status](https://david-dm.org/Laverna/laverna/dev-status.svg)](https://david-dm.org/Laverna/laverna#info=devDependencies) [![Code Climate](https://codeclimate.com/github/Laverna/laverna/badges/gpa.svg)](https://codeclimate.com/github/Laverna/laverna)\n\nLaverna is a JavaScript note-taking web application with a Markdown editor and encryption support.  It's built to be an open source alternative to Evernote.\n\nThe application stores all your notes in your browser databases such as indexedDB or localStorage, which is good for security reasons, because only you have access to them.\n\n**Demo**: https://laverna.cc/ OR http://laverna.github.io/static-laverna\n\n## Features\n-----------\n\n* Markdown editor based on Pagedown\n* Manage your notes, even when you're offline\n* Secure client-side encryption\n* Synchronizes with cloud storage services (currently only with Dropbox and RemoteStorage)\n* Three editing modes: distraction free, preview, and normal mode\n* WYSIWYG control buttons\n* MathJax support\n* Syntax highlighting\n* No registration required\n* Web based\n* Keybindings\n\n## Tools\n\nOn the front-end this project uses JavaScript and the [Marionette JS](http://marionettejs.com/) framework while [Node JS](https://nodejs.org/en/), [Bower](https://bower.io/), and [Gulp.js](http://gulpjs.com/) are used on the back-end.  The test runner used is [karma](https://karma-runner.github.io/1.0/index.html) however,\ncontributors are free to utilize whatever testing tools they desire.\n\n\n## Installation\n---------------\nThere are several ways to start using Laverna:\n\n1. Open [laverna.cc][10] and start using it. No extra steps are needed.\n2. Use a desktop app.\n3. Use a prebuilt version from [Laverna/static-laverna][9] repository.\n4. Build it from the source code.\n\n### Desktop app installation\n---------------\nDownload the latest [Laverna release][13] for your operating system. After downloading the archive, you need to unpack it. Then, in the unpacked folder you need to run an executable (laverna.exe for Windows, laverna for Linux and Mac).\n\n#### Arch Linux (or derived distributions)\n\nThe package can be found [here](https://aur.archlinux.org/packages/laverna/). \n\nFor installation please use :\n\n```bash\n$ pacaur -S laverna\n```\n\nFor issue about installation please report [here](https://github.com/funilrys/PKGBUILD/issues/new) or contact [@funilrys](https://github.com/funilrys) on gitter [here](https://gitter.im/funilrys_/PKGBUILD)\n\n\n### Installation of a prebuilt version\n------------\n#### 1. Download\n\n```bash\n$ wget https://github.com/Laverna/static-laverna/archive/gh-pages.zip -O laverna.zip\n```\n\n#### 2. Unpack the downloaded archive\n\n```bash\n$ unzip laverna.zip\n```\n\n#### 3. Open index.html in a browser\nOpen in your favorite browser the index.html file which is located inside *laverna* directory.\n\n\n## Installation from source\n---------------\nTo install, do the following:\n\n#### 1. Install Git\n\nThis project requires that you have the latest version of git installed. To do so, see [Installing Git][14] (first-time users of git might want to check out the next section for configuring git).\n\n**Note:** Windows users will have to set the PATH variable for git after installing it.\n\n\n\n#### 2. Clone repository:\n\nFor those who plan on contributing to the project's development , hit the fork button at the top of the page first (others can go on to the next step). Open a terminal, or command line, and navigate to the desired location of where you want to download the repository. Then enter the following commands to clone the repo:\n\n```bash\n# clone the repository\n$ git clone git@github.com:Laverna/laverna.git\n# navigate to the project directory\ncd laverna\n```\n\n**3. Ensure you have the node.js platform installed.** (See OS-specific instructions on their [website][8]).\n\n**4. Ensure you have the bower and gulp packages installed** (locally and globally):\n\n```bash\n$ npm install bower\n$ npm install -g bower\n$ npm install gulp\n$ npm install -g gulp\n```\n\n#### 5. Install Laverna's dependencies:\n\n```bash\n$ npm install\n$ bower install\n$ cd test\n$ bower install\n$ cd ..\n```\n\n#### 6. Build minified version of Laverna:\n\n```bash\n$ gulp build\n```\n\n#### 7. Start Laverna:\n\n```bash\n$ gulp\n```\n\n## MacOS notes on accepting incoming connections\nBecause currently Laverna does not sign it's Mac packages, if you want to avoid the \"Accept incoming connections\" warning message everytime the application is launched, you can run the following commands. Assuming your current direction contains the laverna application:\n\n```bash\ncodesign -s - -f ./laverna.app/Contents/Frameworks/Electron\\ Framework.framework\ncodesign -s - -f ./laverna.app/Contents/Frameworks/Electron\\ Helper\\ EH.app \ncodesign -s - -f ./laverna.app/Contents/Frameworks/Electron\\ Helper\\ NP.app\ncodesign -s - -f ./laverna.app/Contents/Frameworks/Electron\\ Helper.app \ncodesign -s - -f ./laverna.app/Contents/Frameworks/Mantle.framework \ncodesign -s - -f ./laverna.app/Contents/Frameworks/ReactiveCocoa.framework \ncodesign -s - -f ./laverna.app/Contents/Frameworks/Squirrel.framework \ncodesign --verify -vv ./laverna.app\n```\n\n## Do you have questions?\n---------------\nPlease have a look in our [wiki][15].\n\n## Support\n---------------\n\n* Hit star button on [github][6]\n* Like us on [alternativeto.net][5]\n* [Contribute][7]\n\n### Coding Style Guidelines\n\nFor those wanting to contribute code, we ask that you use either plain JavaScript or the Marionette.js framework. (For more details on the preferred coding style see [.editorconfig](https://github.com/Laverna/laverna/blob/master/.editorconfig)). Also, all experimental changes are being pushed on the **dev** branch, so any feature changes are preferred to be done on either this branch or a branch that uses the dev branch as its parent.  \n\n\n## Donation:\n-----------\n\n* [Bitcoin][3]\n* [BountySource][12]\n\n## Security\n--------------\nLaverna uses the [SJCL] [1] library implementing the AES algorithm. You can review the code at:\n\n* https://github.com/Laverna/laverna/blob/master/app/scripts/classes/encryption.js\n* https://github.com/Laverna/laverna/blob/master/app/scripts/apps/encryption/\n\n## License\n--------------\nPublished under [MPL-2.0 License][11].\n\nLaverna uses a lot of other libraries and each of these [libraries use different licenses][2].\n\n[1]: http://bitwiseshiftleft.github.io/sjcl/\n[2]: https://github.com/Laverna/laverna/blob/master/bower.json\n[3]: http://blockchain.info/address/1Q68HfLjNvWbLFr3KGK6nfXg7vc3hpDr11\n[4]: https://www.gittip.com/Laverna/\n[5]: http://alternativeto.net/software/laverna/\n[6]: https://github.com/Laverna/laverna\n[7]: https://github.com/Laverna/laverna/blob/master/CONTRIBUTE.md\n[8]: http://nodejs.org\n[9]: https://github.com/Laverna/static-laverna/archive/gh-pages.zip\n[10]: https://laverna.cc/index.html\n[11]: https://www.mozilla.org/en-US/MPL/2.0/\n[12]: https://www.bountysource.com/teams/laverna\n[13]: https://github.com/Laverna/laverna/releases\n[14]: https://git-scm.com/book/en/v2\n[15]: https://github.com/Laverna/laverna/wiki\n"
  },
  {
    "path": "app/.htaccess",
    "content": "# Apache Server Configs v2.2.0 | MIT License\n# https://github.com/h5bp/server-configs-apache\n\n# (!) Using `.htaccess` files slows down Apache, therefore, if you have access\n# to the main server config file (usually called `httpd.conf`), you should add\n# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.\n\n# ##############################################################################\n# # CROSS-ORIGIN RESOURCE SHARING (CORS)                                       #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | Cross-domain AJAX requests                                                 |\n# ------------------------------------------------------------------------------\n\n# Allow cross-origin AJAX requests.\n# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity\n# http://enable-cors.org/\n\n# <IfModule mod_headers.c>\n#    Header set Access-Control-Allow-Origin \"*\"\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | CORS-enabled images                                                        |\n# ------------------------------------------------------------------------------\n\n# Send the CORS header for images when browsers request it.\n# https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image\n# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html\n# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/\n\n<IfModule mod_setenvif.c>\n    <IfModule mod_headers.c>\n        <FilesMatch \"\\.(cur|gif|ico|jpe?g|png|svgz?|webp)$\">\n            SetEnvIf Origin \":\" IS_CORS\n            Header set Access-Control-Allow-Origin \"*\" env=IS_CORS\n        </FilesMatch>\n    </IfModule>\n</IfModule>\n\n# ------------------------------------------------------------------------------\n# | Web fonts access                                                           |\n# ------------------------------------------------------------------------------\n\n# Allow access to web fonts from all domains.\n\n<IfModule mod_headers.c>\n    <FilesMatch \"\\.(eot|otf|tt[cf]|woff)$\">\n        Header set Access-Control-Allow-Origin \"*\"\n    </FilesMatch>\n</IfModule>\n\n\n# ##############################################################################\n# # ERRORS                                                                     #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | 404 error prevention for non-existing redirected folders                   |\n# ------------------------------------------------------------------------------\n\n# Prevent Apache from returning a 404 error as the result of a rewrite\n# when the directory with the same name does not exist.\n# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews\n# http://www.webmasterworld.com/apache/3808792.htm\n\nOptions -MultiViews\n\n# ------------------------------------------------------------------------------\n# | Custom error messages / pages                                              |\n# ------------------------------------------------------------------------------\n\n# Customize what Apache returns to the client in case of an error.\n# http://httpd.apache.org/docs/current/mod/core.html#errordocument\n\nErrorDocument 404 /404.html\n\n\n# ##############################################################################\n# # INTERNET EXPLORER                                                          #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | Better website experience                                                  |\n# ------------------------------------------------------------------------------\n\n# Force Internet Explorer to render pages in the highest available mode\n# in the various cases when it may not.\n# http://hsivonen.iki.fi/doctype/ie-mode.pdf\n\n<IfModule mod_headers.c>\n    Header set X-UA-Compatible \"IE=edge\"\n    # `mod_headers` cannot match based on the content-type, however, this\n    # header should be send only for HTML pages and not for the other resources\n    <FilesMatch \"\\.(appcache|atom|crx|css|cur|eot|f4[abpv]|flv|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|tt[cf]|vcf|vtt|webapp|web[mp]|woff|xml|xpi)$\">\n        Header unset X-UA-Compatible\n    </FilesMatch>\n</IfModule>\n\n# ------------------------------------------------------------------------------\n# | Cookie setting from iframes                                                |\n# ------------------------------------------------------------------------------\n\n# Allow cookies to be set from iframes in Internet Explorer.\n# http://msdn.microsoft.com/en-us/library/ms537343.aspx\n# http://www.w3.org/TR/2000/CR-P3P-20001215/\n\n# <IfModule mod_headers.c>\n#   Header set P3P \"policyref=\\\"/w3c/p3p.xml\\\", CP=\\\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\\\"\"\n# </IfModule>\n\n\n# ##############################################################################\n# # MIME TYPES AND ENCODING                                                    #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | Proper MIME types for all files                                            |\n# ------------------------------------------------------------------------------\n\n<IfModule mod_mime.c>\n\n  # Audio\n    AddType audio/mp4                                   m4a f4a f4b\n    AddType audio/ogg                                   oga ogg opus\n\n  # Data interchange\n    AddType application/json                            json map\n    AddType application/ld+json                         jsonld\n\n  # JavaScript\n    # Normalize to standard type.\n    # http://tools.ietf.org/html/rfc4329#section-7.2\n    AddType application/javascript                      js\n\n  # Video\n    AddType video/mp4                                   f4v f4p m4v mp4\n    AddType video/ogg                                   ogv\n    AddType video/webm                                  webm\n    AddType video/x-flv                                 flv\n\n  # Web fonts\n    AddType application/font-woff                       woff\n    AddType application/vnd.ms-fontobject               eot\n\n    # Browsers usually ignore the font MIME types and simply sniff the bytes\n    # to figure out the font type.\n    # http://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern\n\n    # Chrome however, shows a warning if any other MIME types are used for\n    # the following fonts.\n\n    AddType application/x-font-ttf                      ttc ttf\n    AddType font/opentype                               otf\n\n    # Make SVGZ fonts work on the iPad.\n    # https://twitter.com/FontSquirrel/status/14855840545\n    AddType     image/svg+xml                           svgz\n    AddEncoding gzip                                    svgz\n\n  # Other\n    AddType application/octet-stream                    safariextz\n    AddType application/x-chrome-extension              crx\n    AddType application/x-opera-extension               oex\n    AddType application/x-web-app-manifest+json         webapp\n    AddType application/x-xpinstall                     xpi\n    AddType application/xml                             atom rdf rss xml\n    AddType image/webp                                  webp\n    AddType image/x-icon                                cur\n    AddType text/cache-manifest                         appcache manifest\n    AddType text/vtt                                    vtt\n    AddType text/x-component                            htc\n    AddType text/x-vcard                                vcf\n\n</IfModule>\n\n# ------------------------------------------------------------------------------\n# | UTF-8 encoding                                                             |\n# ------------------------------------------------------------------------------\n\n# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.\nAddDefaultCharset utf-8\n\n# Force UTF-8 for certain file formats.\n<IfModule mod_mime.c>\n    AddCharset utf-8 .atom .css .js .json .jsonld .rss .vtt .webapp .xml\n</IfModule>\n\n\n# ##############################################################################\n# # URL REWRITES                                                               #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | Rewrite engine                                                             |\n# ------------------------------------------------------------------------------\n\n# Turn on the rewrite engine and enable the `FollowSymLinks` option (this is\n# necessary in order for the following directives to work).\n\n# If your web host doesn't allow the `FollowSymlinks` option, you may need to\n# comment it out and use `Options +SymLinksIfOwnerMatch`, but be aware of the\n# performance impact.\n# http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks\n\n# Also, some cloud hosting services require `RewriteBase` to be set.\n# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site\n\n<IfModule mod_rewrite.c>\n    Options +FollowSymlinks\n  # Options +SymLinksIfOwnerMatch\n    RewriteEngine On\n  # RewriteBase /\n</IfModule>\n\n# ------------------------------------------------------------------------------\n# | Suppressing / Forcing the `www.` at the beginning of URLs                  |\n# ------------------------------------------------------------------------------\n\n# The same content should never be available under two different URLs,\n# especially not with and without `www.` at the beginning. This can cause\n# SEO problems (duplicate content), and therefore, you should choose one\n# of the alternatives and redirect the other one.\n\n# By default `Option 1` (no `www.`) is activated.\n# http://no-www.org/faq.php?q=class_b\n\n# If you would prefer to use `Option 2`, just comment out all the lines\n# from `Option 1` and uncomment the ones from `Option 2`.\n\n# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n# Option 1: rewrite www.example.com → example.com\n\n<IfModule mod_rewrite.c>\n    RewriteCond %{HTTPS} !=on\n    RewriteCond %{HTTP_HOST} ^www\\.(.+)$ [NC]\n    RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]\n</IfModule>\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n# Option 2: rewrite example.com → www.example.com\n\n# Be aware that the following might not be a good idea if you use \"real\"\n# subdomains for certain parts of your website.\n\n# <IfModule mod_rewrite.c>\n#    RewriteCond %{HTTPS} !=on\n#    RewriteCond %{HTTP_HOST} !^www\\. [NC]\n#    RewriteCond %{SERVER_ADDR} !=127.0.0.1\n#    RewriteCond %{SERVER_ADDR} !=::1\n#    RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]\n# </IfModule>\n\n\n# ##############################################################################\n# # SECURITY                                                                   #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | Clickjacking                                                               |\n# ------------------------------------------------------------------------------\n\n# Protect website against clickjacking.\n\n# The example below sends the `X-Frame-Options` response header with the value\n# `DENY`, informing browsers not to display the web page content in any frame.\n\n# This might not be the best setting for everyone. You should read about the\n# other two possible values for `X-Frame-Options`: `SAMEORIGIN` & `ALLOW-FROM`.\n# http://tools.ietf.org/html/rfc7034#section-2.1\n\n# Keep in mind that while you could send the `X-Frame-Options` header for all\n# of your site’s pages, this has the potential downside that it forbids even\n# non-malicious framing of your content (e.g.: when users visit your site using\n# a Google Image Search results page).\n\n# Nonetheless, you should ensure that you send the `X-Frame-Options` header for\n# all pages that allow a user to make a state changing operation (e.g: pages\n# that contain one-click purchase links, checkout or bank-transfer confirmation\n# pages, pages that make permanent configuration changes, etc.).\n\n# Sending the `X-Frame-Options` header can also protect your website against\n# more than just clickjacking attacks: https://cure53.de/xfo-clickjacking.pdf.\n\n# http://tools.ietf.org/html/rfc7034\n# http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx\n# https://www.owasp.org/index.php/Clickjacking\n\n# <IfModule mod_headers.c>\n#     Header set X-Frame-Options \"DENY\"\n#     <FilesMatch \"\\.(appcache|atom|crx|css|cur|eot|f4[abpv]|flv|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|tt[cf]|vcf|vtt|webapp|web[mp]|woff|xml|xpi)$\">\n#         Header unset X-Frame-Options\n#     </FilesMatch>\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | Content Security Policy (CSP)                                              |\n# ------------------------------------------------------------------------------\n\n# Mitigate the risk of cross-site scripting and other content-injection attacks.\n\n# This can be done by setting a `Content Security Policy` which whitelists\n# trusted sources of content for your website.\n\n# The example header below allows ONLY scripts that are loaded from the current\n# site's origin (no inline scripts, no CDN, etc). This almost certainly won't\n# work as-is for your site!\n\n# For more details on how to craft a reasonable policy for your site, read:\n# http://html5rocks.com/en/tutorials/security/content-security-policy (or the\n# specification: http://w3.org/TR/CSP). Also, to make things easier, you can\n# use an online CSP header generator such as: http://cspisawesome.com/.\n\n# <IfModule mod_headers.c>\n#     Header set Content-Security-Policy \"script-src 'self'; object-src 'self'\"\n#     <FilesMatch \"\\.(appcache|atom|crx|css|cur|eot|f4[abpv]|flv|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|tt[cf]|vcf|vtt|webapp|web[mp]|woff|xml|xpi)$\">\n#         Header unset Content-Security-Policy\n#     </FilesMatch>\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | File access                                                                |\n# ------------------------------------------------------------------------------\n\n# Block access to directories without a default document.\n# You should leave the following uncommented, as you shouldn't allow anyone to\n# surf through every directory on your server (which may includes rather private\n# places such as the CMS's directories).\n\n<IfModule mod_autoindex.c>\n    Options -Indexes\n</IfModule>\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n# Block access to hidden files and directories.\n# This includes directories used by version control systems such as Git and SVN.\n\n<IfModule mod_rewrite.c>\n    RewriteCond %{SCRIPT_FILENAME} -d [OR]\n    RewriteCond %{SCRIPT_FILENAME} -f\n    RewriteRule \"(^|/)\\.\" - [F]\n</IfModule>\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n# Block access to files that can expose sensitive information.\n\n# By default, block access to backup and source files that may be left by some\n# text editors and can pose a security risk when anyone has access to them.\n# http://feross.org/cmsploit/\n\n# IMPORTANT: Update the `<FilesMatch>` regular expression from below to include\n# any files that might end up on your production server and can expose sensitive\n# information about your website. These files may include: configuration files,\n# files that contain metadata about the project (e.g.: project dependencies),\n# build scripts, etc..\n\n<FilesMatch \"(^#.*#|\\.(bak|config|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$\">\n\n    # Apache < 2.3\n    <IfModule !mod_authz_core.c>\n        Order allow,deny\n        Deny from all\n        Satisfy All\n    </IfModule>\n\n    # Apache ≥ 2.3\n    <IfModule mod_authz_core.c>\n        Require all denied\n    </IfModule>\n\n</FilesMatch>\n\n# ------------------------------------------------------------------------------\n# | Reducing MIME-type security risks                                          |\n# ------------------------------------------------------------------------------\n\n# Prevent some browsers from MIME-sniffing the response.\n\n# This reduces exposure to drive-by download attacks and should be enable\n# especially if the web server is serving user uploaded content, content\n# that could potentially be treated by the browser as executable.\n\n# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx\n# http://msdn.microsoft.com/en-us/library/ie/gg622941.aspx\n# http://mimesniff.spec.whatwg.org/\n\n# <IfModule mod_headers.c>\n#     Header set X-Content-Type-Options \"nosniff\"\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | Reflected Cross-Site Scripting (XSS) attacks                               |\n# ------------------------------------------------------------------------------\n\n# (1) Try to re-enable the Cross-Site Scripting (XSS) filter built into the\n#     most recent web browsers.\n#\n#     The filter is usually enabled by default, but in some cases it may be\n#     disabled by the user. However, in Internet Explorer for example, it can\n#     be re-enabled just by sending the `X-XSS-Protection` header with the\n#     value of `1`.\n#\n# (2) Prevent web browsers from rendering the web page if a potential reflected\n#     (a.k.a non-persistent) XSS attack is detected by the filter.\n#\n#     By default, if the filter is enabled and browsers detect a reflected\n#     XSS attack, they will attempt to block the attack by making the smallest\n#     possible modifications to the returned web page.\n#\n#     Unfortunately, in some browsers (e.g.: Internet Explorer), this default\n#     behavior may allow the XSS filter to be exploited, thereby, it's better\n#     to tell browsers to prevent the rendering of the page altogether, instead\n#     of attempting to modify it.\n#\n#     http://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities\n#\n# IMPORTANT: Do not rely on the XSS filter to prevent XSS attacks! Ensure that\n# you are taking all possible measures to prevent XSS attacks, the most obvious\n# being: validating and sanitizing your site's inputs.\n#\n# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx\n# http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx\n# https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29\n\n# <IfModule mod_headers.c>\n#     #                           (1)    (2)\n#     Header set X-XSS-Protection \"1; mode=block\"\n#     <FilesMatch \"\\.(appcache|atom|crx|css|cur|eot|f4[abpv]|flv|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|tt[cf]|vcf|vtt|webapp|web[mp]|woff|xml|xpi)$\">\n#         Header unset X-XSS-Protection\n#     </FilesMatch>\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | Secure Sockets Layer (SSL)                                                 |\n# ------------------------------------------------------------------------------\n\n# Rewrite secure requests properly in order to prevent SSL certificate warnings.\n# E.g.: prevent `https://www.example.com` when your certificate only allows\n# `https://secure.example.com`.\n\n# <IfModule mod_rewrite.c>\n#    RewriteCond %{SERVER_PORT} !^443\n#    RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | HTTP Strict Transport Security (HSTS)                                      |\n# ------------------------------------------------------------------------------\n\n# Force client-side SSL redirection.\n\n# If a user types `example.com` in his browser, the above rule will redirect\n# him to the secure version of the site. That still leaves a window of\n# opportunity (the initial HTTP connection) for an attacker to downgrade or\n# redirect the request.\n\n# The following header ensures that browser will ONLY connect to your server\n# via HTTPS, regardless of what the users type in the address bar.\n\n# http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1\n# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/\n\n# IMPORTANT: Remove the `includeSubDomains` optional directive if the subdomains\n# are not using HTTPS.\n\n# <IfModule mod_headers.c>\n#    Header set Strict-Transport-Security \"max-age=16070400; includeSubDomains\"\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | Server software information                                                |\n# ------------------------------------------------------------------------------\n\n# Avoid displaying the exact Apache version number, the description of the\n# generic OS-type and the information about Apache's compiled-in modules.\n\n# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!\n\n# ServerTokens Prod\n\n\n# ##############################################################################\n# # WEB PERFORMANCE                                                            #\n# ##############################################################################\n\n# ------------------------------------------------------------------------------\n# | Compression                                                                |\n# ------------------------------------------------------------------------------\n\n<IfModule mod_deflate.c>\n\n    # Force compression for mangled headers.\n    # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping\n    <IfModule mod_setenvif.c>\n        <IfModule mod_headers.c>\n            SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\\s*,?\\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding\n            RequestHeader append Accept-Encoding \"gzip,deflate\" env=HAVE_Accept-Encoding\n        </IfModule>\n    </IfModule>\n\n    # Compress all output labeled with one of the following MIME-types\n    # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`\n    #  and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines\n    #  as `AddOutputFilterByType` is still in the core directives).\n    <IfModule mod_filter.c>\n        AddOutputFilterByType DEFLATE application/atom+xml \\\n                                      application/javascript \\\n                                      application/json \\\n                                      application/ld+json \\\n                                      application/rss+xml \\\n                                      application/vnd.ms-fontobject \\\n                                      application/x-font-ttf \\\n                                      application/x-web-app-manifest+json \\\n                                      application/xhtml+xml \\\n                                      application/xml \\\n                                      font/opentype \\\n                                      image/svg+xml \\\n                                      image/x-icon \\\n                                      text/css \\\n                                      text/html \\\n                                      text/plain \\\n                                      text/x-component \\\n                                      text/xml\n    </IfModule>\n\n</IfModule>\n\n# ------------------------------------------------------------------------------\n# | Content transformations                                                    |\n# ------------------------------------------------------------------------------\n\n# Prevent mobile network providers from modifying the website's content.\n# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.\n\n# <IfModule mod_headers.c>\n#    Header set Cache-Control \"no-transform\"\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | ETags                                                                      |\n# ------------------------------------------------------------------------------\n\n# Remove `ETags` as resources are sent with far-future expires headers.\n# http://developer.yahoo.com/performance/rules.html#etags.\n\n# `FileETag None` doesn't work in all cases.\n<IfModule mod_headers.c>\n    Header unset ETag\n</IfModule>\n\nFileETag None\n\n# ------------------------------------------------------------------------------\n# | Expires headers                                                            |\n# ------------------------------------------------------------------------------\n\n# The following expires headers are set pretty far in the future. If you\n# don't control versioning with filename-based cache busting, consider\n# lowering the cache time for resources such as style sheets and JavaScript\n# files to something like one week.\n\n<IfModule mod_expires.c>\n\n    ExpiresActive on\n    ExpiresDefault                                      \"access plus 1 month\"\n\n  # CSS\n    ExpiresByType text/css                              \"access plus 1 year\"\n\n  # Data interchange\n    ExpiresByType application/json                      \"access plus 0 seconds\"\n    ExpiresByType application/ld+json                   \"access plus 0 seconds\"\n    ExpiresByType application/xml                       \"access plus 0 seconds\"\n    ExpiresByType text/xml                              \"access plus 0 seconds\"\n\n  # Favicon (cannot be renamed!) and cursor images\n    ExpiresByType image/x-icon                          \"access plus 1 week\"\n\n  # HTML components (HTCs)\n    ExpiresByType text/x-component                      \"access plus 1 month\"\n\n  # HTML\n    ExpiresByType text/html                             \"access plus 0 seconds\"\n\n  # JavaScript\n    ExpiresByType application/javascript                \"access plus 1 year\"\n\n  # Manifest files\n    ExpiresByType application/x-web-app-manifest+json   \"access plus 0 seconds\"\n    ExpiresByType text/cache-manifest                   \"access plus 0 seconds\"\n\n  # Media\n    ExpiresByType audio/ogg                             \"access plus 1 month\"\n    ExpiresByType image/gif                             \"access plus 1 month\"\n    ExpiresByType image/jpeg                            \"access plus 1 month\"\n    ExpiresByType image/png                             \"access plus 1 month\"\n    ExpiresByType video/mp4                             \"access plus 1 month\"\n    ExpiresByType video/ogg                             \"access plus 1 month\"\n    ExpiresByType video/webm                            \"access plus 1 month\"\n\n  # Web feeds\n    ExpiresByType application/atom+xml                  \"access plus 1 hour\"\n    ExpiresByType application/rss+xml                   \"access plus 1 hour\"\n\n  # Web fonts\n    ExpiresByType application/font-woff                 \"access plus 1 month\"\n    ExpiresByType application/vnd.ms-fontobject         \"access plus 1 month\"\n    ExpiresByType application/x-font-ttf                \"access plus 1 month\"\n    ExpiresByType font/opentype                         \"access plus 1 month\"\n    ExpiresByType image/svg+xml                         \"access plus 1 month\"\n\n</IfModule>\n\n# ------------------------------------------------------------------------------\n# | Filename-based cache busting                                               |\n# ------------------------------------------------------------------------------\n\n# If you're not using a build process to manage your filename version revving,\n# you might want to consider enabling the following directives to route all\n# requests such as `/css/style.12345.css` to `/css/style.css`.\n\n# To understand why this is important and a better idea than `*.css?v231`, read:\n# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring\n\n# <IfModule mod_rewrite.c>\n#    RewriteCond %{REQUEST_FILENAME} !-f\n#    RewriteRule ^(.+)\\.(\\d+)\\.(js|css|png|jpe?g|gif)$ $1.$3 [L]\n# </IfModule>\n\n# ------------------------------------------------------------------------------\n# | File concatenation                                                         |\n# ------------------------------------------------------------------------------\n\n# Allow concatenation from within specific style sheets and JavaScript files.\n\n# e.g.:\n#\n#   If you have the following content in a file\n#\n#       <!--#include file=\"libs/jquery.js\" -->\n#       <!--#include file=\"plugins/jquery.timer.js\" -->\n#\n#   Apache will replace it with the content from the specified files.\n\n# <IfModule mod_include.c>\n#    <FilesMatch \"\\.combined\\.js$\">\n#        Options +Includes\n#        AddOutputFilterByType INCLUDES application/javascript application/json\n#        SetOutputFilter INCLUDES\n#    </FilesMatch>\n#    <FilesMatch \"\\.combined\\.css$\">\n#        Options +Includes\n#        AddOutputFilterByType INCLUDES text/css\n#        SetOutputFilter INCLUDES\n#    </FilesMatch>\n# </IfModule>\n"
  },
  {
    "path": "app/404.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Page Not Found :(</title>\n        <style>\n            ::-moz-selection {\n                background: #b3d4fc;\n                text-shadow: none;\n            }\n\n            ::selection {\n                background: #b3d4fc;\n                text-shadow: none;\n            }\n\n            html {\n                padding: 30px 10px;\n                font-size: 20px;\n                line-height: 1.4;\n                color: #737373;\n                background: #f0f0f0;\n                -webkit-text-size-adjust: 100%;\n                -ms-text-size-adjust: 100%;\n            }\n\n            html,\n            input {\n                font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n            }\n\n            body {\n                max-width: 500px;\n                _width: 500px;\n                padding: 30px 20px 50px;\n                border: 1px solid #b3b3b3;\n                border-radius: 4px;\n                margin: 0 auto;\n                box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff;\n                background: #fcfcfc;\n            }\n\n            h1 {\n                margin: 0 10px;\n                font-size: 50px;\n                text-align: center;\n            }\n\n            h1 span {\n                color: #bbb;\n            }\n\n            h3 {\n                margin: 1.5em 0 0.5em;\n            }\n\n            p {\n                margin: 1em 0;\n            }\n\n            ul {\n                padding: 0 0 0 40px;\n                margin: 1em 0;\n            }\n\n            .container {\n                max-width: 380px;\n                _width: 380px;\n                margin: 0 auto;\n            }\n\n            input::-moz-focus-inner {\n                padding: 0;\n                border: 0;\n            }\n        </style>\n    </head>\n    <body>\n        <div class=\"container\">\n            <h1>Not found <span>:(</span></h1>\n            <p>Sorry, but the page you were trying to view does not exist.</p>\n            <p>It looks like this was the result of either:</p>\n            <ul>\n                <li>a mistyped address</li>\n                <li>an out-of-date link</li>\n            </ul>\n        </div>\n    </body>\n</html>\n"
  },
  {
    "path": "app/config.xml",
    "content": "<?xml version='1.0' encoding='utf-8'?>\n<widget id=\"cc.laverna.app\" version=\"{{version}}\" xmlns=\"http://www.w3.org/ns/widgets\"\n    xmlns:cdv=\"http://cordova.apache.org/ns/1.0\">\n    <name>Laverna</name>\n    <description>\n        Open source note taking application\n    </description>\n    <author email=\"lavernaproject@gmail.com\" href=\"http://laverna.cc\">\n        Laverna project\n    </author>\n    <content src=\"index.html\" />\n    <plugin name=\"cordova-plugin-whitelist\" spec=\"1\" />\n    <access origin=\"*\" />\n    <allow-intent href=\"http://*/*\" />\n    <allow-intent href=\"https://*/*\" />\n    <allow-intent href=\"tel:*\" />\n    <allow-intent href=\"sms:*\" />\n    <allow-intent href=\"mailto:*\" />\n    <allow-intent href=\"geo:*\" />\n    <icon src=\"www/images/icon/icon.png\" />\n    <platform name=\"android\">\n        <allow-intent href=\"market:*\" />\n    </platform>\n    <platform name=\"ios\">\n        <allow-intent href=\"itms:*\" />\n        <allow-intent href=\"itms-apps:*\" />\n    </platform>\n</widget>\n"
  },
  {
    "path": "app/docs/howto.md",
    "content": "### How to use tags in Laverna\n\nWhen you are editing or creating notes, write \"@\" before any word to create a tag.\n\n### How to use tasks in Laverna\n\nYou can create tasks by prefacing line with [ ] or [x] (incomplete or complete, respectively). Tasks will be automatically rendered as checkboxes that you can check on and off.\n"
  },
  {
    "path": "app/dropbox.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <script src=\"bower_components/dropbox/dropbox.js\"></script>\n    <script type=\"text/javascript\">\n        Dropbox.AuthDriver.Popup.oauthReceiver();\n    </script>\n</head>\n<body>\n</body>\n</html>\n"
  },
  {
    "path": "app/index.html",
    "content": "<!DOCTYPE html>\n<html class=\"no-js\">\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n        <title>Laverna</title>\n        <meta name=\"description\" content=\"Note taking web app with markdown support\">\n        <meta name=\"format-detection\" content=\"telephone=no\">\n        <meta name=\"viewport\" content=\"user-scalable=no, initial-scale=1,\n                maximum-scale=1, minimum-scale=1, width=device-width, height=device-height\">\n        <!-- Add to home screen support (Safari) -->\n        <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n        <link rel=\"apple-touch-icon\" href=\"images/icon/icon-76x76.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"images/icon/icon-76x76.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"images/icon/icon-120x120.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"images/icon/icon-152x152.png\">\n        <!-- Add to home screen support (Android) -->\n        <meta name=\"mobile-web-app-capable\" content=\"yes\">\n        <link rel=\"icon\" sizes=\"36x36\" href=\"images/icon/icon-36x36.png\">\n        <link rel=\"icon\" sizes=\"72x72\" href=\"images/icon/icon-72x72.png\">\n        <link rel=\"icon\" sizes=\"48x48\" href=\"images/icon/icon-48x48.png\">\n        <link rel=\"icon\" sizes=\"96x96\" href=\"images/icon/icon-96x96.png\">\n        <!-- Microsoft Windows 8 & Phone start screen tile support -->\n        <meta name=\"application-name\" content=\"Laverna\" />\n        <meta name=\"msapplication-TileColor\" content=\"#006C60\" />\n        <meta name=\"msapplication-square70x70logo\" content=\"images/icon/icon-70x70.png\" />\n        <meta name=\"msapplication-square150x150logo\" content=\"images/icon/icon-150x150.png\" />\n        <meta name=\"msapplication-wide310x150logo\" content=\"images/icon/icon-310x150.png\" />\n        <meta name=\"msapplication-square310x310logo\" content=\"images/icon/icon-310x310.png\" />\n\n        <link rel=\"stylesheet\" href=\"styles/core.css\">\n        <link rel=\"stylesheet\" href=\"styles/theme-default.css\">\n\n\n        <script src=\"bower_components/modernizr/modernizr.js\"></script>\n    </head>\n    <body class=\"-noscroll\">\n\n        <div class=\"layout\" id=\"wrapper\">\n            <div id=\"sidebar\" class=\"layout--sidebar\">\n                <div id=\"sidebar--navbar\" class=\"layout--navbar\"></div>\n                <div id=\"sidebar--content\" class=\"layout--body -scroll\"></div>\n            </div>\n\n            <div id=\"content\" class=\"layout--content hidden-xs\"></div>\n\n            <div id=\"layout--backdrop\" class=\"layout--backdrop\"></div>\n\n            <div id=\"layout--brand\" class=\"layout--brand -loading\">\n\t\t\t\t<div id=\"loading--circle\" class=\"loading--circle -loading\">\n\t\t\t\t\t<div></div>\n\t\t\t \t</div>\n            </div>\n        </div>\n\n        <!-- Modal -->\n        <div id=\"modal\"></div>\n\n        <!--[if lt IE 7]>\n            <p class=\"chromeframe\">You are using an <strong>outdated</strong> browser. Please <a href=\"http://browsehappy.com/\">upgrade your browser</a> or <a href=\"http://www.google.com/chromeframe/?redirect=true\">activate Google Chrome Frame</a> to improve your experience.</p>\n        <![endif]-->\n\n        <!-- {{cordova}} -->\n        <script data-main=\"scripts/main\" src=\"bower_components/requirejs/require.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "app/locales/ar/translation.json",
    "content": "{\n    \"Search\": \"بحث\",\n    \"All notes\": \"كل الملاحظات\",\n    \"Favourites\": \"المفضلة\",\n    \"Favorite\": \"مفضلة\",\n    \"Trash\": \"سلة المهملات\",\n    \"Open tasks\": \"فتح المهام\",\n    \"Notebooks\": \"دفاتر الملاحظات\",\n    \"Settings\": \"الإعدادات\",\n    \"About\": \"حول\",\n    \"Save\": \"حفظ\",\n    \"Save & Exit\": \"حفظ وخروج\",\n    \"Cancel\": \"إلغاء\",\n    \"Full screen\": \"كامل الشاشة\",\n    \"Preview\": \"معاينة\",\n    \"Normal\": \"عادي\",\n    \"Select notebook\": \"إختر دفتر\",\n    \"Title\": \"العنوان\",\n    \"Submit\": \"أرسل\",\n    \"Tags\": \"علامات\",\n    \"Tag\": \"علامة\",\n    \"Parent\": \"والد\",\n    \"Root\": \"جذر\",\n    \"Notebooks & tags\": \"الدفاتر والعلامات\",\n    \"Notebook\": \"دفتر\",\n    \"Restore\": \"استعادة\",\n    \"Delete\": \"حذف\",\n    \"New tag\": \"علامة جديدة\",\n    \"Edit\": \"تحرير\",\n    \"Remove\": \"إزالة\",\n    \"Forever\": \"للأبد\",\n    \"No\": \"لا\",\n    \"Yes\": \"نعم\",\n    \"Basic\": \"أساسي\",\n    \"Cloud storage\": \"تخزين سحابي\",\n    \"Notes per page\": \"الملاحظات في الصفحة\",\n    \"Sort notebooks\": \"رتّب الملاحظات\",\n    \"Name\": \"الاسم\",\n    \"Created\": \"أنشأ\",\n    \"Default edit mode\": \"وضع التحرير الافتراضي\",\n    \"Fullscreen with preview\": \"معاينة على كامل الشاشة\",\n    \"Use encryption\": \"استخدم التشفير\",\n    \"Encryption parameters\": \"موسطات التشفير\",\n    \"Encryption Password\": \"كلمة سر التشفير\",\n    \"Salt\": \"حافظ\",\n    \"Random\": \"عشوائي\",\n    \"Key size\": \"حجم المفتاح\",\n    \"Strengthen by a factor of\": \"تقوية بمعامل مقداره هو\",\n    \"Authentication strength\": \"قوة الاستيثاق\",\n    \"Unlock\": \"فك\",\n    \"Your new encryption password\": \"كلمة سر التشفير الجديدة لك هي\",\n    \"Your old encryption password\": \"كلمة سر التشفير السابقة هي\",\n    \"Show sidebar\": \"عرض الشريط الجانبي\",\n    \"Previous\": \"السابقة\",\n    \"Next\": \"التالية\",\n    \"Navigation\": \"استعراض\",\n    \"navigateTop\": \"أعلى\",\n    \"navigateBottom\": \"أسفل\",\n    \"Jump\": \"قفز\",\n    \"jumpInbox\": \"إذهب لصندوق الوارد\",\n    \"jumpNotebook\": \"إذهب إلى قائمة الدفاتر\",\n    \"jumpFavorite\": \"إذهب إلى الملاحظات المفضلة\",\n    \"jumpRemoved\": \"إذهب إلى الملاحظات الزائلة\",\n    \"jumpOpenTasks\": \"إذهب إلى الملاحظات ذات المهام القائمة\",\n    \"Actions\": \"إجراءات\",\n    \"actionsEdit\": \"تحرير\",\n    \"actionsOpen\": \"فتح\",\n    \"actionsRemove\": \"إزالة\",\n    \"actionsRotateStar\": \"دوّر النجمة\",\n    \"App\": \"التطبيق\",\n    \"appCreateNote\": \"أنشئ ملاحظة جديدة\",\n    \"appSearch\": \"إبحث عن ملاحظة\",\n    \"appKeyboardHelp\": \"مساعدة لوحة المفاتيح\",\n    \"Change keybindings\": \"تغيير إعدادات ارتباطات المفاتيح\",\n    \"Donate\": \"تبرَّع\",\n    \"Github page\": \"Github صفحة\",\n    \"Report bugs and issues here\": \"أخبرنا عن العلل والمشكلات هنا\",\n    \"Report bugs through email\": \"أبلغ عن العلل بالبريد\",\n    \"Credits\": \"شكر وتقدير\",\n    \"List of contributors\": \"قائمة المساهمين\",\n    \"List of all used libraries\": \"قائمة جميع المكتبات المستخدمة\",\n    \"Are you sure?\": \"هل أنت متأكد؟\",\n    \"You have unsaved changes\": \"لديك تغييرات لم تحفظها.\",\n    \"Dropbox API key\": \"مفتاح API لـ Dropbox\",\n    \"Required\": \"مطلوب\",\n    \"Optional\": \"اختياري\",\n    \"Language\": \"اللغة\",\n    \"Action\": \"الإجراء\",\n    \"Select\": \"اختر\",\n    \"General\": \"عامّ\",\n    \"Encryption\": \"التشفير\",\n    \"Keybindings\": \"ارتباطات المفاتيح\",\n    \"Sync\": \"مزامنة\",\n    \"Profiles\": \"ملفات التعريف\",\n    \"Import\": \"استيراد\",\n    \"Transfer data\": \"استيراد وتصدير\",\n    \"Import settings\": \"استيراد الإعدادات\",\n    \"Export settings\": \"تصدير الإعدادات\",\n    \"Wrong format\": \"صيغة خطأ\",\n    \"useDefaultConfigs\": \"استخدم الإعدادات من ملف التعريف الافتراضي\",\n    \"File chould be in json format\": \"يجب أن يكون الملف بصيغة JSON\",\n    \"Close\": \"إغلاق\",\n    \"Hyperlink\": \"ارتباط تشعبي\",\n    \"Editor\": \"المحرر\",\n    \"Preview\": \"معاينة\",\n    \"Download\": \"سحب\",\n    \"Transfer everything\": \"كل شيء\",\n    \"encryption\": {\n        \"wait\": \"فضلاً انتظر حتى يكتمل التشفير\",\n        \"error\": \"خطأ في التشفير\",\n        \"errorConfirm\": \"خطأ أثناء فك تشفير البيانات. \\r\\r **حدّث إعداداتك** في هذا المستعرض كذلك إن كنت قد غيّرت إعدادات التشفير في مستعرض آخر ، أو حاول استيراد الإعدادات.  \\r\\r وإن كنت لم تغيّر شيئاً **حاول الدخول** مرة ثانية.\",\n        \"errorConfirmSettings\": \"غيّر إعدادات التشفير\",\n        \"errorConfirmAuth\": \"أعد المحاولة\",\n        \"backup\": {\n            \"title\": \"نسخ احتياطي للبيانات\",\n            \"content\": \"قبل الاستمرار للخطوة التالية ، فضلاً اسحب ملف النسخة الاحتياطية.  يحتوي الملف بيانات ملفات التعريف المتغيرة السابقة دون تشفير. إحفظه في مكان آمن.\",\n            \"next\": \"واصل دون سحب ملف النسخة الاحتياطية\"\n        },\n        \"state\": {\n            \"decrypt\": \"جار فك تشفير كل شيء\",\n            \"encrypt\": \"تشفير كل شيء\",\n            \"save\": \"جار حفظ التغييرات\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"سيتم حذف ملف التعريف **{{profile}}** بجميع البيانات بما فيها الملاحظات والعلامات والدفاتر‫.‬  هذا الإجراء لا يمكن التراجع عنه‫!‬\",\n        \"type name\": \"اكتب اسم الملف التعريفي\"\n    },\n    \"files\": {\n        \"file-url\": \"عنوان URL للملف أو الصورة\",\n        \"attach\": \"أرفق ملفاً\",\n        \"attachLink\": \"أرفق رابطاً\",\n        \"attachImage\": \"أرفق صورة\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"سيتم نقل الملاحظة **{{title}}** إلى سلة المهملات.\",\n        \"confirm remove\": \"سيتم حذف الملاحظة **{{title}}** ‫**‬إلى الأبد‫**‬!\",\n        \"create and attach\": \"أنشئ ملاحظة جديدة وأرفق هذا الرابط\",\n        \"create\": \"أنشئ ملاحظة جديدة\",\n        \"hyperlink-dialog\": \"عنوان الملاحظة أو العنوان URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"إختر دفتر ملاحظات\",\n        \"add\": \"أضف دفتر ملاحظات جديد\",\n        \"edit\": \"عدّل دفتر ملاحظات\",\n        \"name\": \"فضلاً أكتب اسماً لهذا الدفتر\",\n        \"confirm remove\": \"الدفتر **{{name}}** سيتم حذفه ‫**‬إلى الأبد‫**‬!\",\n        \"remove with notes\": \"نعم إحذفه والملاحظات المرفقة\",\n        \"remove\": \"نعم إحذفه\"\n    },\n    \"tags\": {\n        \"name\": \"اسم العلامة مطلوب\",\n        \"add\": \"أضف علامة جديدة\",\n        \"edit\": \"حرّر علامة\",\n        \"confirm remove\": \"العلامة **{{name}}** سيتم حذفها ‫**‬إلى الأبد‫**‬!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"ستتم إعادة التوجيه الآن إلى صفحة تسجيل الدخول في **Dropbox**.\\r> فضلاً إضغط زر **OK**.\",\n        \"auth title\": \"Dropbox auth\",\n        \"api info 1\": \"يمكنك إنشاء مفتاح API الخاص بك على\",\n        \"api info 2\": \"ضع في حسبانك أنك عندما تنشئ تطبيقاً جديداً على موقع المطورين على Dropbox فإن :\",\n        \"api info li 1\": \"نوع التطبيق يجب أن يكون Dropbox API app\",\n        \"api info li 2\": \"نوع البيانات يجب أن يكون Files and datastores\"\n    },\n    \"help\": {\n        \"firststart title\": \"مرحباً بك في Laverna\",\n        \"firststart import\": \"إن كنت قد استخدمت Laverna من قبل فيمكنك استيراد إعداداتك السابقة بالضغط على زر 'استيراد' أدناه\",\n        \"firststart next\": \"إذا لم تستخدم Laverna من قبل فاضغط على زر 'التالي' لبدء عملية التثبيت\",\n        \"firststart encryption\": \"إن كنت ترغب استعمال التشفير فاكتب كلمة سر التشفير‫.‬\",\n        \"firststart sync\": \"تحتاج لتمكين المزامنة مع أحد المحوّلات لتتمكن من عرض ملاحظاتك على أجهزة أخرى ، نظراً لأننا لانخزّن أي بيانات على خوادمنا.\",\n        \"firststart backup\": \"لقد اكتمل كل شيء تقريباً‫.‬  يمكنك سحب نسخة احتياطية من إعداداتك والتقدم للخطوة التالية.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/bs_ba/translation.json",
    "content": "﻿{\n    \"en\" : \"Engleski\",\n    \"ru\" : \"Ruski\",\n    \"nl\" : \"Holandski\",\n    \"fr\" : \"Francuski\",\n    \"pt_br\" : \"Portugalski (Brazil)\",\n    \"eo\": \"Esperanto\",\n    \"es\": \"Španski\",\n    \"de\": \"Njemački\",\n    \"se\": \"Švedski\",\n    \"el\": \"Grčki\",\n    \"nb\": \"Norveški (Bokmal)\",\n    \"nn\": \"Norveški (Nynorsk)\",\n    \"Search\": \"Pretraži\",\n    \"All notes\": \"Sve bilješke\",\n    \"Favourites\": \"Omiljene\",\n    \"Favorite\": \"Stavi u omiljene\",\n    \"Trash\": \"Smeće\",\n    \"Notebooks\": \"Bilježnice\",\n    \"Settings\": \"Postavke\",\n    \"About\": \"O programu\",\n    \"Save\": \"Sačuvaj\",\n    \"Save & Exit\": \"Sačuvaj i izađi\",\n    \"Cancel\": \"Odustani\",\n    \"Full screen\": \"Preko cijelog ekrana\",\n    \"Preview\": \"Pregledaj\",\n    \"Normal\": \"Normalno\",\n    \"Select notebook\": \"Izaberi bilježnicu\",\n    \"Title\": \"Naslov\",\n    \"Submit\": \"Pošalji\",\n    \"Tags\": \"Tagovi\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Roditelj\",\n    \"Root\": \"Glavna bilježnica\",\n    \"Notebooks & tags\": \"Bilježnice i tagovi\",\n    \"Notebook\": \"Bilježnica\",\n    \"Restore\": \"Vrati prethodno\",\n    \"Delete\": \"Izbriši\",\n    \"New tag\": \"Novi tag\",\n    \"Edit\": \"Uredi\",\n    \"Remove\": \"Ukloni\",\n    \"Forever\": \"Zauvijek\",\n    \"No\": \"Ne\",\n    \"Yes\": \"Da\",\n    \"Basic\": \"Osnovno\",\n    \"Cloud storage\": \"Cloud smještaj\",\n    \"Notes per page\": \"Bilješki po stranici\",\n    \"Sort notebooks\": \"Sortiraj bilježnice\",\n    \"Name\": \"Ime\",\n    \"Created\": \"Kreirano\",\n    \"Default edit mode\": \"Podrazumijevana forma za uređivanje\",\n    \"Fullscreen with preview\": \"Preko cijelog ekrana sa prikazom\",\n    \"Use encryption\": \"Koristi enkripciju\",\n    \"Encryption parameters\": \"Enkripcijski parametri\",\n    \"Encryption Password\": \"Enkripcijska šifra\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Nasumično\",\n    \"Key size\": \"Veličina ključa\",\n    \"Strengthen by a factor of\": \"Pojačana sa faktorom od\",\n    \"Authentication strength\": \"Snaga autentifikacije\",\n    \"Unlock\": \"Otključaj\",\n    \"Your new encryption password\": \"Vaša nova šifra za enkripciju\",\n    \"Your old encryption password\": \"Vaša stara šifra za enkripciju\",\n    \"Please wait until the encryption will be completed\": \"Molimo sačekajte dok enkripcija ne bude završena.\",\n    \"Shortcuts\": \"Prečice\",\n    \"Newer\": \"Novije\",\n    \"Older\": \"Starije\",\n    \"Navigation\": \"Navigacije\",\n    \"navigateTop\": \"Vrh\",\n    \"navigateBottom\": \"Dno\",\n    \"Jump\": \"Skoči\",\n    \"jumpInbox\": \"Idi u inboks\",\n    \"jumpNotebook\": \"Prikaži listu bilježnica\",\n    \"jumpFavorite\": \"Prikaži omiljene bilješke\",\n    \"jumpRemoved\": \"Prikaži obrisane bilješke\",\n    \"Actions\": \"Akcije\",\n    \"actionsEdit\": \"Uredi\",\n    \"actionsOpen\": \"Otvori\",\n    \"actionsRemove\": \"Izbriši\",\n    \"actionsRotateStar\": \"Rotacijska zvijezda\",\n    \"App\": \"Aplikacija\",\n    \"appCreateNote\": \"Kreiraj novu bilješku\",\n    \"appSearch\": \"Pretraži unutar bilješke\",\n    \"appKeyboardHelp\": \"Pomoć oko tastature\"\n}"
  },
  {
    "path": "app/locales/da/translation.json",
    "content": "{\n    \"Search\": \"Søg\",\n    \"All notes\": \"Alle noter\",\n    \"Favourites\": \"Foretrukne\",\n    \"Favorite\": \"Favorit\",\n    \"Trash\": \"Papirkurv\",\n    \"Open tasks\": \"Åbne opgaver\",\n    \"Notebooks\": \"Notesbøger\",\n    \"Settings\": \"Indstillinger\",\n    \"About\": \"Om\",\n    \"Save\": \"Gem\",\n    \"Save & Exit\": \"Gem & Luk\",\n    \"Cancel\": \"Annuler\",\n    \"Full screen\": \"Fuld skærm\",\n    \"Preview\": \"Smugkig\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Vælg notesbog\",\n    \"Title\": \"Titel\",\n    \"Submit\": \"Indsend\",\n    \"Tags\": \"Tags\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Overliggende\",\n    \"Root\": \"Rod\",\n    \"Notebooks & tags\": \"Notesbøger & tags\",\n    \"Notebook\": \"Notesbog\",\n    \"Restore\": \"Gendan\",\n    \"Delete\": \"Slet\",\n    \"New tag\": \"Nyt tag\",\n    \"Edit\": \"Rediger\",\n    \"Remove\": \"Fjern\",\n    \"Forever\": \"Permanent\",\n    \"No\": \"Nej\",\n    \"Yes\": \"Ja\",\n    \"Basic\": \"Basalt\",\n    \"Cloud storage\": \"Opbevaring i Skyen\",\n    \"Notes per page\": \"Noter per side\",\n    \"Sort notebooks\": \"Sortér notesbøger\",\n    \"Name\": \"Navn\",\n    \"Created\": \"Oprettet\",\n    \"Default edit mode\": \"Standard redigeringsvindue\",\n    \"Fullscreen with preview\": \"Fuldskærm med smugkig\",\n    \"Use encryption\": \"Benyt kryptering\",\n    \"Encryption parameters\": \"Krypteringsparametre\",\n    \"Encryption Password\": \"Krypteringskodeord\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Tilfældig\",\n    \"Key size\": \"Nøglestørrelse\",\n    \"Strengthen by a factor of\": \"Styrk med en faktor på\",\n    \"Authentication strength\": \"Autentifikationsstyrke\",\n    \"Unlock\": \"Lås op\",\n    \"Your new encryption password\": \"Dit nye krypteringskodeord\",\n    \"Your old encryption password\": \"Dit gamle krypteringskodeord\",\n    \"Show sidebar\": \"Vis sidepanelet\",\n    \"Previous\": \"Forrige\",\n    \"Next\": \"Næste\",\n    \"Navigation\": \"Navigation\",\n    \"navigateTop\": \"Top\",\n    \"navigateBottom\": \"Bund\",\n    \"Jump\": \"Hop\",\n    \"jumpInbox\": \"Vis indbakken\",\n    \"jumpNotebook\": \"Åben liste over notesbøger\",\n    \"jumpFavorite\": \"Vis foretrukne noter\",\n    \"jumpRemoved\": \"Vis slettede noter\",\n    \"jumpOpenTasks\": \"Vis noter med ufærdige opgaver\",\n    \"Actions\": \"Handlinger\",\n    \"actionsEdit\": \"Rediger\",\n    \"actionsOpen\": \"Åben\",\n    \"actionsRemove\": \"Fjern\",\n    \"actionsRotateStar\": \"Rotér stjerne\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Opret ny note\",\n    \"appSearch\": \"Søg efter note\",\n    \"appKeyboardHelp\": \"Tastaturhjælp\",\n    \"Change keybindings\": \"Ændre indstillinger for genvejstaster\",\n    \"Donate\": \"Donér\",\n    \"Github page\": \"Github side\",\n    \"Report bugs and issues here\": \"Anmeld fejl og problemer her\",\n    \"Report bugs through email\": \"Anmeld fejl via email\",\n    \"Credits\": \"Anerkendelse\",\n    \"List of contributors\": \"Liste over bidragsydere\",\n    \"List of all used libraries\": \"Liste over alle benyttede libraries\",\n    \"Are you sure?\": \"Er du helt sikker?\",\n    \"You have unsaved changes\": \"Du har ikke-gemte ændringer.\",\n    \"Dropbox API key\": \"Dropbox API nøgle\",\n    \"Required\": \"Obligatorisk\",\n    \"Optional\": \"Valgfrit\",\n    \"Language\": \"Sprog\",\n    \"Action\": \"Handling\",\n    \"Select\": \"Vælg\",\n    \"General\": \"Generelt\",\n    \"Encryption\": \"Kryptering\",\n    \"Keybindings\": \"Genvejstaster\",\n    \"Sync\": \"Synkronisering\",\n    \"Profiles\": \"Profiler\",\n    \"Import\": \"Importer\",\n    \"Transfer data\": \"Overfør data\",\n    \"Import settings\": \"Importer indstillinger\",\n    \"Export settings\": \"Eksporter indstillinger\",\n    \"Wrong format\": \"Forkert format\",\n    \"useDefaultConfigs\": \"Brug standardinstillingerne\",\n    \"File chould be in json format\": \"Filen kunne være i json format\",\n    \"Close\": \"Luk\",\n    \"Hyperlink\": \"Hyperink\",\n    \"Editor\": \"Redigeringsvindue\",\n    \"Preview\": \"Smugkig\",\n    \"Download\": \"Download\",\n    \"Transfer everything\": \"Overfør alt\",\n    \"encryption\": {\n        \"wait\": \"Vent venligst indtil krypteringen er fuldført\",\n        \"error\": \"Krypteringsfejl\",\n        \"errorConfirm\": \"Der opstod en fejl under dekrypteringen.\\r\\r Hvis du har foretaget ændringer af krypteringen i en anden browser, **skal du også ændre dette** i denne browser. Eller forsøge at importere disse ændringer.\\r\\r Hvis du ikke har foretaget nogen ændringer, kan du **forsøge logge ind** igen.\",\n        \"errorConfirmSettings\": \"Ændre indstillinger for kryptering\",\n        \"errorConfirmAuth\": \"Prøv igen\",\n        \"backup\": {\n            \"title\": \"Foretag backup af data\",\n            \"content\": \"Vær venlig at downloade din backup-fil, før du forsætter. Denne indeholder dekrypteret tidligere data fra tidligere profiler. Opbevar denne et sikkert sted.\",\n            \"next\": \"Fortsæt uden at downloade en backup-fil\"\n        },\n        \"state\": {\n            \"decrypt\": \"Dekrypter alt\",\n            \"encrypt\": \"Krypter alt\",\n            \"save\": \"Gem ændringerne\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"Al data for profilen **{{profile}}** , såsom noter, tags og notesbøger, vil blive fjernet permanent !\",\n        \"type name\": \"Skriv profilnavn\"\n    },\n    \"files\": {\n        \"file-url\": \"Sti til fil eller billede\",\n        \"attach\": \"Vedhæft en fil\",\n        \"attachLink\": \"Vedhæft som et link\",\n        \"attachImage\": \"Vedhæft som et billede\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Noten **{{title}}** vil blive flyttet til papirskurven.\",\n        \"confirm remove\": \"Noten **{{title}}** vil blive **fjernet permanent**!\",\n        \"create and attach\": \"Opret en ny note og vedhæft denne som et link\",\n        \"create\": \"Opret en ny note\",\n        \"hyperlink-dialog\": \"Titel på note eller sti\"\n    },\n    \"notebooks\": {\n        \"select\": \"Vælg en notesbog\",\n        \"add\": \"Tilføj en ny notesbog\",\n        \"edit\": \"Rediger notesbog\",\n        \"name\": \"Angiv venligst et navn for notesbogen\",\n        \"confirm remove\": \"Notesbogen **{{name}}** vil blive **fjernet permanent**!\",\n        \"remove with notes\": \"Ja, fjern med vedhæftede noter\",\n        \"remove\": \"Ja tak, fjern\"\n    },\n    \"tags\": {\n        \"name\": \"Tagnavn er obligatorisk\",\n        \"add\": \"Tilføj et nyt tag\",\n        \"edit\": \"Rediger et tag\",\n        \"confirm remove\": \"Tagget **{{name}}** vil blive **fjernet permanent**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Du vil nu blive omdirigeret til **Dropboxs** autentifikationsside.\\r> Klik venligst på **OK**.\",\n        \"auth title\": \"Dropbox auth\",\n        \"api info 1\": \"Du kan angive din egen API nøgle\",\n        \"api info 2\": \"Bemærk følgende når du opretter en ny app på Dropboxs side for udviklere:\",\n        \"api info li 1\": \"Applikationstypen skal være Dropbox API app\",\n        \"api info li 2\": \"Datatypen skal være Files and datastores\"\n    },\n    \"help\": {\n        \"firststart title\": \"Velkommen til Laverna\",\n        \"firststart import\": \"Hvis du har benyttet Laverna før, kan du importere dine data på knappen 'importer'.\",\n        \"firststart next\": \"Hvis ikke du har benyttet dig af Laverna før, kan du gå videre ved at trykke på knappen 'næste' og starte installationen.\",\n        \"firststart encryption\": \"Hvis du vil kryptere dit indhold angiv venligst et kodeord.\",\n        \"firststart sync\": \"Da vi ikke lagre nogen form for data på vores servere, skal du benytte en af synkroniseringstjenesterne for at se dit indhold på dine andre enheder.\",\n        \"firststart backup\": \"Alt er næsten klart. Du kan derfor downloade en backup af dine indstillinger og forsætte til det sidste trin.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/de/translation.json",
    "content": "{\n    \"Search\": \"Suchen\",\n    \"All notes\": \"Alle Notizen\",\n    \"Favourites\": \"Favoriten\",\n    \"Favorite\": \"Favorit\",\n    \"Trash\": \"Mülleimer\",\n    \"Open tasks\": \"Offene Aufgaben\",\n    \"Notebooks\": \"Notizbücher\",\n    \"Settings\": \"Einstellungen\",\n    \"About\": \"Über\",\n    \"Save\": \"Speichern\",\n    \"Save & Exit\": \"Speichern & Schließen\",\n    \"Cancel\": \"Abbrechen\",\n    \"Full screen\": \"Vollbild\",\n    \"Preview\": \"Vorschau\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Notizbuch wählen\",\n    \"Title\": \"Titel\",\n    \"Submit\": \"Senden\",\n    \"Tags\": \"Tags\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Elternelement\",\n    \"Root\": \"Hauptverzeichnis\",\n    \"Notebooks & tags\": \"Notizbücher & Tags\",\n    \"Notebook\": \"Notizbuch\",\n    \"Restore\": \"Wiederherstellen\",\n    \"Delete\": \"Löschen\",\n    \"New tag\": \"Neuer Tag\",\n    \"Edit\": \"Bearbeiten\",\n    \"Remove\": \"Entfernen\",\n    \"Forever\": \"Endgültig\",\n    \"No\": \"Nein\",\n    \"Yes\": \"Ja\",\n    \"Basic\": \"Basis\",\n    \"Cloud storage\": \"Cloudspeicher\",\n    \"Notes per page\": \"Notizen pro Seite\",\n    \"Sort notebooks\": \"Notizbücher sortieren nach\",\n    \"Name\": \"Name\",\n    \"Created\": \"Erstellungsdatum\",\n    \"Default edit mode\": \"Standard Bearbeitungsmodus\",\n    \"Fullscreen with preview\": \"Vollbild mit Vorschau\",\n    \"Use encryption\": \"Verschlüsselung verwenden\",\n    \"Encryption parameters\": \"Verschlüsselungsparameter\",\n    \"Encryption Password\": \"Verschlüsselungspasswort\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Zufällig\",\n    \"Key size\": \"Schlüsselgröße\",\n    \"Strengthen by a factor of\": \"Verstärken um den Faktor\",\n    \"Authentication strength\": \"Verschlüsselungsstärke\",\n    \"Unlock\": \"Entsperren\",\n    \"Your new encryption password\": \"Ihr neues Verschlüsselungspasswort\",\n    \"Your old encryption password\": \"Ihr altes Verschlüsselungspasswort\",\n    \"Show sidebar\": \"Seitenleiste anzeigen\",\n    \"Previous\": \"Zurück\",\n    \"Next\": \"Weiter\",\n    \"Navigation\": \"Navigation\",\n    \"navigateTop\": \"Nach oben\",\n    \"navigateBottom\": \"Nach unten\",\n    \"Jump\": \"Springen\",\n    \"jumpInbox\": \"Zur Inbox\",\n    \"jumpNotebook\": \"Zur Notizbuchliste\",\n    \"jumpFavorite\": \"Zu den Favoriten\",\n    \"jumpRemoved\": \"Zu gelöschten Notizen\",\n    \"jumpOpenTasks\": \"Zu Notizen mit offenen Aufgaben\",\n    \"Actions\": \"Aktionen\",\n    \"actionsEdit\": \"Bearbeiten\",\n    \"actionsOpen\": \"Öffnen\",\n    \"actionsRemove\": \"Entfernen\",\n    \"actionsRotateStar\": \"Favorisieren\",\n    \"App\": \"Anwendung\",\n    \"appCreateNote\": \"Neue Notiz\",\n    \"appSearch\": \"Notizen Durchsuchen\",\n    \"appKeyboardHelp\": \"Tastaturhilfe\",\n    \"Change keybindings\": \"Tastenkürzel ändern\",\n    \"Donate\": \"Spenden\",\n    \"Github page\": \"Github Seite\",\n    \"Report bugs and issues here\": \"Fehler und Anregungen hier melden\",\n    \"Report bugs through email\": \"Fehler per E-Mail melden\",\n    \"Credits\": \"Danksagung\",\n    \"List of contributors\": \"Liste der Mitwirkenden\",\n    \"List of all used libraries\": \"Liste aller benutzten Bibliotheken\",\n    \"Are you sure?\": \"Sind Sie sicher?\",\n    \"You have unsaved changes\": \"Sie haben nicht gespeicherte Änderungen.\",\n    \"Dropbox API key\": \"Dropbox API Schlüssel\",\n    \"Required\": \"Erforderlich\",\n    \"Optional\": \"Optional\",\n    \"Language\": \"Sprache\",\n    \"Action\": \"Aktion\",\n    \"Select\": \"Wählen\",\n    \"General\": \"Allgemein\",\n    \"Encryption\": \"Verschlüsselung\",\n    \"Keybindings\": \"Tastenkürzel\",\n    \"Sync\": \"Synchronisierung\",\n    \"Profiles\": \"Profile\",\n    \"Import\": \"Importieren\",\n    \"Transfer data\": \"Übertrage Daten\",\n\t\"Transfer settings\": \"Übertrage Einstellungen\",\n    \"Import settings\": \"Importeinstellungen\",\n    \"Export settings\": \"Exporteinstellungen\",\n    \"Wrong format\": \"Falsches Format\",\n    \"useDefaultConfigs\": \"Einstellungen des Standardprofils verwenden\",\n    \"File chould be in json format\": \"Datei sollte im JSON-Format vorliegen\",\n    \"Close\": \"Schließen\",\n    \"Hyperlink\": \"Link\",\n    \"Editor\": \"Editor\",\n    \"Download\": \"Download\",\n    \"Transfer everything\": \"Übertrage alles\",\n    \"Other\": \"Sonstiges\",\n    \"Default\": \"Standard\",\n    \"Modules\": \"Module\",\n    \"Import data\": \"Importiere Daten\",\n    \"Export data\": \"Exportiere Daten\",\n    \"Enabled\": \"Aktiviert\",\n    \"Disabled\": \"Deaktiviert\",\n    \"Untitled\": \"Unbenannt\",\n    \"Line of\": \"Zeile {{currentLine}} von {{numberOfLines}}\",\n\t\"Drop files\": \"Ziehen Sie hier die Dateien zum Hochladen hinein\",\n\t\"Spaces per indent\": \"Leerzeichen pro Einrückung\",\n\t\"Sort notes\": \"Notizen sortieren nach\",\n\t\"Updated date\": \"Änderungsdatum\",\n\t\"Created date\": \"Erstelldatum\",\n\t\"Text editor\": \"Texteditor\",\n\t\"Vim\": \"Vim\",\n\t\"Emacs\": \"Emacs\",\n\t\"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"Bitte, geben Sie Ihr Passwort ein\",\n        \"change password\": \"Geben Sie Ihr Passwort hier ein, um es zu ändern\",\n        \"wait\": \"Bitte warten, bis die Verschlüsselung abgeschlossen ist\",\n        \"error\": \"Verschüsselungsfehler\",\n        \"errorConfirm\": \"Fehler beim Verschlüsseln der Daten.\\r\\r Falls Sie die Verschlüsselungseinstellungen in einem anderen Browser geändert haben, **ändern Sie die Einstellungen** auch in diesem Browser. Oder probieren Sie andere Einstellungen.\\r\\r Und falls Sie nichts verändert haben, versuchen Sie, sich **erneut anzumelden**.\",\n        \"errorConfirmSettings\": \"Verschlüsselungseinstellungen speichern\",\n        \"errorConfirmAuth\": \"Nochmal versuchen\",\n        \"backup\": {\n            \"title\": \"Datensicherung\",\n            \"content\": \"Bevor Sie fortfahren, laden Sie bitte Ihre Sicherungsdatei herunter. Sie enthält die entschlüsselten vorherigen Daten veränderter Profile. Verwahren Sie diese an einem sicheren Ort.\",\n            \"next\": \"Fortfahren, ohne die Sicherungsdatei herunterzuladen\"\n        },\n        \"state\": {\n            \"decrypt\": \"Alles entschlüsseln\",\n            \"encrypt\": \"Alles verschlüsseln\",\n            \"save\": \"Änderungen speichern\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"Profilname\",\n        \"confirm remove\": \"Das Profil **{{profile}}** wird mit allen Daten inklusive Notizen, Tags und Notizbüchern gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden!\",\n        \"type name\": \"Profilnamen eingeben\"\n    },\n    \"files\": {\n        \"file-url\": \"Datei oder Bild-URL\",\n        \"attach\": \"Datei anhängen\",\n        \"attachLink\": \"Als Link anhängen\",\n        \"attachImage\": \"Als Bild anhängen\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Die Notiz **{{title}}** wird in den Papierkorb verschoben.\",\n        \"confirm remove\": \"Die Notiz **{{title}}** wird **endgültig** gelöscht!\",\n        \"create and attach\": \"Neue Notiz erstellen und deren Link anhängen\",\n        \"create\": \"Neue Notiz erstellen\",\n        \"hyperlink-dialog\": \"Titel einer Notiz oder URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Notizbuch wählen\",\n        \"add\": \"Neues Notizbuch hinzufügen\",\n        \"edit\": \"Notizbuch bearbeiten\",\n        \"name\": \"Bitte geben Sie den Namen des Notizbuchs an\",\n        \"confirm remove\": \"Das Notizbuch **{{name}}** wird **endgültig** gelöscht!\",\n        \"remove with notes\": \"Ja, mit angehängten Notizen löschen\",\n        \"remove\": \"Ja, löschen\"\n    },\n    \"tags\": {\n        \"name\": \"Tagname ist erforderlich\",\n        \"add\": \"Neuen Tag hinzufügen\",\n        \"edit\": \"Tag bearbeiten\",\n        \"confirm remove\": \"Der Tag **{{name}}** wird **endgültig** gelöscht!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Sie werden jetzt zur **Dropbox** Autorisierungsseite umgeleitet.\\r> Bitte klicken Sie den **OK** Button.\",\n        \"auth title\": \"Dropbox auth\",\n        \"api info 1\": \"Sie können Ihren eigenen API-Schlüssel haben bei\",\n        \"api info 2\": \"Wenn Sie eine neue App auf der Entwicklerwebsite von Dropbox erstellen, beachten Sie, dass:\",\n        \"api info li 1\": \"der API-Typ \\\"Dropbox API\\\" sein sollte\",\n        \"api info li 2\": \"der \\\"type of access\\\" \\\"Full Dropbox\\\" sein sollte\"\n    },\n    \"help\": {\n        \"firststart title\": \"Willkommen zu Laverna\",\n        \"firststart import\": \"Wenn Sie Laverna bereits verwendet haben, können Sie Ihre alten Einstellungen über die 'Importieren' Schaltfläche unten importieren.\",\n        \"firststart next\": \"Wenn Sie Laverna noch nie benutzt haben, klicken Sie auf 'Weiter' um die Installation zu starten.\",\n        \"firststart encryption\": \"Falls Sie Verschlüsselung verwenden wollen, geben Sie bitte das Verschlüsselungspasswort an.\",\n        \"firststart sync\": \"Damit Sie Ihre Notizen auch auf anderen Geräten sehen können, müssen Sie die Synchronisierung mit einem Adapter aktivieren, da wir keine Daten auf unseren Servern speichern.\",\n        \"firststart backup\": \"Fast fertig. Sie können Ihre Einstellungen herunterladen und mit dem letzten Schritt fortfahren.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/de_ch/translation.json",
    "content": "{\n    \"en\" : \"Änglisch\",\n    \"ru\" : \"Russisch\",\n    \"nl\" : \"Dänisch\",\n    \"fr\" : \"Französisch\",\n    \"pt_br\" : \"Portugisich (Brasilien)\",\n    \"nb\" : \"Norwegisch (Bokmal)\",\n    \"nn\" : \"Norwegisch (Nynorsk)\",\n    \"ru\" : \"Russisch\",\n    \"eo\": \"Esperanto\",\n    \"es\": \"Spanisch\",\n    \"se\": \"Schwedisch\",\n    \"el\": \"Griechisch\",\n    \"bs_ba\": \"Bosnisch\",\n    \"hi_in\": \"Hindi\",\n    \"mr-in\": \"Marathi\",\n    \"Search\": \"Sueche\",\n    \"All notes\": \"Alli Notize\",\n    \"Favourites\": \"Favoritä\",\n    \"Favorite\": \"Favorit\",\n    \"Trash\": \"Abfall\",\n    \"Notebooks\": \"Notizbüecher\",\n    \"Settings\": \"Istellige\",\n    \"About\": \"Über uns\",\n    \"Save\": \"Speichere\",\n    \"Save & Exit\": \"Speichere & Zuemache\",\n    \"Cancel\": \"Abbräche\",\n    \"Full screen\": \"Full screen\",\n    \"Preview\": \"Vorschau\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Wähl es Notizbuech\",\n    \"Title\": \"Titel\",\n    \"Submit\": \"Sändä\",\n    \"Tags\": \"Tags\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Parent\",\n    \"Root\": \"Root\",\n    \"Notebooks & tags\": \"Notizbüecher & Tags\",\n    \"Notebook\": \"Notizbuech\",\n    \"Restore\": \"Wiederhärstelle\",\n    \"Delete\": \"Lösche\",\n    \"New tag\": \"Neue Tag\",\n    \"Edit\": \"Bearbeite\",\n    \"Remove\": \"Entfärne\",\n    \"Forever\": \"für immer\",\n    \"No\": \"Nei\",\n    \"Yes\": \"Jo\",\n    \"Basic\": \"Basis\",\n    \"Cloud storage\": \"Cloudspeicher\",\n    \"Notes per page\": \"Notize pro Site\",\n    \"Default edit mode\": \"Standard Bearbeitigsmodus\",\n    \"Fullscreen with preview\": \"Vollbid mit Vorschau\",\n    \"Use encryption\": \"Bruch Verschlüsselig\",\n    \"Encryption parameters\": \"Verschlüsseligsparameter\",\n    \"Encryption Password\": \"Verschlüsseligspasswort\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Zufällig\",\n    \"Key size\": \"Schlüsselgrössi\",\n    \"Strengthen by a factor of\": \"Verstärkt um de Faktor\",\n    \"Authentication strength\": \"Verschlüsseligsstärke\",\n    \"Unlock\": \"Entsperre\",\n    \"Your new encryption password\": \"Dis neue Verschlüsseligspasswort\",\n    \"Your old encryption password\": \"Dis alte Verschlüsseligspasswort\",\n    \"Please wait until the encryption will be completed\": \"Bitte wart bis d Verschlüsselig beändet isch\",\n    \"Shortcuts\": \"Shortcuts\",\n    \"Newer\": \"Zrugg\",\n    \"Older\": \"Vorwärts\",\n    \"Navigation\": \"Navigation\",\n    \"navigateTop\": \"nach oobe\",\n    \"navigateBottom\": \"nach unde\",\n    \"Jump\": \"gumpe\",\n    \"jumpInbox\": \"Gang zur Inbox\",\n    \"jumpNotebook\": \"Gang zur Notizbüecherliste\",\n    \"jumpFavorite\": \"Gang zu de favorisierte Notize\",\n    \"jumpRemoved\": \"Gang zu de glöschten Notize\",\n    \"Actions\": \"Aktione\",\n    \"actionsOpen\": \"Öffne\",\n    \"actionsRotateStar\": \"Stern dreie\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Erstell e neui Notiz\",\n    \"appSearch\": \"Durchsuch d Notizen\",\n    \"appKeyboardHelp\": \"Tastatur Hilfe\"\n}\n\n"
  },
  {
    "path": "app/locales/el/translation.json",
    "content": "{\n    \"en\" : \"Αγγλικά\",\n    \"ru\" : \"Ρώσικα\",\n    \"nl\" : \"Ολλανδικά\",\n    \"fr\" : \"Γαλλικά\",\n    \"pt_br\" : \"Πορτογαλικά Βραζιλίας\",\n    \"Search\": \"Εύρεση\",\n    \"All notes\": \"Όλες οι σημειώσεις\",\n    \"Favourites\": \"Αγαπημένα\",\n    \"Favorite\": \"Αγαπημένο\",\n    \"Trash\": \"Κάδος\",\n    \"Notebooks\": \"Τετράδιο\",\n    \"Settings\": \"Ρυθμίσεις\",\n    \"About\": \"Περί\",\n    \"Save\": \"Αποθήκευση\",\n    \"Save & Exit\": \"Αποθήκευση & Έξοδος\",\n    \"Cancel\": \"Ακύρωση\",\n    \"Full screen\": \"Ολόκληρη οθόνη\",\n    \"Preview\": \"Προεπισκόπηση\",\n    \"Normal\": \"Κανονικό\",\n    \"Select notebook\": \"Επιλογή τετραδίου\",\n    \"Title\": \"Τίτλος\",\n    \"Submit\": \"Αποστολή\",\n    \"Tags\": \"Ετικέτες\",\n    \"Tag\": \"Ετικέτα\",\n    \"Parent\": \"Πηγή\",\n    \"Root\": \"Ρίζα\",\n    \"Notebooks & tags\": \"Τετράδια & ετικέτες\",\n    \"Notebook\": \"Τετράδιο\",\n    \"Restore\": \"Επαναφορά\",\n    \"Delete\": \"Διαγραφή\",\n    \"New tag\": \"Νέα ετικέτα\",\n    \"Edit\": \"Επεξεργασία\",\n    \"Remove\": \"Αφαίρεση\",\n    \"Forever\": \"Για πάντα\",\n    \"No\": \"Όχι\",\n    \"Yes\": \"Ναι\",\n    \"Basic\": \"Βασική\",\n    \"Cloud storage\": \"Αποθήκευση στο σύννεφο\",\n    \"Notes per page\": \"Σημειώσεις ανά σελίδα\",\n    \"Default edit mode\": \"Προεπιλεγμένος τρόπος επεξερασγίας\",\n    \"Fullscreen with preview\": \"Προεπισκόπηση σε πλήρη οθόνη\",\n    \"Use encryption\": \"Χρησιμοποίησε κρυπτογράφηση\",\n    \"Encryption parameters\": \"Ρυθμίσεις κρυπτογράφησης\",\n    \"Encryption Password\": \"Κωδικός κρυπτογράφησης\",\n    \"Salt\": \"Άλατι\",\n    \"Random\": \"Τυχαίο\",\n    \"Key size\": \"Μέγεθος κλειδιού\",\n    \"Strengthen by a factor of\": \"Ενίσχυση με συντελεστή\",\n    \"Authentication strength\": \"Ενίσχυση ταυτοποίησης\",\n    \"Unlock\": \"Ξεκλείδωσε\",\n    \"Your new encryption password\": \"Νέος κωδικός κρυπτογράφησης\",\n    \"Your old encryption password\": \"Παλιός κωδικός κρυπτογράφησης\",\n    \"Please wait until the encryption will be completed\": \"Παρακαλώ περιμένετε να ολοκληρωθεί η διαδικασία κρυπτογράφησης\",\n    \"Shortcuts\": \"Συντομεύσεις\",\n    \"Newer\": \"Προηγούμενο\",\n    \"Older\": \"Επόμενο\",\n    \"Navigation\": \"Πλοήγηση\",\n    \"navigateTop\": \"Πάνω\",\n    \"navigateBottom\": \"Κάτω\",\n    \"Jump\": \"Άλμα\",\n    \"jumpInbox\": \"Πάνε στα εισερχόμενα\",\n    \"jumpNotebook\": \"Πήγαινε στην κατάσταση τετραδίων\",\n    \"jumpFavorite\": \"Πήγαινε στις αγαπημένες σημειώσεις\",\n    \"jumpRemoved\": \"Πήγαινε στην διαγραφή σημειώσεων\",\n    \"Actions\": \"Ενέργειες\",\n    \"actionsOpen\": \"Άνοιξε\",\n    \"actionsRotateStar\": \"Περιστροφή αστεριού\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Δημιουργεία νέας σημείωσης\",\n    \"appSearch\": \"Ψάξε σημείωση\",\n    \"appKeyboardHelp\": \"Βοήθεια πληκτρολογίου\"\n}\n"
  },
  {
    "path": "app/locales/en/translation.json",
    "content": "{\n    \"Search\": \"Search\",\n    \"All notes\": \"All notes\",\n    \"Favourites\": \"Favourites\",\n    \"Favorite\": \"Favourites\",\n    \"Trash\": \"Trash\",\n    \"Open tasks\": \"Open tasks\",\n    \"Notebooks\": \"Notebooks\",\n    \"Settings\": \"Settings\",\n    \"About\": \"About\",\n    \"Save\": \"Save\",\n    \"Save & Exit\": \"Save & Exit\",\n    \"Cancel\": \"Cancel\",\n    \"Full screen\": \"Full screen\",\n    \"Preview\": \"Preview\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Select notebook\",\n    \"Title\": \"Title\",\n    \"Submit\": \"Submit\",\n    \"Tags\": \"Tags\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Parent\",\n    \"Root\": \"Root\",\n    \"Notebooks & tags\": \"Notebooks & Tags\",\n    \"Notebook\": \"Notebook\",\n    \"Restore\": \"Restore\",\n    \"Delete\": \"Delete\",\n    \"New tag\": \"New tag\",\n    \"Edit\": \"Edit\",\n    \"Remove\": \"Remove\",\n    \"Forever\": \"Forever\",\n    \"No\": \"No\",\n    \"Yes\": \"Yes\",\n    \"Basic\": \"Basic\",\n    \"Cloud storage\": \"Cloud storage\",\n    \"Notes per page\": \"Notes per page\",\n    \"Sort notebooks\": \"Sort notebooks by\",\n    \"Name\": \"Name\",\n    \"Created\": \"Created\",\n    \"Default edit mode\": \"Default edit mode\",\n    \"Fullscreen with preview\": \"Fullscreen with preview\",\n    \"Use encryption\": \"Use encryption\",\n    \"Encryption parameters\": \"Encryption parameters\",\n    \"Encryption Password\": \"Encryption Password\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Random\",\n    \"Key size\": \"Key size\",\n    \"Strengthen by a factor of\": \"Strengthen by a factor of\",\n    \"Authentication strength\": \"Authentication strength\",\n    \"Unlock\": \"Unlock\",\n    \"Your new encryption password\": \"Your new encryption password\",\n    \"Your old encryption password\": \"Your old encryption password\",\n    \"Show sidebar\": \"Show sidebar\",\n    \"Previous\": \"Previous\",\n    \"Next\": \"Next\",\n    \"Navigation\": \"Navigation\",\n    \"navigateTop\": \"Top\",\n    \"navigateBottom\": \"Bottom\",\n    \"Jump\": \"Jump\",\n    \"jumpInbox\": \"Go to inbox\",\n    \"jumpNotebook\": \"Go to notebook list\",\n    \"jumpFavorite\": \"Go to favourite notes\",\n    \"jumpRemoved\": \"Go to removed notes\",\n    \"jumpOpenTasks\": \"Go to notes with open tasks\",\n    \"Actions\": \"Actions\",\n    \"actionsEdit\": \"Edit\",\n    \"actionsOpen\": \"Open\",\n    \"actionsRemove\": \"Remove\",\n    \"actionsRotateStar\": \"Toggle Star\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Create new note\",\n    \"appSearch\": \"Search note\",\n    \"appKeyboardHelp\": \"Keyboard help\",\n    \"Change keybindings\": \"Change keybinding settings\",\n    \"Donate\": \"Donate\",\n    \"Github page\": \"Github page\",\n    \"Report bugs and issues here\": \"Report bugs and issues here\",\n    \"Report bugs through email\": \"Report bugs through email\",\n    \"Credits\": \"Credits\",\n    \"List of contributors\": \"List of contributors\",\n    \"List of all used libraries\": \"List of all used libraries\",\n    \"Are you sure?\": \"Are you sure?\",\n    \"You have unsaved changes\": \"You have unsaved changes.\",\n    \"Dropbox API key\": \"Dropbox API key\",\n    \"Required\": \"Required\",\n    \"Optional\": \"Optional\",\n    \"Language\": \"Language\",\n    \"Action\": \"Action\",\n    \"Select\": \"Select\",\n    \"General\": \"General\",\n    \"Encryption\": \"Encryption\",\n    \"Keybindings\": \"Keybindings\",\n    \"Sync\": \"Sync\",\n    \"Profiles\": \"Profiles\",\n    \"Import\": \"Import\",\n    \"Transfer data\": \"Transfer data\",\n\t\"Transfer settings\": \"Transfer settings\",\n    \"Import settings\": \"Import settings\",\n    \"Export settings\": \"Export settings\",\n    \"Wrong format\": \"Wrong format\",\n    \"useDefaultConfigs\": \"Use settings from the default profile\",\n    \"File should be in json format\": \"File should be in json format\",\n    \"Close\": \"Close\",\n    \"Hyperlink\": \"Hyperlink\",\n    \"Editor\": \"Editor\",\n    \"Preview\": \"Preview\",\n    \"Download\": \"Download\",\n    \"Transfer everything\": \"Transfer everything\",\n    \"Find in page\": \"Find in page\",\n    \"Other\": \"Other\",\n    \"Default\": \"Default\",\n    \"Modules\": \"Modules\",\n    \"Import data\": \"Import data\",\n    \"Export data\": \"Export data\",\n    \"Enabled\": \"Enabled\",\n    \"Disabled\": \"Disabled\",\n    \"Untitled\": \"Untitled\",\n    \"Line of\": \"Line {{currentLine}} of {{numberOfLines}}\",\n\t\"Drop files\": \"Drop files here to upload\",\n\t\"Spaces per indent\": \"Spaces per indent\",\n\t\"Sort notes\": \"Sort notes by\",\n\t\"Updated date\": \"Modification date\",\n\t\"Created date\": \"Creation date\",\n\t\"Text editor\": \"Text editor\",\n\t\"Vim\": \"Vim\",\n\t\"Emacs\": \"Emacs\",\n\t\"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"Please, provide your password\",\n        \"change password\": \"Type your password here to change it\",\n        \"wait\": \"Please wait until the encryption is completed\",\n        \"error\": \"Encryption error\",\n        \"errorConfirm\": \"Error while decrypting data.\\r\\r If you changed encryption settings in another browser, **update your settings** in this browser too. Or try to import settings.\\r\\r And if you did not change anything, **try to login** again.\",\n        \"errorConfirmSettings\": \"Change encryption settings\",\n        \"errorConfirmAuth\": \"Retry again\",\n        \"backup\": {\n            \"title\": \"Backup Data\",\n            \"content\": \"Please, before proceeding to the next step, download your backup file. It contains decrypted previous data of changed profiles. Keep it in a safe place.\",\n            \"next\": \"Procceed without downloading the backup file\"\n        },\n        \"state\": {\n            \"decrypt\": \"Decrypting everything\",\n            \"encrypt\": \"Encrypting everything\",\n            \"save\": \"Saving changes\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"Profile name\",\n        \"confirm remove\": \"Profile **{{profile}}** will be removed with all the data, including notes, tags, and notebooks. This action is irreversible!\",\n        \"type name\": \"Type profile name\"\n    },\n    \"files\": {\n        \"file-url\": \"File or image URL\",\n        \"attach\": \"Attach a file\",\n        \"attachLink\": \"Attach as a link\",\n        \"attachImage\": \"Attach as an image\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"The note **{{title}}** will be moved to trash.\",\n        \"confirm remove\": \"The note **{{title}}** will be removed **for ever**!\",\n        \"create and attach\": \"Create a new note and attach its link\",\n        \"create\": \"Create a new note\",\n        \"hyperlink-dialog\": \"Title of a note or URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Select a Notebook\",\n        \"add\": \"Add a new notebook\",\n        \"edit\": \"Edit a notebook\",\n        \"name\": \"Please, provide notebook's name\",\n        \"confirm remove\": \"The notebook **{{name}}** will be removed **for ever**!\",\n        \"remove with notes\": \"Yes, remove with attached notes\",\n        \"remove\": \"Yes, remove\"\n    },\n    \"tags\": {\n        \"name\": \"Tag name is required\",\n        \"add\": \"Add a new tag\",\n        \"edit\": \"Edit a tag\",\n        \"confirm remove\": \"The tag **{{name}}** will be removed **for ever**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Now you will be redirected to **Dropbox** authorization page.\\r> Please click **OK** button.\",\n        \"auth title\": \"Dropbox auth\",\n        \"api info 1\": \"You can have your own API key on\",\n        \"api info 2\": \"When you create a new app at Dropbox's Developer site you should keep in mind that:\",\n        \"api info li 1\": \"Type of API should be **Dropbox API**\",\n        \"api info li 2\": \"Type of access should be **App Folder**\"\n    },\n    \"help\": {\n        \"firststart title\": \"Welcome to Laverna\",\n        \"firststart import\": \"If you have already used Laverna before, you can import your old settings by clicking on 'import' button bellow.\",\n        \"firststart next\": \"If you have never used Laverna before, click on 'next' button to start installation process.\",\n        \"firststart encryption\": \"If you want to use encryption, please, provide encryption password.\",\n        \"firststart sync\": \"Since we don't store any data on our servers, you need to enable synchronization with one of the adapters to be able to view your notes on other devices.\",\n        \"firststart backup\": \"Everything is almost ready. You can download your settings backup and proceed to the last step.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/eo/translation.json",
    "content": "{\n    \"en\" : \"Angla\",\n    \"ru\" : \"Rusa\",\n    \"nl\" : \"Nederlanda\",\n    \"fr\" : \"Franca\",\n    \"pt_br\" : \"Brazila Portugala\",\n    \"es\": \"Hispana\",\n    \"Search\": \"Serĉi\",\n    \"All notes\": \"Ĉiuj notoj\",\n    \"Favourites\": \"Ŝatataj\",\n    \"Favorite\": \"Ŝatata\",\n    \"Trash\": \"Rubo\",\n    \"Notebooks\": \"Kajeroj\",\n    \"Settings\": \"Agordoj\",\n    \"About\": \"Pri\",\n    \"Save\": \"Konservi\",\n    \"Save & Exit\": \"Konservu & Eliri\",\n    \"Cancel\": \"Nuligi\",\n    \"Full screen\": \"Plena ekrano\",\n    \"Preview\": \"Antaŭrigardo\",\n    \"Normal\": \"Normala\",\n    \"Select notebook\": \"Elektu kajero\",\n    \"Title\": \"Titolo\",\n    \"Submit\": \"Submetiĝi\",\n    \"Tags\": \"Etikedoj\",\n    \"Tag\": \"Etikejo\",\n    \"Parent\": \"Patro\",\n    \"Root\": \"Radiko\",\n    \"Notebooks & tags\": \"Kajeroj & etikedoj\",\n    \"Notebook\": \"Kajero\",\n    \"Restore\": \"Restarigi\",\n    \"Delete\": \"Forviŝi\",\n    \"New tag\": \"Novaj etikedon\",\n    \"Edit\": \"Redakti\",\n    \"Remove\": \"Forigi\",\n    \"Forever\": \"Eterne\",\n    \"No\": \"Ne\",\n    \"Yes\": \"Jes\",\n    \"Basic\": \"Bazaj\",\n    \"Cloud storage\": \"Nubo stokado\",\n    \"Notes per page\": \"Notoj po paĝo\",\n    \"Default edit mode\": \"Defaŭlta redaktu modo\",\n    \"Fullscreen with preview\": \"Plena ekrano kun antaŭvido\",\n    \"Use encryption\": \"Uzu ĉifrado\",\n    \"Encryption parameters\": \"Ĉifrado parametroj\",\n    \"Encryption Password\": \"Ĉifrado Pasvorto\",\n    \"Salt\": \"Salo\",\n    \"Random\": \"Hazarda\",\n    \"Key size\": \"Ŝlosila amplekso\",\n    \"Strengthen by a factor of\": \"Plifortigi per faktoro de\",\n    \"Authentication strength\": \"Aŭtentigo forto\",\n    \"Unlock\": \"Malŝlosi\",\n    \"Your new encryption password\": \"Via nova ĉifrada pasvorto\",\n    \"Your old encryption password\": \"Via malnova ĉifrada pasvorto\",\n    \"Please wait until the encryption will be completed\": \"Bonvolu atendi ĝis la ĉifrado estos kompletigita\",\n    \"Shortcuts\": \"Klavkombinoj\",\n    \"Newer\": \"Antaŭa\",\n    \"Older\": \"Sekva\",\n    \"Navigation\": \"Navigado\",\n    \"Top\": \"Supro\",\n    \"Bottom\": \"Malsupro\",\n    \"Jump\": \"Salti\",\n    \"Go to inbox\": \"Iru al enirkesto\",\n    \"Go to notebook list\": \"Iru al kajero listo\",\n    \"Go to favourite notes\": \"Iru al preferataj notoj\",\n    \"Go to removed notes\": \"Iru al forigita notoj\",\n    \"Actions\": \"Agoj\",\n    \"Open\": \"Malferma\",\n    \"Rotate Star\": \"Rotacii stelo\",\n    \"App\": \"Programo\",\n    \"Create new note\": \"Krei novan noto\",\n    \"Search note\": \"Serĉi noto\",\n    \"Keyboard help\": \"Klavaro helpo\"\n}\n"
  },
  {
    "path": "app/locales/es/translation.json",
    "content": "{\n    \"en\" : \"Inglés\",\n    \"ru\" : \"Ruso\",\n    \"nl\" : \"Neerlandés\",\n    \"fr\" : \"Francés\",\n    \"pt_br\" : \"Portugués de Brasil\",\n    \"eo\": \"Esperanto\",\n    \"es\" : \"Español\",\n    \"Search\": \"Búsqueda\",\n    \"All notes\": \"Notas\",\n    \"Favourites\": \"Favoritos\",\n    \"Favorite\": \"Favorito\",\n    \"Trash\": \"Papelera de reciclaje\",\n    \"Notebooks\": \"Cuadernos\",\n    \"Settings\": \"Configuraciones\",\n    \"About\": \"Acerca de\",\n    \"Save\": \"Guardar\",\n    \"Save & Exit\": \"Guardar y salir\",\n    \"Cancel\": \"Cancelar\",\n    \"Full screen\": \"Pantalla completa\",\n    \"Preview\": \"Vista previa\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Seleccionar cuaderno\",\n    \"Title\": \"Título\",\n    \"Submit\": \"Enviar\",\n    \"Tags\": \"Etiquetas\",\n    \"Tag\": \"Etiqueta\",\n    \"Parent\": \"Padre\",\n    \"Root\": \"Raiz\",\n    \"Notebooks & tags\": \"Cuadernos y etiquetas\",\n    \"Notebook\": \"Cuaderno\",\n    \"Restore\": \"Restaurar\",\n    \"Delete\": \"Borrar\",\n    \"New tag\": \"Nueva etiqueta\",\n    \"Edit\": \"Editar\",\n    \"Remove\": \"Borrar\",\n    \"Forever\": \"Para siempre\",\n    \"No\": \"No\",\n    \"Yes\": \"Sí\",\n    \"Basic\": \"Básico\",\n    \"Cloud storage\": \"Almacenamiento en la nube\",\n    \"Notes per page\": \"Notas por página\",\n    \"Sort notebooks\": \"Ordenar cuadernos\",\n    \"Name\": \"Nombre\",\n    \"Created\": \"Creado\",\n    \"Default edit mode\": \"Modo editar predeterminado\",\n    \"Fullscreen with preview\": \"Pantalla completa con previsualización\",\n    \"Use encryption\": \"Usar cifrado\",\n    \"Encryption parameters\": \"Parámetros de cifrado\",\n    \"Encryption Password\": \"Contraseña de cifrado\",\n    \"Salt\": \"Sal\",\n    \"Random\": \"Aleatorio\",\n    \"Key size\": \"Tamaño de la clave\",\n    \"Strengthen by a factor of\": \"Fortalecer por un factor de\",\n    \"Authentication strength\": \"Fortaleza de la autenticación\",\n    \"Unlock\": \"Desbloquear\",\n    \"Your new encryption password\": \"Su nueva contraseña de cifrado\",\n    \"Your old encryption password\": \"Su antigüa contraseña de cifrado\",\n    \"Please wait until the encryption will be completed\": \"Por favor espere mientras el cifrado se completa\",\n    \"Shortcuts\": \"Accesos directos\",\n    \"Newer\": \"Anterior\",\n    \"Older\": \"Siguiente\",\n    \"Navigation\": \"Navegación\",\n    \"Top\": \"Superior\",\n    \"Bottom\": \"Inferior\",\n    \"Jump\": \"Saltar\",\n    \"Go to inbox\": \"Ir a la bandeja de entrada\",\n    \"Go to notebook list\": \"Ir a la lista de cuadernos\",\n    \"Go to favourite notes\": \"Ir a los cuadernos favoritos\",\n    \"Go to removed notes\": \"Ir a las notas borradas\",\n    \"Actions\": \"Acciones\",\n    \"Open\": \"Abrir\",\n    \"Rotate Star\": \"Girar la Estrella\",\n    \"App\": \"App\",\n    \"Create new note\": \"Crear nueva nota\",\n    \"Search note\": \"Buscar nota\",\n    \"Keyboard help\": \"Ayuda del teclado\"\n}\n"
  },
  {
    "path": "app/locales/fr/translation.json",
    "content": "{\n    \"Search\": \"Chercher\",\n    \"All notes\": \"Toutes les notes\",\n    \"Favourites\": \"Favoris\",\n    \"Favorite\": \"Favori\",\n    \"Trash\": \"Corbeille\",\n    \"Open tasks\": \"Tâches en cours\",\n    \"Notebooks\": \"Bloc-notes\",\n    \"Settings\": \"Paramètres\",\n    \"About\": \"À propos\",\n    \"Save\": \"Sauvegarder\",\n    \"Save & Exit\": \"Sauvegarder & Quitter\",\n    \"Cancel\": \"Annuler\",\n    \"Full screen\": \"Plein écran\",\n    \"Preview\": \"Prévisualisation\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Sélectionner un bloc-notes\",\n    \"Title\": \"Titre\",\n    \"Submit\": \"Envoyer\",\n    \"Tags\": \"Étiquettes\",\n    \"Tag\": \"Étiquette\",\n    \"Parent\": \"Parent\",\n    \"Root\": \"Racine\",\n    \"Notebooks & tags\": \"Bloc-notes et étiquette\",\n    \"Notebook\": \"Bloc-notes\",\n    \"Restore\": \"Restaurer\",\n    \"Delete\": \"Supprimer\",\n    \"New tag\": \"Nouvelle étiquette\",\n    \"Edit\": \"Éditer\",\n    \"Remove\": \"Supprimer\",\n    \"Forever\": \"Effacer\",\n    \"No\": \"Non\",\n    \"Yes\": \"Oui\",\n    \"Basic\": \"Basique\",\n    \"Cloud storage\": \"Stockage dans le cloud\",\n    \"Notes per page\": \"Notes par page\",\n    \"Sort notebooks\": \"Trier les bloc-notes\",\n    \"Name\": \"Nom\",\n    \"Created\": \"Créé\",\n    \"Default edit mode\": \"Mode d'édition par défaut\",\n    \"Fullscreen with preview\": \"Plein écran avec visualisation\",\n    \"Use encryption\": \"Utiliser le chiffrement\",\n    \"Encryption parameters\": \"Paramètres de chiffrement\",\n    \"Encryption Password\": \"Mot de passe de chiffrement\",\n    \"Salt\": \"Salage\",\n    \"Random\": \"Aléatoire\",\n    \"Key size\": \"Taille de la clef\",\n    \"Strengthen by a factor of\": \"Renforcer par un facteur de\",\n    \"Authentication strength\": \"Force d'authentification\",\n    \"Unlock\": \"Déverrouiller\",\n    \"Your new encryption password\": \"Votre nouveau mot de passe de chiffrement\",\n    \"Your old encryption password\": \"Votre ancien mot de passe de chiffrement\",\n    \"Show sidebar\": \"Montrer le panneau latéral\",\n    \"Previous\": \"Précédent\",\n    \"Next\": \"Suivant\",\n    \"Navigation\": \"Navigation\",\n    \"navigateTop\": \"Haut\",\n    \"navigateBottom\": \"Bas\",\n    \"Jump\": \"Déplacement\",\n    \"jumpInbox\": \"Aller à la boîte de réception\",\n    \"jumpNotebook\": \"Aller à la liste de bloc-notes\",\n    \"jumpFavorite\": \"Aller aux notes favoris\",\n    \"jumpRemoved\": \"Aller aux notes supprimées\",\n    \"jumpOpenTasks\": \"Aller aux tâches ouvertes\",\n    \"Actions\": \"Actions\",\n    \"actionsEdit\": \"Modifier\",\n    \"actionsOpen\": \"Ouvrir\",\n    \"actionsRemove\": \"Supprimer\",\n    \"actionsRotateStar\": \"Ajouter/Supprimer des favoris\",\n    \"App\": \"Application\",\n    \"appCreateNote\": \"Créer une nouvelle note\",\n    \"appSearch\": \"Rechercher une note\",\n    \"appKeyboardHelp\": \"Afficher les raccourcis clavier\",\n    \"Change keybindings\": \"Changer les raccourcis clavier\",\n    \"Donate\": \"Faire un don\",\n    \"Github page\": \"Page Github\",\n    \"Report bugs and issues here\": \"Rapporter les bugs et les problèmes ici\",\n    \"Report bugs through email\": \"Rapporter les bugs par courriel\",\n    \"Credits\": \"Crédits\",\n    \"List of contributors\": \"Liste des contributeurs\",\n    \"List of all used libraries\": \"Liste de toutes les librairies utilisées\",\n    \"Are you sure?\": \"Êtes-vous sûr\",\n    \"You have unsaved changes\": \"Certaines modifications ne sont pas sauvegardées.\",\n    \"Dropbox API key\": \"Clé de l'API Dropbox\",\n    \"Required\": \"Requis\",\n    \"Optional\": \"Optionel\",\n    \"Language\": \"Langue\",\n    \"Action\": \"Action\",\n    \"Select\": \"Sélectionner\",\n    \"General\": \"Général\",\n    \"Encryption\": \"Chiffrement\",\n    \"Keybindings\": \"Raccourcis claviers\",\n    \"Sync\": \"Synchroniser\",\n    \"Profiles\": \"Profiles\",\n    \"Import\": \"Importer\",\n    \"Transfer data\": \"Importer & exporter\",\n    \"Import settings\": \"Paramètres d'import\",\n    \"Export settings\": \"Paramètres d'export\",\n    \"Wrong format\": \"Mauvais format\",\n    \"useDefaultConfigs\": \"Utiliser les paramètres du profile par défaut\",\n    \"File should be in json format\": \"Le fichier devrait être au format json\",\n    \"Close\": \"Fermer\",\n    \"Hyperlink\": \"Hyperlien\",\n    \"Editor\": \"Éditeur\",\n    \"Download\": \"Télécharger\",\n    \"Transfer everything\": \"Tout\",\n    \"Find in page\": \"Chercher dans la page\",\n    \"Other\": \"Divers\",\n    \"Default\": \"Par défaut\",\n    \"Modules\": \"Modules\",\n    \"Import data\": \"Importer les données\",\n    \"Export data\": \"Exporter les données\",\n    \"Enabled\": \"Activé\",\n    \"Disabled\": \"Désactivé\",\n    \"encryption\": {\n        \"provide password\": \"Veuillez saisir votre mot de passe\",\n        \"change password\": \"Saisissez votre mot de passe ici pour le modifier\",\n        \"wait\": \"Veuillez attendre que le chiffrement soit terminé\",\n        \"error\": \"Erreur de chiffrement\",\n        \"errorConfirm\": \"Erreur lors du déchiffrement des données.\\r\\r Si vous avez changé les paramètres de chiffrement sur un autre navigateur, veuillez aussi **mettre à jour vos paramètres** dans ce navigateur. Ou essayez d'importer les paramètres.\\r\\r Et si vous n'avez rien changé, **essayez de vous connecter** à nouveau.\",\n        \"errorConfirmSettings\": \"Changer les paramètres de chiffrement\",\n        \"errorConfirmAuth\": \"Réessayer\",\n        \"backup\": {\n            \"title\": \"Sauvegarder les données\",\n            \"content\": \"Veuillez télécharger votre fichier de sauvegarde avant de passer à l'étape suivante. Il contient des données déchiffrées préalablement au changement de profile. Gardez le en lieu sûr.\",\n            \"next\": \"Continuer sans télécharger le fichier de sauvegarde\"\n        },\n        \"state\": {\n            \"decrypt\": \"Déchiffrement en cours\",\n            \"encrypt\": \"Chiffrement en cours\",\n            \"save\": \"Sauvegarde en cours\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"Le profile **{{profile}}** sera supprimé avec toutes les données qu'il contient, en particulier les notes, les étiquettes, et les bloc-notes. Cette action est irréversible !\",\n        \"type name\": \"Saisir le nom du profile\"\n    },\n    \"files\": {\n        \"file-url\": \"URL du fichier ou de l'image\",\n        \"attach\": \"Joindre un fichier\",\n        \"attachLink\": \"Joindre en tant que lien\",\n        \"attachImage\": \"Joindre en tant qu'image\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"La note **{{title}}** va être déplacée dans la corbeille.\",\n        \"confirm remove\": \"La note **{{title}}** va être supprimée **pour toujours**!\",\n        \"create and attach\": \"Créer une note et joindre son lien\",\n        \"create\": \"Créer une nouvelle note\",\n        \"hyperlink-dialog\": \"Titre d'une note ou URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Sélectionner un bloc-notes\",\n        \"add\": \"Créer un nouveau bloc-notes\",\n        \"edit\": \"Modifier un bloc-notes\",\n        \"name\": \"Veuillez saisir le nom du bloc-notes\",\n        \"confirm remove\": \"Le bloc-notes **{{name}}** sera supprimé **pour toujours**!\",\n        \"remove with notes\": \"Oui, supprimer avec les notes attachées\",\n        \"remove\": \"Oui, supprimer\"\n    },\n    \"tags\": {\n        \"name\": \"Le nom de l'étiquette est requis\",\n        \"add\": \"Créer une nouvelle étiquette\",\n        \"edit\": \"Modifier une étiquette\",\n        \"confirm remove\": \"L'étiquette **{{name}}** sera supprimée **pour toujours**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Vous allez maintenant être redirigé sur la page d'autorisation de **Dropbox**.\\r> Veuillez cliquer sur le bouton **OK**.\",\n        \"auth title\": \"Autentification de Dropbox\",\n        \"api info 1\": \"Vous pouvez utiliser votre propre clé d'API\",\n        \"api info 2\": \"Lorsque vous créez une nouvelle application sur le site développeur de Dropbox, vous devez garder ceci en tête:\",\n        \"api info li 1\": \"Le type d'app doit être Dropbox API app\",\n        \"api info li 2\": \"Le type de donnée doit être Files et datastores\"\n    },\n    \"help\": {\n        \"firststart title\": \"Bienvenue sur Laverna\",\n        \"firststart import\": \"Si vous avez déja utilisé Laverna auparavant, vous pouvez importer vos paramètres en cliquant sur le bouton 'importer' ci-dessous.\",\n        \"firststart next\": \"Si vous n'avez jamais utilisé auparavant, cliquez sur le bouton 'suivant' et commencez le processus d'installation.\",\n        \"firststart encryption\": \"Si vous voulez utiliser le chiffrement, veuillez fournir un mot de passe de chiffrement.\",\n        \"firststart sync\": \"Comme nous ne stockons aucune donnée sur nos serveurs, vous devez activer la synchronisation avec l'un des services pour pouvoir visualiser vos notes sur d'autres terminaux.\",\n        \"firststart backup\": \"L'installation est presque terminée. Vous pouvez télécharger une sauvegarde de vos paramètres et continuer à la prochaine étape.\"\n    }\n}\n\n"
  },
  {
    "path": "app/locales/gl/translation.json",
    "content": "{\n    \"Search\": \"Buscar\",\n    \"All notes\": \"Todas as notas\",\n    \"Favourites\": \"Favoritos\",\n    \"Favorite\": \"Favorito\",\n    \"Trash\": \"Lixo\",\n    \"Open tasks\": \"Abrir as tarefas\",\n    \"Notebooks\": \"Cadernos de notas\",\n    \"Settings\": \"Configuracións\",\n    \"About\": \"Sobre de\",\n    \"Save\": \"Gardar\",\n    \"Save & Exit\": \"Gardar e saír\",\n    \"Cancel\": \"Cancelar\",\n    \"Full screen\": \"Pantalla completa\",\n    \"Preview\": \"Visualización previa\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Escoller o caderno\",\n    \"Title\": \"Título\",\n    \"Submit\": \"Enviar\",\n    \"Tags\": \"Etiquetas\",\n    \"Tag\": \"Etiquetas\",\n    \"Parent\": \"Nai\",\n    \"Root\": \"Raíz\",\n    \"Notebooks & tags\": \"Caderno e etiquetas\",\n    \"Notebook\": \"Caderno\",\n    \"Restore\": \"Restaurar\",\n    \"Delete\": \"Borrar\",\n    \"New tag\": \"Nova etiqueta\",\n    \"Edit\": \"Editar\",\n    \"Remove\": \"Borrar\",\n    \"Forever\": \"Para sempre\",\n    \"No\": \"Non\",\n    \"Yes\": \"Si\",\n    \"Basic\": \"Básico\",\n    \"Cloud storage\": \"Almacenamento na nube\",\n    \"Notes per page\": \"Notas por páxina\",\n    \"Sort notebooks\": \"Ordenar os cadernos por\",\n    \"Name\": \"Nome\",\n    \"Created\": \"Creado\",\n    \"Default edit mode\": \"Modo de edición por defecto\",\n    \"Fullscreen with preview\": \"Pantalla completa con vista previa\",\n    \"Use encryption\": \"Usar o cifrado\",\n    \"Encryption parameters\": \"Parámetros de cifrado\",\n    \"Encryption Password\": \"Contrasinal para o cifrado\",\n    \"Salt\": \"Saltar\",\n    \"Random\": \"Ao chou\",\n    \"Key size\": \"Tamaño da clave\",\n    \"Strengthen by a factor of\": \"Fortalecer a un nivel de\",\n    \"Authentication strength\": \"Forza da autenticación\",\n    \"Unlock\": \"Desbloquear\",\n    \"Your new encryption password\": \"O teu novo contrasinal de cifrado\",\n    \"Your old encryption password\": \"O teu anterior contrasinal de cifrado\",\n    \"Show sidebar\": \"Mostrar a barra lateral\",\n    \"Previous\": \"Vista previa\",\n    \"Next\": \"Seguinte\",\n    \"Navigation\": \"Navegación\",\n    \"navigateTop\": \"Cara a arriba\",\n    \"navigateBottom\": \"Cara a abaixo\",\n    \"Jump\": \"Saltar\",\n    \"jumpInbox\": \"Ir á caixa de entrada\",\n    \"jumpNotebook\": \"Ir á lista de cadernos\",\n    \"jumpFavorite\": \"Ir ás notas favoritas\",\n    \"jumpRemoved\": \"Ir ás notas borradas\",\n    \"jumpOpenTasks\": \"Ir ás notas con tarefas abertas\",\n    \"Actions\": \"Accións\",\n    \"actionsEdit\": \"Editar\",\n    \"actionsOpen\": \"Abrir\",\n    \"actionsRemove\": \"Borrar\",\n    \"actionsRotateStar\": \"Mudar o favorito\",\n    \"App\": \"Aplicación\",\n    \"appCreateNote\": \"Crear unha nova nota\",\n    \"appSearch\": \"Buscar notas\",\n    \"appKeyboardHelp\": \"Axuda co teclado\",\n    \"Change keybindings\": \"Cambiar a configuración dos atallos\",\n    \"Donate\": \"Doar\",\n    \"Github page\": \"Páxina de Github\",\n    \"Report bugs and issues here\": \"Informar desde aquí de erros e problemas\",\n    \"Report bugs through email\": \"Informar de erros polo correo\",\n    \"Credits\": \"Créditos\",\n    \"List of contributors\": \"Lista dos que contribuíron\",\n    \"List of all used libraries\": \"Lista de todas as librarías usadas\",\n    \"Are you sure?\": \"Estás seguro?\",\n    \"You have unsaved changes\": \"Tes cambios sen gardar.\",\n    \"Dropbox API key\": \"Clave da API de Dropbox\",\n    \"Required\": \"Obrigatorio\",\n    \"Optional\": \"Opcional\",\n    \"Language\": \"Lingua\",\n    \"Action\": \"Acción\",\n    \"Select\": \"Escoller\",\n    \"General\": \"Xeral\",\n    \"Encryption\": \"Cifrado\",\n    \"Keybindings\": \"Teclas dos atallos\",\n    \"Sync\": \"Sincronización\",\n    \"Profiles\": \"Perfís\",\n    \"Import\": \"Importar\",\n    \"Transfer data\": \"Transferir datos\",\n\t\"Transfer settings\": \"Transferir configuracións\",\n    \"Import settings\": \"Importar configuracións\",\n    \"Export settings\": \"Exportar configuracións\",\n    \"Wrong format\": \"Formato equivocado\",\n    \"useDefaultConfigs\": \"Usar a configuración do perfil por defecto\",\n    \"File should be in json format\": \"O ficheiro ten que estar nun formato json\",\n    \"Close\": \"Pechar\",\n    \"Hyperlink\": \"Hiperligazón\",\n    \"Editor\": \"Editor\",\n    \"Preview\": \"Vista previa\",\n    \"Download\": \"Descargar\",\n    \"Transfer everything\": \"Transferir todo\",\n    \"Find in page\": \"Atopar na páxina\",\n    \"Other\": \"Outros\",\n    \"Default\": \"Por defecto\",\n    \"Modules\": \"Módulos\",\n    \"Import data\": \"Importar datos\",\n    \"Export data\": \"Exportar datos\",\n    \"Enabled\": \"Activado\",\n    \"Disabled\": \"Desactivado\",\n    \"Untitled\": \"Sen título\",\n    \"Line of\": \"Liña {{currentLine}} de {{numberOfLines}}\",\n\t\"Drop files\": \"Arrastra aquí os ficheiros a subir\",\n\t\"Spaces per indent\": \"Espazos por indentación\",\n\t\"Sort notes\": \"Ordenar as notar por\",\n\t\"Updated date\": \"Data de cambios\",\n\t\"Created date\": \"Data de creación\",\n\t\"Text editor\": \"Editor de texto\",\n\t\"Vim\": \"Vim\",\n\t\"Emacs\": \"Emacs\",\n\t\"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"Introduce o contrasinal\",\n        \"change password\": \"Escribe o contrasinal para cambialo\",\n        \"wait\": \"Agarda a que acabe o cifrado\",\n        \"error\": \"Erro de cifrado\",\n        \"errorConfirm\": \"Houbo un erro cifrando os datos.\\r\\r Se mudaches as configuracións de cifrado noutro navegador **actualiza as túas configuracións** aquí tamén. Senón, proba a importar a configuración.\\r\\r E se aínda así non houbo cambios, **intenta rexistrarte** de novo.\",\n        \"errorConfirmSettings\": \"Cambiar a configuración do cifrado\",\n        \"errorConfirmAuth\": \"Volver a intentalo\",\n        \"backup\": {\n            \"title\": \"Copia de seguridade de datos\",\n            \"content\": \"Antes de continuar co seguinte paso, descarga o ficheiro coa copia de seguridade de datos. Iso contén os datos previos sen cifrar que se empregaron nos perfís anteriores. Gárdao nun lugar seguro.\",\n            \"next\": \"Continuar sen descargar a copia de seguridade de datos\"\n        },\n        \"state\": {\n            \"decrypt\": \"Descifrar todo\",\n            \"encrypt\": \"Cifrar todo\",\n            \"save\": \"Gardando os cambios\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"Nome do perfil\",\n        \"confirm remove\": \"O perfil **{{profile}}** vaise mover con todos os datos, incluíndo as notas, as etiquetas e os cadernos. Isto é algo que non se poderá desfacer!\",\n        \"type name\": \"Escribe o nome do perfil\"\n    },\n    \"files\": {\n        \"file-url\": \"URL de ficheiro ou imaxe\",\n        \"attach\": \"Anexar un ficheiro\",\n        \"attachLink\": \"Anexar unha ligazón\",\n        \"attachImage\": \"Anexar como unha imaxe\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Vaise tirar a nota **{{title}}** ao lixo.\",\n        \"confirm remove\": \"Vaise borrar a nota **{{title}}** **para sempre**!\",\n        \"create and attach\": \"Crear unha nova nota e engadir a súa ligazón\",\n        \"create\": \"Crear unha nova nota\",\n        \"hyperlink-dialog\": \"Título da nota ou URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Escoller un caderno\",\n        \"add\": \"Engadir un novo caderno\",\n        \"edit\": \"Editar un caderno\",\n        \"name\": \"Dálle un nome ao caderno\",\n        \"confirm remove\": \"Vaise borrar o caderno **{{name}}** **para sempre**!\",\n        \"remove with notes\": \"Si, e borralo coas notas que teña anexas\",\n        \"remove\": \"Si, borrar\"\n    },\n    \"tags\": {\n        \"name\": \"Fai falla un nome de etiqueta\",\n        \"add\": \"Engadir unha nova etiqueta\",\n        \"edit\": \"Editar a etiqueta\",\n        \"confirm remove\": \"Vaise borrar a etiqueta **{{name}}** **para sempre**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Agora mostraráseche a páxina de permisos para **Dropbox**.\\r> Prémelle ao botón de **Aceptar**.\",\n        \"auth title\": \"Autenticación de Dropbox\",\n        \"api info 1\": \"Agora podes ter activa a túa propia clave da API\",\n        \"api info 2\": \"Cando se crea unha nova aplicación no sitio de desenvolvemento de Dropbox hai que ter en conta que:\",\n        \"api info li 1\": \"O tipo de API ten que ser **Dropbox API**\",\n        \"api info li 2\": \"O tipo de acceso ten que ser **App Folder**\"\n    },\n    \"help\": {\n        \"firststart title\": \"Benvido/as a Laverna\",\n        \"firststart import\": \"Se xa usaches Laverna antes podes importar a configuración anterior premendo o botón de 'importar' que está abaixo.\",\n        \"firststart next\": \"Se aínda non usaches Laverna, preme en 'seguinte' e comeza a súa instalación.\",\n        \"firststart encryption\": \"Se queres usar un cifrado, introduce un contrasinal de cifrado.\",\n        \"firststart sync\": \"Xa que non ofrecemos un servizo de almacenamento de datos nos nosos servidores, para ver as túas notas noutros dispositivos precisas activar a sincronización con un dos adaptadores que temos.\",\n        \"firststart backup\": \"Xa case está todo listo. Podes descargar unha copia de seguridade da configuración e continuar co último destes pasos.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/hi_in/translation.json",
    "content": "{\n    \"en\" : \"अंग्रेजी\",\n    \"ru\" : \"रूसी\",\n    \"nl\" : \"डच\",\n    \"fr\" : \"फ्रेंच\",\n    \"pt_br\" : \"ब्राजील पुर्तगाली\",\n    \"eo\": \"एस्पेरान्तो\",\n    \"es\": \"स्पेनिश\",\n    \"de\": \"जर्मन\",\n    \"de_ch\": \"स्विस जर्मन\",\n    \"se\": \"स्वीडिश\",\n    \"el\": \"यूनानी\",\n    \"nb\": \"नार्वेजियन (बोकमाल)\",\n    \"nn\": \"नॉर्वेजियाई (नायनोर्स्क)\",\n    \"bs_ba\": \"बोस्नियाई\",\n    \"hi_in\": \"हिन्दी\",\n    \"mr_in\": \"मराठी\",\n    \"zh_cn\": \"सरलीकृत चीनी\",\n    \"Search\": \"खोजें\",\n    \"All notes\": \"सभी नोट्स\",\n    \"Favourites\": \"पसंदीदा\",\n    \"Favorite\": \"पसंदीदा\",\n    \"Trash\": \"कचरा\",\n    \"Notebooks\": \"नोटबुक\",\n    \"Settings\": \"सेटिंग्स\",\n    \"About\": \"बारे में\",\n    \"Save\": \"बचालें\",\n    \"Save & Exit\": \"बचाके निकलें\",\n    \"Cancel\": \"रद्द करें\",\n    \"Full screen\": \"पूर्ण स्क्रीन\",\n    \"Preview\": \"पूर्वावलोकन\",\n    \"Normal\": \"सामान्य\",\n    \"Select notebook\": \"नोटबुक चुनें\",\n    \"Title\": \"शीर्षक\",\n    \"Submit\": \"प्रस्तुत करें\",\n    \"Tags\": \"टैग\",\n    \"Tag\": \"टैग\",\n    \"Parent\": \"पैत्रिक\",\n    \"Root\": \"मूल\",\n    \"Notebooks & tags\": \"नोटबुक और टैग\",\n    \"Notebook\": \"नोटबुक\",\n    \"Restore\": \"वापस लाऐं\",\n    \"Delete\": \"मिटाऐं\",\n    \"New tag\": \"नया टैग\",\n    \"Edit\": \"संपादित करें\",\n    \"Remove\": \"हटाऐं\",\n    \"Forever\": \"हमेशा के लिये\",\n    \"No\": \"नहीं\",\n    \"Yes\": \"हाँ\",\n    \"Basic\": \"बुनियादी\",\n    \"Cloud storage\": \"बादल भंडारण\",\n    \"Notes per page\": \"प्रति पृष्ठ नोट्स\",\n    \"Sort notebooks\": \"नोटबुक क्रमबद्ध करें\",\n    \"Name\": \"नाम\",\n    \"Created\": \"जब बनाया\",\n    \"Default edit mode\": \"डिफ़ॉल्ट संपादन मोड\",\n    \"Fullscreen with preview\": \"फुल स्क्रीन के साथ पूर्वावलोकन\",\n    \"Use encryption\": \"एन्क्रिप्शन उपयोग करें\",\n    \"Encryption parameters\": \"एन्क्रिप्शन के मापदंडों\",\n    \"Encryption Password\": \"एन्क्रिप्शन का पासवर्ड\",\n    \"Salt\": \"साल्ट\",\n    \"Random\": \"यादृच्छिक करें\",\n    \"Key size\": \"की साईज\",\n    \"Strengthen by a factor of\": \"ताकत का कारक\",\n    \"Authentication strength\": \"प्रमाणीकरण ताकत\",\n    \"Unlock\": \"अनलॉक\",\n    \"Your new encryption password\": \"आपका नया एन्क्रिप्शन पासवर्ड\",\n    \"Your old encryption password\": \"आपका  पुराना एन्क्रिप्शन पासवर्ड\",\n    \"Please wait until the encryption will be completed\": \"कृपया एन्क्रिप्शन पूरा होने तक प्रतीक्षा करें\",\n    \"Shortcuts\": \"शॉर्टकट\",\n    \"Newer\": \"नए\",\n    \"Older\": \"पुराने\",\n    \"Navigation\": \"नेविगेशन\",\n    \"navigateTop\": \"ऊपर\",\n    \"navigateBottom\": \"नीचे\",\n    \"Jump\": \"कूदो\",\n    \"jumpInbox\": \"इनबॉक्स में जाओ\",\n    \"jumpNotebook\": \"नोटबुक सूची में जाओ\",\n    \"jumpFavorite\": \"पसंदीदा नोट्स पर जाएँ\",\n    \"jumpRemoved\": \"हटाऐं हुऐ नोट्स पर जाएँ\",\n    \"Actions\": \"प्रक्रियाऐं\",\n    \"actionsEdit\": \"संपादित करें\",\n    \"actionsOpen\": \" खोलें\",\n    \"actionsRemove\": \"हटाऐं\",\n    \"actionsRotateStar\": \"तारा घुमाएं\",\n    \"App\": \"एप्लिकेशन\",\n    \"appCreateNote\": \"नयी नोट बनाएं\",\n    \"appSearch\": \"नोट खोजें\",\n    \"appKeyboardHelp\": \"कीबोर्ड मदद\"\n}\n"
  },
  {
    "path": "app/locales/it/translation.json",
    "content": "{\n    \"en\" : \"Inglese\",\n    \"ru\" : \"Russo\",\n    \"nl\" : \"Olandese\",\n    \"fr\" : \"Francese\",\n    \"pt_br\" : \"Portoghese Brasiliano\",\n    \"eo\": \"Esperanto\",\n    \"es\": \"Spagnolo\",\n    \"de\": \"Tedesco\",\n    \"de_ch\": \"Tedesco Svizzero\",\n    \"Search\": \"Cerca\",\n    \"All notes\": \"Tutte le note\",\n    \"Favourites\": \"Preferiti\",\n    \"Favorite\": \"Preferito\",\n    \"Trash\": \"Cestino\",\n    \"Notebooks\": \"Quaderni\",\n    \"Settings\": \"Impostazioni\",\n    \"About\": \"Informazioni\",\n    \"Save\": \"Salva\",\n    \"Save & Exit\": \"Salva ed esci\",\n    \"Cancel\": \"Annulla\",\n    \"Full screen\": \"Schermo intero\",\n    \"Preview\": \"Anteprima\",\n    \"Normal\": \"Normale\",\n    \"Select notebook\": \"Scegli quaderno\",\n    \"Title\": \"Titolo\",\n    \"Submit\": \"Invia\",\n    \"Tags\": \"Tags\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Superiore\",\n    \"Root\": \"Root\",\n    \"Notebooks & tags\": \"Quaderni & tag\",\n    \"Notebook\": \"Quaderno\",\n    \"Restore\": \"Ripristina\",\n    \"Delete\": \"Elimina\",\n    \"New tag\": \"Nuovo tag\",\n    \"Edit\": \"Modifica\",\n    \"Remove\": \"Rimuovi\",\n    \"Forever\": \"Per sempre\",\n    \"No\": \"No\",\n    \"Yes\": \"Sì\",\n    \"Basic\": \"Basic\",\n    \"Cloud storage\": \"Cloud storage\",\n    \"Notes per page\": \"Note per pagina\",\n    \"Sort notebooks\": \"Ordina quaderni\",\n    \"Name\": \"Nome\",\n    \"Created\": \"Creato\",\n    \"Default edit mode\": \"Modalità default\",\n    \"Fullscreen with preview\": \"Schermo intero con anteprima\",\n    \"Use encryption\": \"Usa crittografia\",\n    \"Encryption parameters\": \"Parametri crittografia\",\n    \"Encryption Password\": \"Password crittografia\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Casuale\",\n    \"Key size\": \"Dimensione chiave\",\n    \"Strengthen by a factor of\": \"Rafforza di un fattore di\",\n    \"Authentication strength\": \"Resistenza autenticazione\",\n    \"Unlock\": \"Sblocca\",\n    \"Your new encryption password\": \"La tua nuova password di cifratura\",\n    \"Your old encryption password\": \"La tua vecchia password di cifratura\",\n    \"Please wait until the encryption will be completed\": \"Aspetta fino a che la cifratura non è completa\",\n    \"Shortcuts\": \"Collegamenti\",\n    \"Previous\": \"Precedente\",\n    \"Next\": \"Prossimo\",\n    \"Navigation\": \"Navigazione\",\n    \"navigateTop\": \"Su\",\n    \"navigateBottom\": \"Giù\",\n    \"Jump\": \"Salta\",\n    \"jumpInbox\": \"Vai alla inbox\",\n    \"jumpNotebook\": \"Vai alla lista dei quaderni\",\n    \"jumpFavorite\": \"Vai alle note preferite\",\n    \"jumpRemoved\": \"Vai alle note cancellate\",\n    \"Actions\": \"Azioni\",\n    \"actionsEdit\": \"Modifica\",\n    \"actionsOpen\": \"Apri\",\n    \"actionsRemove\": \"Cancella\",\n    \"actionsRotateStar\": \"Ruota la stella\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Crea nuova nota\",\n    \"appSearch\": \"Cerca nota\",\n    \"appKeyboardHelp\": \"Aiuto tastiera\",\n    \"Remove profile\": \"Sei sicuro di voler cancellare il profilo '__profile__'?\",\n    \"Change shortcuts\": \"Cambia impostazioni collegamento\",\n    \"Donate\": \"Dona\",\n    \"Github page\": \"Pagina Github\",\n    \"Report bugs and issues here\": \"Segnala bug e problemi qui\",\n    \"Report bugs through email\": \"Segnala bug via e-mail\",\n    \"Credits\": \"Riconoscimenti\",\n    \"List of contributors\": \"Lista di contributori\",\n    \"List of all used libraries\": \"Lista di tutte le librerie usate\",\n    \"notebooks\": {\n        \"name\": \"Fornisci un nome per il quaderno\"\n    },\n    \"tags\": {\n        \"name\": \"Il nome del tag è necessario\"\n    }\n}\n"
  },
  {
    "path": "app/locales/ja/translation.json",
    "content": "{\n    \"Search\": \"検索\",\n    \"All notes\": \"すべてのノート\",\n    \"Favourites\": \"お気に入り集\",\n    \"Favorite\": \"お気に入り\",\n    \"Trash\": \"ゴミ箱\",\n    \"Open tasks\": \"タスク集\",\n    \"Notebooks\": \"ノートブック集\",\n    \"Settings\": \"設定\",\n    \"About\": \"Lavernaについて\",\n    \"Save\": \"保存\",\n    \"Save & Exit\": \"保存して終了\",\n    \"Cancel\": \"キャンセル\",\n    \"Full screen\": \"フルスクリーン\",\n    \"Preview\": \"プレビュー\",\n    \"Normal\": \"標準\",\n    \"Select notebook\": \"ノートブックを選択\",\n    \"Title\": \"タイトル\",\n    \"Submit\": \"実行\",\n    \"Tags\": \"タグ集\",\n    \"Tag\": \"タグ\",\n    \"Parent\": \"親\",\n    \"Root\": \"ルート\",\n    \"Notebooks & tags\": \"ノートブック集とタグ集\",\n    \"Notebook\": \"ノートブック\",\n    \"Restore\": \"元に戻す\",\n    \"Delete\": \"削除\",\n    \"New tag\": \"新しいタグ\",\n    \"Edit\": \"編集\",\n    \"Remove\": \"削除\",\n    \"Forever\": \"完全に削除\",\n    \"No\": \"いいえ\",\n    \"Yes\": \"はい\",\n    \"Basic\": \"Basic\",\n    \"Cloud storage\": \"クラウドストレージ\",\n    \"Notes per page\": \"ノート数/ページ\",\n    \"Sort notebooks\": \"ノートブックの並び順\",\n    \"Name\": \"名前\",\n    \"Created\": \"作成された\",\n    \"Default edit mode\": \"デフォルトの編集モード\",\n    \"Fullscreen with preview\": \"プレビューありのフルスクリーン\",\n    \"Use encryption\": \"暗号化する\",\n    \"Encryption parameters\": \"暗号化パラメータ\",\n    \"Encryption Password\": \"暗号化パスワード\",\n    \"Salt\": \"ソルト\",\n    \"Random\": \"ランダムな値\",\n    \"Key size\": \"鍵長(ビット)\",\n    \"Strengthen by a factor of\": \"鍵生成iteration回数\",\n    \"Authentication strength\": \"認証強度\",\n    \"Unlock\": \"ロック解除\",\n    \"Your new encryption password\": \"新しいパスワード\",\n    \"Your old encryption password\": \"古いパスワード\",\n    \"Show sidebar\": \"サイドバーを表示\",\n    \"Previous\": \"前\",\n    \"Next\": \"次\",\n    \"Navigation\": \"キー操作\",\n    \"navigateTop\": \"上\",\n    \"navigateBottom\": \"下\",\n    \"Jump\": \"ジャンプ\",\n    \"jumpInbox\": \"すべてを表示\",\n    \"jumpNotebook\": \"ノートブック集\",\n    \"jumpFavorite\": \"お気に入りノート集\",\n    \"jumpRemoved\": \"ゴミ箱\",\n    \"jumpOpenTasks\": \"タスク集\",\n    \"Actions\": \"アクション\",\n    \"actionsEdit\": \"編集\",\n    \"actionsOpen\": \"開く\",\n    \"actionsRemove\": \"削除\",\n    \"actionsRotateStar\": \"星マークをON/OFF\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"ノートを新規作成\",\n    \"appSearch\": \"ノートを検索\",\n    \"appKeyboardHelp\": \"キー割り当て表を表示\",\n    \"Change keybindings\": \"キー割り当ての変更\",\n    \"Donate\": \"カンパする\",\n    \"Github page\": \"Github ページ\",\n    \"Report bugs and issues here\": \"バグや課題を報告する\",\n    \"Report bugs through email\": \"メールでバグを報告\",\n    \"Credits\": \"クレジット\",\n    \"List of contributors\": \"貢献者リスト\",\n    \"List of all used libraries\": \"使用ライブラリのリスト\",\n    \"Are you sure?\": \"本当にいいですか？\",\n    \"You have unsaved changes\": \"変更が保存されていません\",\n    \"Dropbox API key\": \"Dropbox API キー\",\n    \"Required\": \"必須\",\n    \"Optional\": \"任意\",\n    \"Language\": \"言語\",\n    \"Action\": \"操作\",\n    \"Select\": \"選択\",\n    \"General\": \"総合\",\n    \"Encryption\": \"暗号化\",\n    \"Keybindings\": \"キー割り当て\",\n    \"Sync\": \"同期\",\n    \"Profiles\": \"プロファイル\",\n    \"Import\": \"インポート\",\n    \"Transfer data\": \"データを移す\",\n    \"Transfer settings\": \"設定を移す\",\n    \"Import settings\": \"設定を取り込む\",\n    \"Export settings\": \"設定を取り出す\",\n    \"Wrong format\": \"形式が異なります\",\n    \"useDefaultConfigs\": \"デフォルトのプロファイルの設定を使う\",\n    \"File should be in json format\": \"ファイルはjson形式でないといけません\",\n    \"Close\": \"閉じる\",\n    \"Hyperlink\": \"ハイパーリンク\",\n    \"Editor\": \"エディタ\",\n    \"Preview\": \"プレビュー\",\n    \"Download\": \"ダウンロード\",\n    \"Transfer everything\": \"すべて移す\",\n    \"Find in page\": \"ページ内で探す\",\n    \"Other\": \"その他\",\n    \"Default\": \"デフォルト\",\n    \"Modules\": \"モジュール\",\n    \"Import data\": \"データを取り込む\",\n    \"Export data\": \"データを取り出す\",\n    \"Enabled\": \"有効\",\n    \"Disabled\": \"無効\",\n    \"Untitled\": \"無題\",\n    \"Line of\": \"{{numberOfLines}}行中{{currentLine}}行目\",\n\t\"Drop files\": \"ファイルをここにドロップしてアップロード\",\n\t\"Spaces per indent\": \"タブの空白数(半角)\",\n\t\"Sort notes\": \"ノートの並び順\",\n\t\"Updated date\": \"変更日時\",\n\t\"Created date\": \"作成日時\",\n\t\"Text editor\": \"テキストエディタ\",\n\t\"Vim\": \"Vim\",\n\t\"Emacs\": \"Emacs\",\n\t\"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"パスワードを入力してください\",\n        \"change password\": \"パスワードを変更したい場合ここに入力してください\",\n        \"wait\": \"暗号化が完了するまでお待ちください\",\n        \"error\": \"暗号化エラー\",\n        \"errorConfirm\": \"データを復号中にエラーが発生しました。\\r\\r 別のブラウザでパスワードを変更した場合, このブラウザでも**設定を変更**してください。または設定を取り込みます。\\r\\r 何の設定を変更していなければもう一度**ログイン**してください。\",\n        \"errorConfirmSettings\": \"暗号の設定を変更\",\n        \"errorConfirmAuth\": \"再試行してくださ\",\n        \"backup\": {\n            \"title\": \"データをバックアップ\",\n            \"content\": \"続行する前にバックアップをダウンロードしてください。バックアップファイルには暗号化されていないデータが入っています。安全な場所で保管してください。\",\n            \"next\": \"バックアップファイルをダウンロードせずに続行する。\"\n        },\n        \"state\": {\n            \"decrypt\": \"すべてを復号\",\n            \"encrypt\": \"すべてを暗号化\",\n            \"save\": \"変更を保存\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"プロファイル名\",\n        \"confirm remove\": \"これはもとに戻せない操作です！ノート・タグ・ノートブックを含め、「**{{profile}}**」のすべてのデータは削除されます。\",\n        \"type name\": \"プロファイル名を入力\"\n    },\n    \"files\": {\n        \"file-url\": \"ファイル、または画像のURL\",\n        \"attach\": \"ファイルを添付する\",\n        \"attachLink\": \"リンクを添付する\",\n        \"attachImage\": \"画像を添付する\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"ノート「**{{title}}**」をゴミ箱に移動します\",\n        \"confirm remove\": \"「**{{title}}**」は**完全に**削除されます\",\n        \"create and attach\": \"リンクを添付して新しいノートを作成\",\n        \"create\": \"新しノートを作成\",\n        \"hyperlink-dialog\": \"ノートの題名またはURL\"\n    },\n    \"notebooks\": {\n        \"select\": \"ノートブックを選択\",\n        \"add\": \"ノートブックを追加\",\n        \"edit\": \"ノートブック名を変更\",\n        \"name\": \"ノートブック名を入力してください\",\n        \"confirm remove\": \"「**{{name}}**」は**完全に**削除されます！\",\n        \"remove with notes\": \"はい、中のノートも削除する\",\n        \"remove\": \"はい、中のノートは削除しない\"\n    },\n    \"tags\": {\n        \"name\": \"タグ名が必要です\",\n        \"add\": \"タグを追加\",\n        \"edit\": \"タグを編集\",\n        \"confirm remove\": \"「**{{name}}**」は**完全に**削除されます！\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"それでは**Dropbox**認証ページにリダイレクトされます。\\r> **OK** ボタンをクリックしてください\",\n        \"auth title\": \"Dropbox 認証\",\n        \"api info 1\": \"あなた専用のAPIキーを使うことができます。\",\n        \"api info 2\": \"Dropbox's Developer で新しいアプリを作成するとき次のことに注意してください:\",\n        \"api info li 1\": \"API タイプは **Dropbox API** です\",\n        \"api info li 2\": \"アクセスタイプは **App Folder** です\"\n    },\n    \"help\": {\n        \"firststart title\": \"Lavernaへようこそ\",\n        \"firststart import\": \"既にLavernaをご利用の場合、下の'import'ボタンをクリックしてその設定を取り込むことができます。\",\n        \"firststart next\": \"Lavernaを初めて使う場合、'next'ボタンをクリックしてインストールを進めてください。\",\n        \"firststart encryptiON\": \"暗号化機能を使う場合、パスワードを入力してください。\",\n        \"firststart sync\": \"Lavernaはサーバにデータを保管しないので、他のデバイスでもノートを使いたい場合は同期を機能を有効にしてください。\",\n        \"firststart backup\": \"もう少しで完了です。設定のバックアップを取れるようになりました。そして、最後のステップへ進んでください。\"\n    }\n}\n"
  },
  {
    "path": "app/locales/ko/translation.json",
    "content": "{\n    \"Search\": \"검색\",\n    \"All notes\": \"모든 노트\",\n    \"Favourites\": \"즐겨찾기\",\n    \"Favorite\": \"즐겨찾기\",\n    \"Trash\": \"휴지통\",\n    \"Open tasks\": \"진행중인 작업\",\n    \"Notebooks\": \"노트북\",\n    \"Settings\": \"설정\",\n    \"About\": \"정보\",\n    \"Save\": \"저장\",\n    \"Save & Exit\": \"저장 후 종료\",\n    \"Cancel\": \"취소\",\n    \"Full screen\": \"전체화면\",\n    \"Preview\": \"미리보기\",\n    \"Normal\": \"기본\",\n    \"Select notebook\": \"노트북 선택\",\n    \"Title\": \"제목\",\n    \"Submit\": \"제출하기\",\n    \"Tags\": \"태그\",\n    \"Tag\": \"태그\",\n    \"Parent\": \"위치\",\n    \"Root\": \"최상위\",\n    \"Notebooks & tags\": \"노트북 & 태그\",\n    \"Notebook\": \"노트북\",\n    \"Restore\": \"복원\",\n    \"Delete\": \"삭제\",\n    \"New tag\": \"새로운 태그\",\n    \"Edit\": \"편집\",\n    \"Remove\": \"제거\",\n    \"Forever\": \"영원히\",\n    \"No\": \"아니오\",\n    \"Yes\": \"예\",\n    \"Basic\": \"기본\",\n    \"Cloud storage\": \"클라우드 저장소\",\n    \"Notes per page\": \"페이지 당 노트수\",\n    \"Sort notebooks\": \"노트북 정렬\",\n    \"Name\": \"이름순으로 정렬\",\n    \"Created\": \"시간순으로 정렬\",\n    \"Default edit mode\": \"기본 편집모드\",\n    \"Fullscreen with preview\": \"전체화면과 미리보기\",\n    \"Use encryption\": \"암호화 사용\",\n    \"Encryption parameters\": \"암호화 변수\",\n    \"Encryption Password\": \"암호화 비밀번호\",\n    \"Salt\": \"소금\",\n    \"Random\": \"무작위\",\n    \"Key size\": \"암호키 길이\",\n    \"Strengthen by a factor of\": \"강화 인자\",\n    \"Authentication strength\": \"인증 강도\",\n    \"Unlock\": \"Unlock\",\n    \"Your new encryption password\": \"새로운 암호화 비밀번호\",\n    \"Your old encryption password\": \"기존 암호화 비밀번호\",\n    \"Show sidebar\": \"사이드바 보이기\",\n    \"Previous\": \"이전\",\n    \"Next\": \"다음\",\n    \"Navigation\": \"탐색\",\n    \"navigateTop\": \"위로\",\n    \"navigateBottom\": \"아래로\",\n    \"Jump\": \"빠른 이동\",\n    \"jumpInbox\": \"모든 노트 목록으로 이동\",\n    \"jumpNotebook\": \"노트북 목록으로 이동\",\n    \"jumpFavorite\": \"즐겨찾기로 이동\",\n    \"jumpRemoved\": \"휴지통으로 이동\",\n    \"jumpOpenTasks\": \"진행중인 작업으로 이동\",\n    \"Actions\": \"작업\",\n    \"actionsEdit\": \"편집\",\n    \"actionsOpen\": \"열기\",\n    \"actionsRemove\": \"삭제\",\n    \"actionsRotateStar\": \"즐겨찾기 토글\",\n    \"App\": \"앱\",\n    \"appCreateNote\": \"새 노트 만들기\",\n    \"appSearch\": \"노트 검색\",\n    \"appKeyboardHelp\": \"단축키 보기\",\n    \"Change keybindings\": \"단축키 변경하기\",\n    \"Donate\": \"기부하기\",\n    \"Github page\": \"Github 페이지\",\n    \"Report bugs and issues here\": \"Github에 버그 제보하기\",\n    \"Report bugs through email\": \"이메일로 버그 제보하기\",\n    \"Credits\": \"크레딧\",\n    \"List of contributors\": \"기여자 목록\",\n    \"List of all used libraries\": \"사용된 라이브러리 목록\",\n    \"Are you sure?\": \"정말이세요?\",\n    \"You have unsaved changes\": \"변경한 사항이 저장되지 않습니다.\",\n    \"Dropbox API key\": \"Dropbox API 키\",\n    \"Required\": \"필수사항\",\n    \"Optional\": \"선택사항\",\n    \"Language\": \"언어\",\n    \"Action\": \"작업\",\n    \"Select\": \"선택\",\n    \"General\": \"일반\",\n    \"Encryption\": \"암호화\",\n    \"Keybindings\": \"단축키\",\n    \"Sync\": \"동기화\",\n    \"Profiles\": \"프로필\",\n    \"Import\": \"가져오기\",\n    \"Transfer data\": \"가져오기 & 내보내기\",\n    \"Import settings\": \"가져오기 설정\",\n    \"Export settings\": \"내보내기 설정\",\n    \"Wrong format\": \"잘못된 형식의 파일\",\n    \"useDefaultConfigs\": \"기본 프로필의 설정을 사용합니다\",\n    \"File should be in json format\": \"json 형식의 파일이여야 합니다\",\n    \"Close\": \"닫기\",\n    \"Hyperlink\": \"하이퍼링크\",\n    \"Editor\": \"편집자\",\n    \"Preview\": \"미리보기\",\n    \"Download\": \"다운로드\",\n    \"Transfer everything\": \"전부\",\n    \"Find in page\": \"페이지에서 찾기\",\n    \"Other\": \"기타\",\n    \"Default\": \"기본\",\n    \"Modules\": \"모듈\",\n    \"Import data\": \"데이터 가져오기\",\n    \"Export data\": \"데이터 내보내기\",\n    \"Enabled\": \"활성화\",\n    \"Disabled\": \"비활성화\",\n    \"encryption\": {\n        \"provide password\": \"비밀번호를 입력해주세요\",\n        \"change password\": \"변경할 비밀번호를 입력해주세요\",\n        \"wait\": \"암호화가 끝날때까지 기다려주세요\",\n        \"error\": \"암호화 에러\",\n        \"errorConfirm\": \"데이터 암호화중 에러가 발생했습니다.\\r\\r 만약 또 다른 Laverna 윈도우에서 암호화 설정을 변경하셨다면 현재 윈도우에서도 **설정을 갱신하거나** 설정 가져오기를 시도해보세요.\\r\\r 그래도 아무것도 변하지 않는다면 **다시 로그인해주세요**.\",\n        \"errorConfirmSettings\": \"암호화 설정 변경\",\n        \"errorConfirmAuth\": \"다시 시도\",\n        \"backup\": {\n            \"title\": \"데이터 백업\",\n            \"content\": \"계속 진행하기 전에, 백업 파일을 다운로드 해주세요. 이 파일은 변경된 프로필의 암호화되지 않은 데이터로 구성되어 있습니다. 안전한 장소에 보관해주세요.\",\n            \"next\": \"백업 파일을 다운로드 하지 않고 진행하기\"\n        },\n        \"state\": {\n            \"decrypt\": \"모든 데이터 해독 중\",\n            \"encrypt\": \"모든 데이터 암호화 중\",\n            \"save\": \"변경사항 저장중\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"**{{profile}}** 프로필이 지워집니다. 프로필에 포함된 노트, 태그, 노트북 등 모든 데이터가 삭제되고, 되돌릴 수 없어요!\",\n        \"type name\": \"새로운 프로필 이름을 입력하세요\"\n    },\n    \"files\": {\n        \"file-url\": \"파일 또는 이미지 URL\",\n        \"attach\": \"파일 첨부\",\n        \"attachLink\": \"링크로 첨부\",\n        \"attachImage\": \"이미지로 첨부\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"**{{title}}** 노트가 휴지통으로 이동합니다.\",\n        \"confirm remove\": \"**{{title}}** 노트가 **영원히** 지워집니다!\",\n        \"create and attach\": \"새 노트를 만들고 링크를 연결합니다\",\n        \"create\": \"새 노트 만들기\",\n        \"hyperlink-dialog\": \"노트 제목 또는 URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"노트북 선택\",\n        \"add\": \"노트북 추가\",\n        \"edit\": \"노트북 편집\",\n        \"name\": \"노트북 이름을 입력해주세요\",\n        \"confirm remove\": \"**{{name}}** 노트북이 **영원히** 지워집니다!\",\n        \"remove with notes\": \"네, 포함된 노트도 함께 지워주세요\",\n        \"remove\": \"네, 지워주세요\"\n    },\n    \"tags\": {\n        \"name\": \"태그 이름을 입력해주세요\",\n        \"add\": \"태그 추가\",\n        \"edit\": \"태그 편집\",\n        \"confirm remove\": \"**{{name}}** 태그가 **영원히** 지워집니다!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"이제 **Dropbox** 권한 승인 페이지로 이동합니다.\\r> **OK** 버튼을 눌러주세요.\",\n        \"auth title\": \"Dropbox 권한 승인\",\n        \"api info 1\": \"여기서 여러분의 API key를 만들 수 있습니다:\",\n        \"api info 2\": \"Dropbox 개발자 사이트에서 새 앱을 만들 때 이것을 기억해주세요:\",\n        \"api info li 1\": \"앱의 종류는 Dropbox API 앱이여야 합니다.\",\n        \"api info li 2\": \"데이터의 종류는 파일과 데이터저장소여야 합니다.\"\n    },\n    \"help\": {\n        \"firststart title\": \"Laverna에 오신걸 환영합니다\",\n        \"firststart import\": \"이미 Laverna를 사용하고 계신다면, 아래의 '가져오기' 버튼으로 이전 설정을 가져올 수 있습니다.\",\n        \"firststart next\": \"Laverna를 처음 사용하신다면, '다음' 버튼으로 설치를 시작하세요.\",\n        \"firststart encryption\": \"암호화를 사용하길 원하신다면, 암호화 비밀번호를 입력해주세요.\",\n        \"firststart sync\": \"우리는 어떠한 데이터도 서버에 저장하지 않기 때문에, 다른 장치에서 여러분의 노트를 보기 위해서는 적용된 서비스들 중 하나와 동기화를 사용할 필요가 있습니다.\",\n        \"firststart backup\": \"모든 준비가 거의 완료되었습니다. 여러분의 설정 백업을 다운로드하고 마지막 단계를 진행할 수 있습니다.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/locales.json",
    "content": "{\n    \"ar\": {\n        \"name\": \"Arabic\",\n        \"nativeName\": \"العربية\"\n    },\n    \"it\": {\n        \"name\": \"Italian\",\n        \"nativeName\": \"Italiano\"\n    },\n    \"bs_ba\": {\n        \"name\": \"Bosnian\",\n        \"nativeName\": \"Bosnian\"\n    },\n    \"cs\": {\n        \"name\": \"Czech\",\n        \"nativeName\": \"Čeština\"\n    },\n    \"da\": {\n        \"name\": \"Danish\",\n        \"nativeName\": \"Dansk\"\n    },\n    \"de\": {\n        \"name\": \"German\",\n        \"nativeName\": \"Deutsch\"\n    },\n    \"de_ch\": {\n        \"name\": \"Swiss german\",\n        \"nativeName\": \"Schwiizerdütsch\"\n    },\n    \"el\": {\n        \"name\": \"Greek\",\n        \"nativeName\": \"Ελληνικά\"\n    },\n    \"en\": {\n        \"name\": \"English\",\n        \"nativeName\": \"English\"\n    },\n    \"eo\": {\n        \"name\": \"Esperanto\",\n        \"nativeName\": \"Esperanto\"\n    },\n    \"es\": {\n        \"name\": \"Spanish\",\n        \"nativeName\": \"Español\"\n    },\n    \"fr\": {\n        \"name\": \"French\",\n        \"nativeName\": \"Français\"\n    },\n    \"gl\": {\n        \"name\": \"Galician\",\n        \"nativeName\": \"Galego\"\n    },\n    \"hi_in\": {\n        \"name\": \"Hindi\",\n        \"nativeName\": \"Hindi\"\n    },\n    \"ja\": {\n        \"name\": \"Japanese\",\n        \"nativeName\": \"日本語\"\n    },\n    \"ko\": {\n        \"name\": \"Korean\",\n        \"nativeName\": \"한국어\"\n    },\n    \"mr_in\": {\n        \"name\": \"Marathi\",\n        \"nativeName\": \"Marathi\"\n    },\n    \"nb\": {\n        \"name\": \"Norwegian Bokmål\",\n        \"nativeName\": \"Norsk bokmål\"\n    },\n    \"nl\": {\n        \"name\": \"Dutch\",\n        \"nativeName\": \"Nederlands\"\n    },\n    \"nn\": {\n        \"name\": \"Norwegian Nynorsk\",\n        \"nativeName\": \"Norsk nynorsk\"\n    },\n    \"oc\": {\n        \"name\": \"Occitan\",\n        \"nativeName\": \"Occitan\"\n    },\n    \"lt\": {\n      \"name\": \"Lithuanian\",\n      \"nativeName\": \"Lietuvių\"\n    },\n    \"lv\": {\n      \"name\": \"Latvian\",\n      \"nativeName\": \"Latviešu\"\n    },\n    \"pl\": {\n        \"name\": \"Polish\",\n        \"nativeName\": \"Polski\"\n    },\n    \"pt_br\": {\n        \"name\": \"Portugisich (Brasilien)\",\n        \"nativeName\": \"Portugisich (Brasilien)\"\n    },\n    \"ru\": {\n        \"name\": \"Russian\",\n        \"nativeName\": \"Русский\"\n    },\n    \"se\": {\n        \"name\": \"Swedish\",\n        \"nativeName\": \"Svenska\"\n    },\n    \"sq\": {\n        \"name\": \"Albanian\",\n        \"nativeName\": \"Shqip\"\n    },\n    \"tr\": {\n        \"name\": \"Turkish\",\n        \"nativeName\": \"Türkçe\"\n    },\n    \"zh_cn\": {\n        \"name\": \"Simplified Chinese\",\n        \"nativeName\": \"Simplified Chinese\"\n    },\n    \"zh_tw\": {\n        \"name\": \"Traditional Chinese (Taiwan)\",\n        \"nativeName\": \"Traditional Chinese (Taiwan)\"\n    }\n}\n"
  },
  {
    "path": "app/locales/lt/translation.json",
    "content": "{\n    \"Search\": \"Paieška\",\n    \"All notes\": \"Visi užrašai\",\n    \"Favourites\": \"Mėgstamiausi\",\n    \"Favorite\": \"Mėgstamiausi\",\n    \"Trash\": \"Šiukšlinė\",\n    \"Open tasks\": \"Nebaigtos užduotys\",\n    \"Notebooks\": \"Užrašinės\",\n    \"Settings\": \"Nustatymai\",\n    \"About\": \"Apie\",\n    \"Save\": \"Išsaugoti\",\n    \"Save & Exit\": \"Išsaugoti ir išeiti\",\n    \"Cancel\": \"Atšaukti\",\n    \"Full screen\": \"Pilnas ekranas\",\n    \"Preview\": \"Peržiūra\",\n    \"Normal\": \"Normalus\",\n    \"Select notebook\": \"Pasirinkti užrašinę\",\n    \"Title\": \"Pavadinimas\",\n    \"Submit\": \"Pateikti\",\n    \"Tags\": \"Gairės\",\n    \"Tag\": \"Gairė\",\n    \"Parent\": \"Užrašinė\",\n    \"Root\": \"Pagrindinė\",\n    \"Notebooks & tags\": \"Užrašinės ir gairės\",\n    \"Notebook\": \"Užrašinė\",\n    \"Restore\": \"Atkurti\",\n    \"Delete\": \"Ištrinti\",\n    \"New tag\": \"Nauja gairė\",\n    \"Edit\": \"Keisti\",\n    \"Remove\": \"Pašalinti\",\n    \"Forever\": \"Visam laikui\",\n    \"No\": \"Ne\",\n    \"Yes\": \"Taip\",\n    \"Basic\": \"Bazinis\",\n    \"Cloud storage\": \"Saugykla debesyse\",\n    \"Notes per page\": \"Užrašų puslapiuose\",\n    \"Sort notebooks\": \"Rūšiuoti užrašines\",\n    \"Name\": \"Pavadinimas\",\n    \"Created\": \"Sukurta\",\n    \"Default edit mode\": \"Numatytasis keitimo rėžimas\",\n    \"Fullscreen with preview\": \"Pilno ekrano peržiūra\",\n    \"Use encryption\": \"Naudoti šifravimą\",\n    \"Encryption parameters\": \"Šifravimo parametrai\",\n    \"Encryption Password\": \"Slaptažodis\",\n    \"Salt\": \"Druska\",\n    \"Random\": \"Atsitiktinis\",\n    \"Key size\": \"Rakto dydis\",\n    \"Strengthen by a factor of\": \"Sustiprinti daugikliu\",\n    \"Authentication strength\": \"Autentifikacijos stiprumas\",\n    \"Unlock\": \"Atrakinti\",\n    \"Your new encryption password\": \"Jūsų naujas slaptažodis\",\n    \"Your old encryption password\": \"Jūsų senas slaptažodis\",\n    \"Show sidebar\": \"Rodyti šoninę juostą\",\n    \"Previous\": \"Ankstesnis\",\n    \"Next\": \"Kitas\",\n    \"Navigation\": \"Navigacija\",\n    \"navigateTop\": \"Aukštyn\",\n    \"navigateBottom\": \"Žemyn\",\n    \"Jump\": \"Pereiti į\",\n    \"jumpInbox\": \"Gautuosius\",\n    \"jumpNotebook\": \"Užrašinių sąrašą\",\n    \"jumpFavorite\": \"Mėgstamiausių sąrašą\",\n    \"jumpRemoved\": \"Pašalintus užrašus\",\n    \"jumpOpenTasks\": \"Užrašus su nebaigtomis užduotimis\",\n    \"Actions\": \"Veiksmai\",\n    \"actionsEdit\": \"Keisti\",\n    \"actionsOpen\": \"Atidaryti\",\n    \"actionsRemove\": \"Šalinti\",\n    \"actionsRotateStar\": \"Perjungti žvaigždutę\",\n    \"App\": \"Programa\",\n    \"appCreateNote\": \"Sukurti naują užrašą\",\n    \"appSearch\": \"Ieškoti užrašų\",\n    \"appKeyboardHelp\": \"Klaviatūros pagalba\",\n    \"Change keybindings\": \"Keisti klaviatūros nustatymus\",\n    \"Donate\": \"Parama\",\n    \"Github page\": \"Github puslapis\",\n    \"Report bugs and issues here\": \"Pranešti apie klaidas ir problemas čia\",\n    \"Report bugs through email\": \"Praneši apie klaidas naudojant el. paštą\",\n    \"Credits\": \"Kreditai\",\n    \"List of contributors\": \"Prisidėjusių sąrašas\",\n    \"List of all used libraries\": \"Naudotų bibliotekų sąrašas\",\n    \"Are you sure?\": \"Esate tuo tikras?\",\n    \"You have unsaved changes\": \"Turite neišsaugotų pakeitimų.\",\n    \"Dropbox API key\": \"Dropbox API raktas\",\n    \"Required\": \"Privaloma\",\n    \"Optional\": \"Nebūtina\",\n    \"Language\": \"Kalba\",\n    \"Action\": \"Veiksmas\",\n    \"Select\": \"Pasirinkti\",\n    \"General\": \"Bendra\",\n    \"Encryption\": \"Šifravimas\",\n    \"Keybindings\": \"Spartieji klavišai\",\n    \"Sync\": \"Sinchronizacija\",\n    \"Profiles\": \"Profiliai\",\n    \"Import\": \"Importuoti\",\n    \"Transfer data\": \"Importuoti ir eksportuoti\",\n    \"Import settings\": \"Imporavimo nustatymai\",\n    \"Export settings\": \"Eksportavimo nustatymai\",\n    \"Wrong format\": \"Blogas formatas\",\n    \"useDefaultConfigs\": \"Naudoti nustatymus iš numatytojo profilio\",\n    \"File should be in json format\": \"Failas turi būti json formate\",\n    \"Close\": \"Uždaryti\",\n    \"Hyperlink\": \"Hypernuoroda\",\n    \"Editor\": \"Redaktorius\",\n    \"Preview\": \"Peržiūra\",\n    \"Download\": \"Atsisiųsti\",\n    \"Transfer everything\": \"Viskas\",\n    \"Find in page\": \"Rasti puslapyje\",\n    \"Other\": \"Kita\",\n    \"Default\": \"Numatyta\",\n    \"Modules\": \"Mobuliai\",\n    \"Import data\": \"Importuoti duomenis\",\n    \"Export data\": \"Eksportuoti duomenis\",\n    \"Enabled\": \"Įjungta\",\n    \"Disabled\": \"Išjungta\",\n    \"encryption\": {\n        \"provide password\": \"Prašome nurodyti šifravimui skirtą slaptažodį\",\n        \"change password\": \"Norėdami pakeisti slaptažodį įveskite jį čia\",\n        \"wait\": \"Prašome palaukti, kol bus užbaigtas šifravimas\",\n        \"error\": \"Šifravimo klaida\",\n        \"errorConfirm\": \"Klaida iššifruojant duomenis.\\r\\r If you changed encryption settings in another browser, **update your settings** in this browser too. Or try to import settings.\\r\\r And if you did not change anything, **try to login** again.\",\n        \"errorConfirmSettings\": \"Change encryption settings\",\n        \"errorConfirmAuth\": \"Bandyti dar kartą\",\n        \"backup\": {\n            \"title\": \"Atkūrimo duomenys\",\n            \"content\": \"Please, before proceeding to the next step, download your backup file. It contains decrypted previous data of changed profiles. Keep it in a safe place.\",\n            \"next\": \"Procceed without downloading the backup file\"\n        },\n        \"state\": {\n            \"decrypt\": \"Viskas iššifruojama\",\n            \"encrypt\": \"Viskas šifruojama\",\n            \"save\": \"Išsaugomi pakeitimai\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"Profile **{{profile}}** will be removed with all the data, including notes, tags, and notebooks. This action is irreversible!\",\n        \"type name\": \"Įvesti profilio pavadimą\"\n    },\n    \"files\": {\n        \"file-url\": \"Failo arba vaizdo URL\",\n        \"attach\": \"Pridėti failą\",\n        \"attachLink\": \"Pridėti kaip nuorodą\",\n        \"attachImage\": \"Pridėti kaip vaizdą\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Užrašai **{{title}}** bus perkelti į šiukšlinę.\",\n        \"confirm remove\": \"Užrašai **{{title}}** bus pašalinti **visam laikui**!\",\n        \"create and attach\": \"Sukurti naujus užrašus ir pridėti jų nuorodą\",\n        \"create\": \"Sukurti naujus užrašus\",\n        \"hyperlink-dialog\": \"Užrašų pavadinimas arba URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Pasirinkti užrašinę\",\n        \"add\": \"Pridėti naują užrašinę\",\n        \"edit\": \"Keisti užrašinę\",\n        \"name\": \"Prašome įvesti užrašinės pavadinimą\",\n        \"confirm remove\": \"Užrašinė **{{name}}** bus pašalinta **visam laikui**!\",\n        \"remove with notes\": \"Taip, pašalinti priskirtus užrašus\",\n        \"remove\": \"Taip, pašalinti\"\n    },\n    \"tags\": {\n        \"name\": \"Gairės pavadinimas yra būtinas\",\n        \"add\": \"Pridėti naują gairę\",\n        \"edit\": \"Keisti gairę\",\n        \"confirm remove\": \"Gairė **{{name}}** bus pašalinta **visam laikui**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Now you will be redirected to **Dropbox** authorization page.\\r> Please click **OK** button.\",\n        \"auth title\": \"Dropbox auth\",\n        \"api info 1\": \"You can have your own API key on\",\n        \"api info 2\": \"When you create a new app at Dropbox's Developer site you should keep in mind that:\",\n        \"api info li 1\": \"Type of app should be Dropbox API app\",\n        \"api info li 2\": \"Type of data should be Files and datastores\"\n    },\n    \"help\": {\n        \"firststart title\": \"Sveiki atvykę į Laverna\",\n        \"firststart import\": \"Jeigu naudojotės Laverna anksčiau, tai galite importuoti senuosius nustatymus spausdami žemiau esantį mytuką „Imporuoti“\",\n        \"firststart next\": \"Jeigu niekada nesinaudojote Laverna, spauskite mygtuką „Toliau“, kad pradėtumėte įdiegimo procesą.\",\n        \"firststart encryption\": \"Jeigu norite naudoti kodavimą, prašome nurodyti kodavimo slaptažodį.\",\n        \"firststart sync\": \"Kadangi mes nesaugome jokių duomenų mūsų serveriuose, jums reikia įgalinti sinchronizaciją su vienu iš adapterių, kuris galėtų peržiūrėti jūsų užrašus kituose įrenginiuose.\",\n        \"firststart backup\": \"Viskas yra beveik paruošta. Galite atsisiųsti savo nustatymų kopiją ir eiti į paskutinį žingsnį.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/lv/translation.json",
    "content": "{\n    \"Search\": \"Meklēt\",\n    \"All notes\": \"Piezīmes\",\n    \"Favourites\": \"Izlase\",\n    \"Favorite\": \"Izlase\",\n    \"Trash\": \"Atkritne\",\n    \"Open tasks\": \"Atvērtie uzdevumi\",\n    \"Notebooks\": \"Klades\",\n    \"Settings\": \"Iestatījumi\",\n    \"About\": \"Par\",\n    \"Save\": \"Saglabāt\",\n    \"Save & Exit\": \"Saglabāt un iziet\",\n    \"Cancel\": \"Atcelt\",\n    \"Full screen\": \"Pilnekrānā\",\n    \"Preview\": \"Priekšskatījums\",\n    \"Normal\": \"Parasts skats\",\n    \"Select notebook\": \"Izvēlēties kladi\",\n    \"Title\": \"Nosaukums\",\n    \"Submit\": \"Iesniegt\",\n    \"Tags\": \"Tagi\",\n    \"Tag\": \"Tags\",\n    \"Parent\": \"Vecāki\",\n    \"Root\": \"Sakne\",\n    \"Notebooks & tags\": \"Klades un tagi\",\n    \"Notebook\": \"Klade\",\n    \"Restore\": \"Atjaunot\",\n    \"Delete\": \"Izdzēst\",\n    \"New tag\": \"Jauns tags\",\n    \"Edit\": \"Rediģēt\",\n    \"Remove\": \"Noņemt\",\n    \"Forever\": \"Uz visiem laikiem\",\n    \"No\": \"Nē\",\n    \"Yes\": \"Jā\",\n    \"Basic\": \"Pamata\",\n    \"Cloud storage\": \"Mākoņglabātuve\",\n    \"Notes per page\": \"Piezīmes uz lapu\",\n    \"Sort notebooks\": \"Kārtot klades\",\n    \"Name\": \"Pēc nosaukuma\",\n    \"Created\": \"Pēc izveidošanas datuma\",\n    \"Default edit mode\": \"Noklusējuma rediģēšanas režīms\",\n    \"Fullscreen with preview\": \"Pilnekrāna ar priekšskatījumu\",\n    \"Use encryption\": \"Izmantot šifrēšanu\",\n    \"Encryption parameters\": \"Šifrēšanas parametri\",\n    \"Encryption Password\": \"Šifrēšanas parole\",\n    \"Salt\": \"Sāls\",\n    \"Random\": \"Nejauša\",\n    \"Key size\": \"Atslēgas garums\",\n    \"Strengthen by a factor of\": \"Stiprināt par koeficientu\",\n    \"Authentication strength\": \"Autentifikācijas stiprums\",\n    \"Unlock\": \"Atslēgt\",\n    \"Your new encryption password\": \"Jūsu jaunā šifra parole\",\n    \"Your old encryption password\": \"Jūsu vecā šifra parole\",\n    \"Show sidebar\": \"Rādīt sānjoslu\",\n    \"Previous\": \"Iepriekšējā\",\n    \"Next\": \"Nākamā\",\n    \"Navigation\": \"Navigācija\",\n    \"navigateTop\": \"Uz augšu\",\n    \"navigateBottom\": \"Uz leju\",\n    \"Jump\": \"Pārlēkt\",\n    \"jumpInbox\": \"Iet uz iesūtni\",\n    \"jumpNotebook\": \"Iet uz klažu sarakstu\",\n    \"jumpFavorite\": \"Iet uz piezīmju izlasi\",\n    \"jumpRemoved\": \"Iet uz noņemtajām piezīmēm\",\n    \"jumpOpenTasks\": \"Iet uz piezīmēm ar atvērtiem uzdevumiem\",\n    \"Actions\": \"Darbības\",\n    \"actionsEdit\": \"Rediģēt\",\n    \"actionsOpen\": \"Atvērt\",\n    \"actionsRemove\": \"Noņemt\",\n    \"actionsRotateStar\": \"Rotēt zvaigzi\",\n    \"App\": \"Aplikācija\",\n    \"appCreateNote\": \"Izveidot jaunu piezīmi\",\n    \"appSearch\": \"Meklēt piezīmi\",\n    \"appKeyboardHelp\": \"Tastatūras palīdzība\",\n    \"Change keybindings\": \"Mainīt taustiņu iestatījumus\",\n    \"Donate\": \"Ziedot\",\n    \"Github page\": \"GitHub lapa\",\n    \"Report bugs and issues here\": \"Ziņot par kļūdām un problēmām šeit\",\n    \"Report bugs through email\": \"Ziņot par kļūdām caur e-pastu\",\n    \"Credits\": \"Titri\",\n    \"List of contributors\": \"Līdzstrādnieku saraksts\",\n    \"List of all used libraries\": \"Izmantoto koda bibliotēku saraksts\",\n    \"Are you sure?\": \"Vai esat pārliecināts/-a?\",\n    \"You have unsaved changes\": \"Jums ir nesaglabātas izmaiņas.\",\n    \"Dropbox API key\": \"Dropbox API atslēga\",\n    \"Required\": \"Obligāts\",\n    \"Optional\": \"Neobligāts\",\n    \"Language\": \"Valoda\",\n    \"Action\": \"Darbība\",\n    \"Select\": \"Izvēlēties\",\n    \"General\": \"Vispārēji\",\n    \"Encryption\": \"Šifrēšana\",\n    \"Keybindings\": \"Taustiņu iestatījumi\",\n    \"Sync\": \"Sinhronizēt\",\n    \"Profiles\": \"Profili\",\n    \"Import\": \"Importēt\",\n    \"Transfer data\": \"Importēt un eksportēt\",\n    \"Import settings\": \"Importa iestatījumi\",\n    \"Export settings\": \"Eksporta iestatījumi\",\n    \"Wrong format\": \"Nepareizs formāts\",\n    \"useDefaultConfigs\": \"Izmantot iestatījumus no noklusējuma profila\",\n    \"File should be in json format\": \"Failam vajag būt JSON formātā\",\n    \"Close\": \"Aizvērt\",\n    \"Hyperlink\": \"Hipersaite\",\n    \"Editor\": \"Redaktors\",\n    \"Preview\": \"Priekšskatījums\",\n    \"Download\": \"Lejupielāde\",\n    \"encryption\": {\n        \"wait\": \"Lūdzu uzgaidīt, līdz šifrēšana būs pabeigta\",\n        \"error\": \"Šifrēšanas kļūda\",\n        \"errorConfirm\": \"Kļūda šifrējot datus.\\r\\r Ja Jūs mainījāt šifrēšanas iestātījumus citā pārlūkā, **atjauniniet Jūsu iestatījumus** arī šajā pārlūkā vai arī mēģiniet importēt iestatījumus.\\r\\r Ja tas neko nemaina, **mēģiniet ielogoties** atkal.\",\n        \"errorConfirmSettings\": \"Mainīt šifrēšanas parametrus\",\n        \"errorConfirmAuth\": \"Mēģiniet atkal\",\n        \"backup\": {\n            \"title\": \"Datu rezerves kopija\",\n            \"content\": \"Lūdzu, pirms ejiet uz nākamo soli, lejupielādējiet Jūsu rezerves kopiju. Tā satur atšifrētus iepriekšējos datus un mainītos profilus. Glabājiet to drošā vietā.\",\n            \"next\": \"Turpināt, nelejupielādējot rezerves kopiju\"\n        },\n        \"state\": {\n            \"decrypt\": \"Atšifrē visu\",\n            \"encrypt\": \"Šifrē visu\",\n            \"save\": \"Saglabā izmaiņas\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"Profils **{{profile}}** tiks noņemts ar visiem datiem, tai skaitā piezīmēm, tagiem un piezīmju grāmatiņām. Šī darbība ir neatgriezeniska!\",\n        \"type name\": \"Ierakstiet profila vārdu\"\n    },\n    \"files\": {\n        \"file-url\": \"Faila vai attēla URL\",\n        \"attach\": \"Pievienot failu\",\n        \"attachLink\": \"Pievienot kā saiti\",\n        \"attachImage\": \"Pievienot kā attēlu\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Piezīme **{{title}}** nonāks atkritnē.\",\n        \"confirm remove\": \"Piezīme**{{title}}** tiks izdzēsta **uz visiem laikiem**!\",\n        \"create and attach\": \"Izveidot jaunu piezīmi un pievienot tās saiti\",\n        \"create\": \"Izveidot jaunu piezīmi\",\n        \"hyperlink-dialog\": \"Piezīmes vai URL nosaukums\"\n    },\n    \"notebooks\": {\n        \"select\": \"Izvēlēties kladi\",\n        \"add\": \"Pievienot jaunu kladi\",\n        \"edit\": \"Rediģēt kladi\",\n        \"name\": \"Lūdzu, sniedziet kladei nosaukumu\",\n        \"confirm remove\": \"Klade **{{name}}** tiks izdzēsta **uz visiem laikiem**!\",\n        \"remove with notes\": \"Jā, izdzēst ar pievienotajām piezīmēm\",\n        \"remove\": \"Jā, izdzēst\"\n    },\n    \"tags\": {\n        \"name\": \"Taga nosaukums ir obligāts\",\n        \"add\": \"Pievienot jaunu tagu\",\n        \"edit\": \"Rediģēt tagu\",\n        \"confirm remove\": \"Tags **{{name}}** tiks dzēsts **uz visiem laikiem**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Tagad Jūs tiksiet novirzīts/-a uz **Dropbox** autorizācijas lapu.\\r> Lūdzu noklikšķiniet **OK** pogu.\",\n        \"auth title\": \"Dropbox autentifikācija\"\n    },\n    \"help\": {\n        \"firststart title\": \"Laipni lūgti Laverna\",\n        \"firststart import\": \"Ja Jūs jau esat lietojis/-usi Laverna iepriekš, Jūs varat importēt Jūsu iepriekšējos iestatījumu klikšķinot 'importēt' pogu lejup.\",\n        \"firststart next\": \"Ja nekad neesat lietojis/-usi Laverna līdz šim, klikšķiniet uz 'nākamā' pogas, lai sāktu instalācijas procesu.\",\n        \"firststart encryption\": \"Ja vēlaties izmantot šifrēšanu, lūdzu sniedziet šifrēšans paroli.\",\n        \"firststart sync\": \"Tā kā mēs neglabājam jebkādus datus mūsu serveros, Jums vajag ieslēgt sinhronizāciju ar vienu no no adapteriem lai varētu skatīt savas piezīmes arī uz citām ierīcēm.\",\n        \"firststart backup\": \"Viss ir gandrīz gatavs. Jūs varat lejupielādēt savu iestatījumu rezerves kopiju un pāriet uz nākamo soli.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/mr_in/translation.json",
    "content": "{\n    \"en\": \"इंग्रजी\",\n    \"ru\": \"रशियन\",\n    \"nl\": \"डच\",\n    \"fr\": \"फ्रेंच\",\n    \"pt_br\": \"ब्राझिलियन पोर्तुगीज\",\n    \"eo\": \"एस्पेरान्तो\",\n    \"es\": \"स्पॅनिश\",\n    \"de\": \"जर्मन\",\n    \"de_ch\": \"स्विस जर्मन\",\n    \"se\": \"स्वीडिश\",\n    \"el\": \"ग्रीक\",\n    \"nb\": \"नॉर्वेजियन (बोकमाल)\",\n    \"nn\": \"नॉर्वेजियन (न्योर्स्क)\",\n    \"bs_ba\": \"बोस्नियन\",\n    \"hi_in\": \"हिंदी\",\n    \"mr_in\": \"मराठी\",\n    \"zh_cn\": \"सरलीकृत चीनी\",\n    \"Search\": \"शोधा\",\n    \"All notes\": \"सर्व नोट्स\",\n    \"Favourites\": \"आवडत्या\",\n    \"Favorite\": \"आवडता\",\n    \"Trash\": \"कचरा\",\n    \"Notebooks\": \"नोटबुक्स\",\n    \"Settings\": \"सेटिंग्ज\",\n    \"About\": \"विषयक\",\n    \"Save\": \"जतन करा\",\n    \"Save & Exit\": \"जतन करून बंद करा\",\n    \"Cancel\": \"रद्द करा\",\n    \"Full screen\": \"पूर्ण स्क्रीन\",\n    \"Preview\": \"पूर्वावलोकन\",\n    \"Normal\": \"सामान्य\",\n    \"Select notebook\": \"नोटबुक निवडा\",\n    \"Title\": \"शीर्षक\",\n    \"Submit\": \"सबमिट करा\",\n    \"Tags\": \"टॅग्ज\",\n    \"Tag\": \"टॅग\",\n    \"Parent\": \"पूर्वज\",\n    \"Root\": \"मूळे\",\n    \"Notebooks & tags\": \"नोटबुक्स आणि टॅग्ज\",\n    \"Notebook\": \"नोटबुक\",\n    \"Restore\": \"पुनर्संचयित करा\",\n    \"Delete\": \"मिटवा\",\n    \"New tag\": \"नवीन टॅग\",\n    \"Edit\": \"संपादन करा\",\n    \"Remove\": \"काढा\",\n    \"Forever\": \"कायमचे\",\n    \"No\": \"नाही\",\n    \"Yes\": \"होय\",\n    \"Basic\": \"मूलभूत\",\n    \"Cloud storage\": \"मेघ संचय\",\n    \"Notes per page\": \"नोट्स प्रत्येक पानावर\",\n    \"Sort notebooks\": \"नोटबुक वर्गीकरण करा\",\n    \"Name\": \"नाव\",\n    \"Created\": \"निर्माण\",\n    \"Default edit mode\": \"मुलभूत संपादन मोड\",\n    \"Fullscreen with preview\": \"पूर्वावलोकन सह पूर्णस्क्रीन\",\n    \"Use encryption\": \"एनक्रिप्शन वापरा\",\n    \"Encryption parameters\": \"एनक्रिप्शनचे मापदंड\",\n    \"Encryption Password\": \"एन्क्रिप्शनचे पासवर्ड\",\n    \"Salt\": \"साल्त\",\n    \"Random\": \"यादृच्छिक करा\",\n    \"Key size\": \"की साईझ\",\n    \"Strengthen by a factor of\": \"शक्तीचा घटक\",\n    \"Authentication strength\": \"प्रमाणीकरण शक्ती\",\n    \"Unlock\": \"अनलॉक\",\n    \"Your new encryption password\": \"आपले नवीन एनक्रिप्शनचे पासवर्ड\",\n    \"Your old encryption password\": \"आपल्या जुन्या एनक्रिप्शनचे पासवर्ड\",\n    \"Please wait until the encryption will be completed\": \"एन्क्रिप्शन पूर्ण होईल तोपर्यंत प्रतीक्षा करा\",\n    \"Shortcuts\": \"शॉर्टकत्स\",\n    \"Newer\": \"नवीन\",\n    \"Older\": \"जुने\",\n    \"Navigation\": \"नेव्हिगेशन\",\n    \"navigateTop\": \"सुरवातीला\",\n    \"navigateBottom\": \"तळाशी\",\n    \"Jump\": \"उडी\",\n    \"jumpInbox\": \"इनबॉक्सला जा\",\n    \"jumpNotebook\": \"नोटबुक यादीला जा\",\n    \"jumpFavorite\": \"आवडत्या नोट्सना जा\",\n    \"jumpRemoved\": \"काढलेल्या नोट्सना जा\",\n    \"Actions\": \"क्रिया\",\n    \"actionsEdit\": \"संपादन करा\",\n    \"actionsOpen\": \"उघडा\",\n    \"actionsRemove\": \"काढा\",\n    \"actionsRotateStar\": \"तारा फिरवा\",\n    \"App\": \"अनुप्रयोग\",\n    \"appCreateNote\": \"नवीन नोट तयार करा\",\n    \"appSearch\": \"नोट शोधा\",\n    \"appKeyboardHelp\": \"कीबोर्ड मदत\"\n}\n"
  },
  {
    "path": "app/locales/nb/translation.json",
    "content": "{\n  \"About\": \"Om\",\n  \"Actions\": \"Handlinger\",\n  \"All notes\": \"Alle notat\",\n  \"App\": \"Applikasjon\",\n  \"Authentication strength\": \"Autentiseringsstyrke\",\n  \"Basic\": \"Enkel\",\n  \"navigateBottom\": \"Ned\",\n  \"Cancel\": \"Avbryt\",\n  \"Cloud storage\": \"Skylagring\",\n  \"appCreateNote\": \"Lag nytt notat\",\n  \"Default edit mode\": \"Standard redigeringsmodus\",\n  \"Delete\": \"Slett\",\n  \"Edit\": \"Rediger\",\n  \"Encryption Password\": \"Krypteringspassord\",\n  \"Encryption parameters\": \"Krypteringsparametere\",\n  \"Favorite\": \"Favoritt\",\n  \"Favourites\": \"Favoritter\",\n  \"Forever\": \"Alltid\",\n  \"Full screen\": \"Fullskjerm\",\n  \"Fullscreen with preview\": \"Fullskjerm med forhåndsvisning\",\n  \"jumpFavorite\": \"Gå til favorittnotat\",\n  \"jumpInbox\": \"Gå til innboks\",\n  \"jumpNotebook\": \"Gå til notatblokkliste\",\n  \"jumpRemoved\": \"Gå til fjernede notat\",\n  \"Jump\": \"Hopp\",\n  \"Key size\": \"Nøkkelstørrelse\",\n  \"appKeyboardHelp\": \"Tastaturhjelp\",\n  \"Navigation\": \"Navigering\",\n  \"New tag\": \"Ny emneknagg\",\n  \"Older\": \"Neste\",\n  \"No\": \"Nei\",\n  \"Normal\": \"Vanlig\",\n  \"Notebook\": \"Notatblokk\",\n  \"Notebooks & tags\": \"Notatblokker & emneknagger\",\n  \"Notebooks\": \"Notatblokker\",\n  \"Notes per page\": \"Notat per side\",\n  \"actionsOpen\": \"Åpne\",\n  \"Parent\": \"Forelder\",\n  \"Please wait until the encryption will be completed\": \"Vennligst vent til krypteringen er ferdig\",\n  \"Preview\": \"Forhåndsvis\",\n  \"Newer\": \"Forrige\",\n  \"Random\": \"Tilfeldig\",\n  \"Remove\": \"Fjern\",\n  \"Restore\": \"Gjenopprett\",\n  \"Root\": \"Rot\",\n  \"actionsRotateStar\": \"Vend stjerne\",\n  \"Salt\": \"Salt\",\n  \"Save & Exit\": \"Lagre og avslutt\",\n  \"Save\": \"Lagre\",\n  \"appSearch\": \"Søk etter notat\",\n  \"Search\": \"Søk\",\n  \"Select notebook\": \"Velg notatblokk\",\n  \"Settings\": \"Innstillinger\",\n  \"Shortcuts\": \"Snarveier\",\n  \"Strengthen by a factor of\": \"Styrk med faktor\",\n  \"Submit\": \"Legg inn\",\n  \"Tag\": \"Emneknagg\",\n  \"Tags\": \"Emneknagger\",\n  \"Title\": \"Tittel\",\n  \"navigateTop\": \"Opp\",\n  \"Trash\": \"Papirkurv\",\n  \"Unlock\": \"Lås opp\",\n  \"Use encryption\": \"Bruk kryptering\",\n  \"Yes\": \"Ja\",\n  \"Your new encryption password\": \"Ditt nye krypteringspassord\",\n  \"Your old encryption password\": \"Ditt gamle krypteringspassord\",\n  \"en\": \"Engelsk\",\n  \"fr\": \"Fransk\",\n  \"nl\": \"Nederlandsk\",\n  \"pt_br\": \"Brasiliansk portugisisk\",\n  \"ru\": \"Russisk\"\n}\n"
  },
  {
    "path": "app/locales/nl/translation.json",
    "content": "{\n    \"Search\": \"Zoeken\",\n    \"All notes\": \"Inbox\",\n    \"Favourites\": \"Favorieten\",\n    \"Trash\": \"Prullenbak\",\n    \"Notebooks\": \"Notitieboeken\",\n    \"Settings\": \"Instellingen\",\n    \"About\": \"Over\",\n    \"Save\": \"Opslaan\",\n    \"Cancel\": \"Annuleren\",\n    \"Full screen\": \"Volledig scherm\",\n    \"Preview\": \"Voorbeeld\",\n    \"Normal\": \"Normaal\",\n    \"Select notebook\": \"Selecteer notitieblok\",\n    \"Title\": \"Titel\",\n    \"Submit\": \"Toepassen\",\n    \"Tags\": \"Labels\",\n    \"Tag\": \"Label\",\n    \"Parent\": \"Parent\",\n    \"Root\": \"Root\",\n    \"Notebooks & tags\": \"Notitieblokken & labels\",\n    \"Notebook\": \"Notitie\",\n    \"Restore\": \"Herstellen\",\n    \"Delete\": \"Verwijderen\",\n    \"New tag\": \"Nieuw label\",\n    \"Edit\": \"Aanpassen\",\n    \"Remove\": \"Weghalen\",\n    \"Forever\": \"Altijd\",\n    \"No\": \"Nee\",\n    \"Yes\": \"Ja\",\n    \"Basic\": \"Basis\",\n    \"Cloud storage\": \"Cloud opslag\",\n    \"Notes per page\": \"Notities per pagina\",\n    \"Default edit mode\": \"Standaard bewerkingsmodus\",\n    \"Fullscreen with preview\": \"Volledig scherm met voorbeeld\",\n    \"Use encryption\": \"Gebruik encryptie\",\n    \"Encryption parameters\": \"Encryptie waarden\",\n    \"Encryption Password\": \"Encryptie Wachtwoord\",\n    \"Salt\": \"Zout\",\n    \"Random\": \"Willekeurig\",\n    \"Key size\": \"Sleutel grootte\",\n    \"Strengthen by a factor of\": \"Versterken met de factor\",\n    \"Authentication strength\": \"Authenticatie sterkte\",\n    \"Unlock\": \"Ontgrendelen\",\n    \"Your new encryption password\": \"Uw nieuwe encryptie wachtwoord\",\n    \"Your old encryption password\": \"Uw oude encryptie wachtwoord\",\n    \"Please wait until the encryption will be completed\": \"Wacht alstublieft tot de versleuteling voltooid is\",\n    \"Shortcuts\": \"Snelkoppelingen\",\n    \"Newer\": \"Vorige\",\n    \"Older\": \"Volgende\",\n    \"Navigation\": \"Navigatie\",\n    \"navigateTop\": \"Boven\",\n    \"navigateBottom\": \"Onder\",\n    \"Jump\": \"Spring\",\n    \"jumpInbox\": \"Ga naar inbox\",\n    \"jumpNotebook\": \"Ga naar notitieblok lijst\",\n    \"jumpFavorite\": \"Ga naar favorite notities\",\n    \"jumpRemoved\": \"Ga naar verwijderde notities\",\n    \"Actions\": \"Acties\",\n    \"actionsOpen\": \"Open\",\n    \"actionsRotateStar\": \"Draai Ster\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Maak nieuwe notitie\",\n    \"appSearch\": \"Zoek notitie\",\n    \"appKeyboardHelp\": \"Toetsenbord help\"\n}\n"
  },
  {
    "path": "app/locales/nn/translation.json",
    "content": "{\n    \"en\" : \"Engelsk\",\n    \"ru\" : \"Russisk\",\n    \"nl\" : \"Nederlandsk\",\n    \"fr\" : \"Fransk\",\n    \"pt_br\" : \"Brasiliansk portugisisk\",\n    \"Search\": \"Søk\",\n    \"All notes\": \"Alle notat\",\n    \"Favourites\": \"Favorittar\",\n    \"Favorite\": \"Favoritt\",\n    \"Trash\": \"Papirkorga\",\n    \"Notebooks\": \"Notatblokkar\",\n    \"Settings\": \"Innstillingar\",\n    \"About\": \"Om\",\n    \"Save\": \"Lagra\",\n    \"Save & Exit\": \"Lagra og avslutt\",\n    \"Cancel\": \"Avbryt\",\n    \"Full screen\": \"Fullskjerm\",\n    \"Preview\": \"Førehandsvis\",\n    \"Normal\": \"Vanleg\",\n    \"Select notebook\": \"Vel notatblokk\",\n    \"Title\": \"Tittel\",\n    \"Submit\": \"Legg inn\",\n    \"Tags\": \"Emneknaggar\",\n    \"Tag\": \"Emneknagg\",\n    \"Parent\": \"Forelder\",\n    \"Root\": \"Rot\",\n    \"Notebooks & tags\": \"Notatblokkar & emneknaggar\",\n    \"Notebook\": \"Notatblokk\",\n    \"Restore\": \"Gjenopprett\",\n    \"Delete\": \"Slett\",\n    \"New tag\": \"Ny emneknagg\",\n    \"Edit\": \"Rediger\",\n    \"Remove\": \"Fjern\",\n    \"Forever\": \"Alltid\",\n    \"No\": \"Nei\",\n    \"Yes\": \"Ja\",\n    \"Basic\": \"Enkel\",\n    \"Cloud storage\": \"Skylagring\",\n    \"Notes per page\": \"Notat per side\",\n    \"Default edit mode\": \"Standard redigeringsmodus\",\n    \"Fullscreen with preview\": \"Fullskjerm med førehandsvisning\",\n    \"Use encryption\": \"Bruk kryptering\",\n    \"Encryption parameters\": \"Krypteringsparameterar\",\n    \"Encryption Password\": \"Krypteringspassord\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Tilfeldig\",\n    \"Key size\": \"Nøkkelstorleik\",\n    \"Strengthen by a factor of\": \"Styrk med faktor\",\n    \"Authentication strength\": \"Autentiseringsstyrke\",\n    \"Unlock\": \"Lås opp\",\n    \"Your new encryption password\": \"Det nye krypteringspassordet ditt\",\n    \"Your old encryption password\": \"Det gamle krypteringspassordet ditt\",\n    \"Please wait until the encryption will be completed\": \"Ver vennleg og vent til krypteringa er ferdig\",\n    \"Shortcuts\": \"Snarvegar\",\n    \"Newer\": \"Førre\",\n    \"Older\": \"Neste\",\n    \"Navigation\": \"Navigering\",\n    \"navigateTop\": \"Opp\",\n    \"navigateBottom\": \"Ned\",\n    \"Jump\": \"Hopp\",\n    \"jumpInbox\": \"Gå til innboks\",\n    \"jumpNotebook\": \"Gå til notatblokkliste\",\n    \"jumpFavorite\": \"Gå til favorittnotat\",\n    \"jumpRemoved\": \"Gå til fjerna notat\",\n    \"Actions\": \"Handlingar\",\n    \"actionsOpen\": \"Opna\",\n    \"actionsRotateStar\": \"Vend stjerne\",\n    \"App\": \"Applikasjon\",\n    \"appCreateNote\": \"Lag nytt notat\",\n    \"appSearch\": \"Søk etter notat\",\n    \"appKeyboardHelp\": \"Tastaturhjelp\"\n}\n"
  },
  {
    "path": "app/locales/oc/translation.json",
    "content": "{\n    \"Search\": \"Cercar\",\n    \"All notes\": \"Totas las nòtas\",\n    \"Favourites\": \"Favorits\",\n    \"Favorite\": \"Favorit\",\n    \"Trash\": \"Escobilhièr\",\n    \"Open tasks\": \"Prètzfaits en escritura\",\n    \"Notebooks\": \"Blòts\",\n    \"Settings\": \"Paramètres\",\n    \"About\": \"A prepaus\",\n    \"Save\": \"Salvar\",\n    \"Save & Exit\": \"Salvar & Sortir\",\n    \"Cancel\": \"Anullar\",\n    \"Full screen\": \"Plen ecran\",\n    \"Preview\": \"Apercebut\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Seleccionar un blòt\",\n    \"Title\": \"Títol\",\n    \"Submit\": \"Mandar\",\n    \"Tags\": \"Etiquetas\",\n    \"Tag\": \"Etiqueta\",\n    \"Parent\": \"Parent\",\n    \"Root\": \"Raiç\",\n    \"Notebooks & tags\": \"Blòts & Etiquetas\",\n    \"Notebook\": \"Blòt\",\n    \"Restore\": \"Restablir\",\n    \"Delete\": \"Suprimir\",\n    \"New tag\": \"Novèla etiqueta\",\n    \"Edit\": \"Modificar\",\n    \"Remove\": \"Levar\",\n    \"Forever\": \"Per totjorn\",\n    \"No\": \"Non\",\n    \"Yes\": \"Òc\",\n    \"Basic\": \"Basic\",\n    \"Cloud storage\": \"Enregistrament alonhat sul Cloud\",\n    \"Notes per page\": \"Nòtas per pagina\",\n    \"Sort notebooks\": \"Triar los blòts\",\n    \"Name\": \"Nom\",\n    \"Created\": \"Creat\",\n    \"Default edit mode\": \"Mòde d'edicion per defaut\",\n    \"Fullscreen with preview\": \"Plen ecran amb apercebut\",\n    \"Use encryption\": \"Utilizar lo chiframent\",\n    \"Encryption parameters\": \"Paramètres de chiframent\",\n    \"Encryption Password\": \"Senhal de chiframent\",\n    \"Salt\": \"Sal\",\n    \"Random\": \"Aleatòri\",\n    \"Key size\": \"Talha de la clau\",\n    \"Strengthen by a factor of\": \"Renfortir per un factor de\",\n    \"Authentication strength\": \"Autentificacion fòrta\",\n    \"Unlock\": \"Desclavar\",\n    \"Your new encryption password\": \"Vòstre novèl senhal\",\n    \"Your old encryption password\": \"Vòstre ancian senhal\",\n    \"Show sidebar\": \"Afichar la barra laterala\",\n    \"Previous\": \"Precedent\",\n    \"Next\": \"Seguent\",\n    \"Navigation\": \"Navigacion\",\n    \"navigateTop\": \"Naut\",\n    \"navigateBottom\": \"Bas\",\n    \"Jump\": \"Sautar\",\n    \"jumpInbox\": \"Anar a la bóstia de recepcion\",\n    \"jumpNotebook\": \"Anar a la lista de blòts\",\n    \"jumpFavorite\": \"Anar a las nòtas preferidas\",\n    \"jumpRemoved\": \"Anar a las nòtas levadas\",\n    \"jumpOpenTasks\": \"Anar a los prètzfaits en escritura\",\n    \"Actions\": \"Accions\",\n    \"actionsEdit\": \"Modificar\",\n    \"actionsOpen\": \"Dobrir\",\n    \"actionsRemove\": \"Levar\",\n    \"actionsRotateStar\": \"Ajustar/Levar dels favorits\",\n    \"App\": \"Aplicacion\",\n    \"appCreateNote\": \"Crear una novèla nòta\",\n    \"appSearch\": \"Cercar una nòta\",\n    \"appKeyboardHelp\": \"Ajuda pel clavièr\",\n    \"Change keybindings\": \"Cambiar los acorchis de clavièr\",\n    \"Donate\": \"Far un don\",\n    \"Github page\": \"Pagina Github\",\n    \"Report bugs and issues here\": \"Senhalar los bugs e problèmas aquí\",\n    \"Report bugs through email\": \"Senhalar los bugs per corrièl\",\n    \"Credits\": \"Crèdits\",\n    \"List of contributors\": \"Lista dels contributors\",\n    \"List of all used libraries\": \"Lista de totas las bibliotècas utilizadas\",\n    \"Are you sure?\": \"Sètz segur ?\",\n    \"You have unsaved changes\": \"Avètz pas salvat los cambiaments.\",\n    \"Dropbox API key\": \"Clau de l'API Dropbox\",\n    \"Required\": \"Requesit\",\n    \"Optional\": \"Opcional\",\n    \"Language\": \"Lenga\",\n    \"Action\": \"Accion\",\n    \"Select\": \"Seleccionar\",\n    \"General\": \"General\",\n    \"Encryption\": \"Chiframent\",\n    \"Keybindings\": \"Acorchis de clavièr\",\n    \"Sync\": \"Sincronizar\",\n    \"Profiles\": \"Perfils\",\n    \"Import\": \"Importar\",\n    \"Transfer data\": \"Importar & Exportar\",\n    \"Transfer settings\": \"Paramètres de transferiment\",\n    \"Import settings\": \"Importar los paramètres\",\n    \"Export settings\": \"Exportar los paramètres\",\n    \"Wrong format\": \"Marrit format\",\n    \"useDefaultConfigs\": \"Utilizar los paramètres del perfil per defaut\",\n    \"File should be in json format\": \"Cal que lo fichièr siá al format json\",\n    \"Close\": \"Tampar\",\n    \"Hyperlink\": \"Iperligam\",\n    \"Editor\": \"Editor\",\n    \"Preview\": \"Apercebut\",\n    \"Download\": \"Telecargar\",\n    \"Transfer everything\": \"Tot\",\n    \"Find in page\": \"Trobar dins la pagina\",\n    \"Other\": \"Autre\",\n    \"Default\": \"Per defaut\",\n    \"Modules\": \"Moduls\",\n    \"Import data\": \"Importar las donadas\",\n    \"Export data\": \"Exportar las donadas\",\n    \"Enabled\": \"Activat\",\n    \"Disabled\": \"Desactivat\",\n    \"Untitled\": \"Cap de nom\",\n    \"Line of\": \"Linha {{currentLine}} sus {{numberOfLines}}\",\n    \"Drop files\": \"Pausar los fichièrs aquí per los enviar\",\n    \"Spaces per indent\": \"Espacis pels alineas\",\n    \"Sort notes\": \"Triar las nòtas per\",\n    \"Updated date\": \"Data de modificacion\",\n    \"Created date\": \"Data de creacion\",\n    \"Text editor\": \"Editor de tèxtes\",\n    \"Vim\": \"Vim\",\n    \"Emacs\": \"Emacs\",\n    \"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"Mercés de fornir vòstre senhal\",\n        \"change password\": \"Marcatz vòstre senhal aquí per o cambiar\",\n        \"wait\": \"Mercés d'esperar dusca que lo chiframent siá acabat\",\n        \"error\": \"Error al moment de chifrar\",\n        \"errorConfirm\": \"Error pendent lo deschiframent de las donadas.\\r\\r S'avètz cambiat los paramètres de chiframent sus un navigator mai, **metètz a jorn vòstres paramètres** sus aiceste navigator tanben. O ensajatz d'importar los paramètres.\\r\\r E s'avètz pas cambiat res, **ensajatz de vos connectar** de nòu.\",\n        \"errorConfirmSettings\": \"Cambiar los paramètres de chiframent\",\n        \"errorConfirmAuth\": \"Tornatz ensajar\",\n        \"backup\": {\n            \"title\": \"Donadas de salvagarda\",\n            \"content\": \"Mercés de telecargar vòstre fichièr de salvagarda abans de passar a l'etapa seguenta. Conten las donadas deschifradas d'abans los cambiaments de perfils. Gardatz-lo en un luòc segur.\",\n            \"next\": \"Contunhar sens telecargar lo fichièr de salvament\"\n        },\n        \"state\": {\n            \"decrypt\": \"O deschifrar tot\",\n            \"encrypt\": \"O chifrar tot\",\n            \"save\": \"Enregistrament dels cambiaments\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"Nom del perfil\",\n        \"confirm remove\": \"Lo perfil **{{profile}}** serà levat amb totas las donadas, inclús las nòtas, etiquetas e los blòts. Aquesta accion es irreversibla !\",\n        \"type name\": \"Marcatz lo nom del perfil\"\n    },\n    \"files\": {\n        \"file-url\": \"URL d'un fichièr o d'un imatge\",\n        \"attach\": \"Ligar coma un fichièr\",\n        \"attachLink\": \"Ligar coma un ligam\",\n        \"attachImage\": \"Ligar coma un imatge\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"La nòta **{{title}}** serà desplaçada a l'escobilhièr.\",\n        \"confirm remove\": \"La nòta **{{title}}** serà levada **per totjorn** !\",\n        \"create and attach\": \"Crear una nòta novèla e ligar sos ligams\",\n        \"create\": \"Crear una nòta novèla\",\n        \"hyperlink-dialog\": \"Títol d'una nòta o una URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Seleccionatz un blòt\",\n        \"add\": \"Ajustar un blòt novèl\",\n        \"edit\": \"Modificar un blòt\",\n        \"name\": \"Mercés de donar un nom al blòt\",\n        \"confirm remove\": \"Lo blòt **{{name}}** serà levat **per totjorn** !\",\n        \"remove with notes\": \"Òc-ben, levatz-lo amb las nòtas ligadas\",\n        \"remove\": \"Òc-ben, levatz-lo\"\n    },\n    \"tags\": {\n        \"name\": \"Lo nom de l'etiqueta es requesit\",\n        \"add\": \"Ajustar una novèla etiqueta\",\n        \"edit\": \"Modificar una etiqueta\",\n        \"confirm remove\": \"L'etiqueta **{{name}}** serà levada **per totjorn** !\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Seretz menat a la pagina d'autorizacion de **Dropbox**.\\r> Mercés de far clic sul boton **OK**.\",\n        \"auth title\": \"Dropbox auth\",\n        \"api info 1\": \"Podètz aver vòstra pròpria clau d'API\",\n        \"api info 2\": \"Quand creatz una novèla app sul site Dropbox's Developer vos cal gardar al cap aquò : \",\n        \"api info li 1\": \"Tip d'API deu èsser **Dropbox API**\",\n        \"api info li 2\": \"Tip d'accès deu èsser **App Folder**\"\n    },\n    \"help\": {\n        \"firststart title\": \"Benvengut a Laverna\",\n        \"firststart import\": \"S'avètz ja utilizat Laverna, podètz importar vòstres paramètres d'abans en clicant sul boton 'importar' çaijós.\",\n        \"firststart next\": \"S'avètz pas jamai utilizat Laverna, clicatz sul boton 'seguent' per començar lo  processús d'installacion.\",\n        \"firststart encryption\": \"Se volètz utilizar lo chiframent, mercés de fornir un senhal de chiframent.\",\n        \"firststart sync\": \"Ja que gardem pas cap de donadas sus nòstres servidors, vos cal activar la sincronizacion amb un de los adaptadors per poder veire vòstras nòtas en d'aparelhs mai.\",\n        \"firststart backup\": \"Tot es gaireben prèst. Podètz telecargar vòstra salvagarda de paramètres e anar a la darrièra etapa.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/pl/translation.json",
    "content": "{\n    \"Search\": \"Szukaj...\",\n    \"All notes\": \"Notatki\",\n    \"Favourites\": \"Oznaczone\",\n    \"Favorite\": \"Oznaczone\",\n    \"Trash\": \"Kosz\",\n    \"Open tasks\": \"Otwarte zadania\",\n    \"Notebooks\": \"Notesy\",\n    \"Settings\": \"Ustawienia\",\n    \"About\": \"Laverna - informacje\",\n    \"Save\": \"Zapisz\",\n    \"Save & Exit\": \"Zapisz i wyjdź\",\n    \"Cancel\": \"Anuluj\",\n    \"Full screen\": \"Pełen ekran\",\n    \"Preview\": \"Podgląd\",\n    \"Normal\": \"Standardowy\",\n    \"Select notebook\": \"Wybierz notes\",\n    \"Title\": \"Tytuł\",\n    \"Submit\": \"Zapisz zmiany\",\n    \"Tags\": \"Etykiety\",\n    \"Tag\": \"Etykieta\",\n    \"Parent\": \"Nadrzędny\",\n    \"Root\": \"Root\",\n    \"Notebooks & tags\": \"Notesy\",\n    \"Notebook\": \"Notes\",\n    \"Restore\": \"Przywróć\",\n    \"Delete\": \"Usuń\",\n    \"New tag\": \"Nowa etykieta\",\n    \"Edit\": \"Edytuj\",\n    \"Remove\": \"Usuń\",\n    \"Forever\": \"Usuń\",\n    \"No\": \"Nie\",\n    \"Yes\": \"Tak\",\n    \"Basic\": \"Podstawowe\",\n    \"Cloud storage\": \"Synchronizacja\",\n    \"Notes per page\": \"Notatki na stronę\",\n    \"Sort notebooks\": \"Sortowanie notesów\",\n    \"Name\": \"Nazwa\",\n    \"Created\": \"Data utworzenia\",\n    \"Default edit mode\": \"Domyślny tryb edycji\",\n    \"Fullscreen with preview\": \"Pełen ekran z podglądem\",\n    \"Use encryption\": \"Szyfrowanie\",\n    \"Encryption parameters\": \"Parametry szyfrowania\",\n    \"Encryption Password\": \"Hasło\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Losowy\",\n    \"Key size\": \"Długość klucza\",\n    \"Strengthen by a factor of\": \"Wzmocnij o krotność\",\n    \"Authentication strength\": \"Siła uwierzytelniania\",\n    \"Unlock\": \"Odblokuj\",\n    \"Your new encryption password\": \"Podaj nowe hasło\",\n    \"Your old encryption password\": \"Podaj stare hasło\",\n    \"Show sidebar\": \"Pokaż pasek boczny\",\n    \"Previous\": \"Wstecz\",\n    \"Next\": \"Dalej\",\n    \"Navigation\": \"Nawigacja\",\n    \"navigateTop\": \"Góra\",\n    \"navigateBottom\": \"Dół\",\n    \"Jump\": \"Przejdź do\",\n    \"jumpInbox\": \"Wszystkich notatek\",\n    \"jumpNotebook\": \"Notesów\",\n    \"jumpFavorite\": \"Oznaczonych notatek\",\n    \"jumpRemoved\": \"Kosza\",\n    \"jumpOpenTasks\": \"Otwartych zadań\",\n    \"Actions\": \"Akcje\",\n    \"actionsEdit\": \"Edytuj\",\n    \"actionsOpen\": \"Otwórz\",\n    \"actionsRemove\": \"Usuń\",\n    \"actionsRotateStar\": \"Oznacz notatkę\",\n    \"App\": \"Aplikacja\",\n    \"appCreateNote\": \"Stwórz nową notatkę\",\n    \"appSearch\": \"Szukaj notatki\",\n    \"appKeyboardHelp\": \"Szybka pomoc\",\n    \"Change keybindings\": \"Zmień skróty klawiszowe\",\n    \"Donate\": \"Wspomóż\",\n    \"Github page\": \"Strona Github\",\n    \"Report bugs and issues here\": \"Zgłaszanie błędów i sugestii\",\n    \"Report bugs through email\": \"Zgłaszanie błędów przez mail\",\n    \"Credits\": \"Podziękowania\",\n    \"List of contributors\": \"Lista współtwórców\",\n    \"List of all used libraries\": \"Lista użytych bibliotek\",\n    \"Are you sure?\": \"Jesteś pewien?\",\n    \"You have unsaved changes\": \"Niezapisane zmiany zostaną utracone.\",\n    \"Dropbox API key\": \"Klucz API Dropbox\",\n    \"Required\": \"Wymagane\",\n    \"Optional\": \"Opcjonalne\",\n    \"Language\": \"Język\",\n    \"Action\": \"Akcja\",\n    \"Select\": \"Wybierz\",\n    \"General\": \"Podstawowe\",\n    \"Encryption\": \"Szyfrowanie\",\n    \"Keybindings\": \"Skróty klawiszowe\",\n    \"Sync\": \"Synchronizacja\",\n    \"Profiles\": \"Profile\",\n    \"Import\": \"Import\",\n    \"Transfer data\": \"Import i Eksport\",\n\t\"Transfer settings\": \"Ustawienia\",\n    \"Import settings\": \"Importuj ustawienia\",\n    \"Export settings\": \"Eksportuj ustawienia\",\n    \"Wrong format\": \"Niewłaściwy format\",\n    \"useDefaultConfigs\": \"Użyj ustawień z profilu domyślnego\",\n    \"File should be in json format\": \"Plik powinien być w formacie json\",\n    \"Close\": \"Zamknij\",\n    \"Hyperlink\": \"Hiperłącze\",\n    \"Editor\": \"Edytor\",\n    \"Preview\": \"Podgląd\",\n    \"Download\": \"Pobierz\",\n    \"Transfer everything\": \"Wszystko\",\n    \"Find in page\": \"Znajdź na stronie\",\n    \"Other\": \"Inne\",\n    \"Default\": \"Domyślny\",\n    \"Modules\": \"Moduły\",\n    \"Import data\": \"Importuj dane\",\n    \"Export data\": \"Eksportuj dane\",\n    \"Enabled\": \"Włączony\",\n    \"Disabled\": \"Wyłączony\",\n    \"Untitled\": \"Bez nazwy\",\n    \"Line of\": \"Linia {{currentLine}} z {{numberOfLines}}\",\n\t\"Drop files\": \"Przeciągnij tutaj plik, żeby przesłać\",\n\t\"Spaces per indent\": \"Liczba spacji wcięcia akapitu\",\n\t\"Sort notes\": \"Sortowanie notatek\",\n\t\"Updated date\": \"Dacie modyfikacji\",\n\t\"Created date\": \"Dacie utworzenia\",\n\t\"Text editor\": \"Edytor tekstowy\",\n\t\"Vim\": \"Vim\",\n\t\"Emacs\": \"Emacs\",\n\t\"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"Proszę podać hasło\",\n        \"change password\": \"Wprowadź nowe hasło\",\n        \"wait\": \"Trwa szyfrowanie\",\n        \"error\": \"Błąd szyfrowania\",\n        \"errorConfirm\": \"Błąd odszyfrowywania.\\r\\r Jeżeli zmieniałeś ustawienia szyfrowania w innej przeglądarce, **zaktualizuj ustawienia** również w tej przeglądarce lub spróbuj zaimportować ustawienia.\\r\\r Jeżeli nie zmieniałeś niczego, **spróbuj zalogować się jeszcze raz**.\",\n        \"errorConfirmSettings\": \"Zmień ustawienia szyfrowania\",\n        \"errorConfirmAuth\": \"Spróbuj ponownie\",\n        \"backup\": {\n            \"title\": \"Kopia zapasowa\",\n            \"content\": \"Przed kolejnym krokiem pobierz proszę kopię zapasową, zawiera ona odszyfrowaną wersję zmienianego profilu. Trzymaj w bezpiecznym miejscu.\",\n            \"next\": \"Przejdź dalej bez pobierania kopii zapasowej\"\n        },\n        \"state\": {\n            \"decrypt\": \"Odszyfruj wszystko\",\n            \"encrypt\": \"Zaszyfruj wszystko\",\n            \"save\": \"Zapisz ustawienia\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"Nazwa profilu\",\n        \"confirm remove\": \"Profil **{{profile}}** zostanie usunięty wraz ze wszystkimi powiązanymi danymi. Akcja jest nieodwracalna!\",\n        \"type name\": \"Wpisz nazwę profilu\"\n    },\n    \"files\": {\n        \"file-url\": \"URL pliku lub obrazka\",\n        \"attach\": \"Dołącz plik\",\n        \"attachLink\": \"Dołącz hiperłącze\",\n        \"attachImage\": \"Dołącz obrazek\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Notatka **{{title}}** zostanie przeniesiona do kosza\",\n        \"confirm remove\": \"Notatka **{{title}}** zostanie **nieodwracalnie** usunięta!\",\n        \"create and attach\": \"Stwórz notatkę i załącz jej adres\",\n        \"create\": \"Stwórz nową notatkę\",\n        \"hyperlink-dialog\": \"Tytuł notatki lub URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Wybierz notes\",\n        \"add\": \"Stwórz notes\",\n        \"edit\": \"Edytuj notes\",\n        \"name\": \"Podaj nazwę notesu\",\n        \"confirm remove\": \"Notes **{{name}}** zostanie **nieodwracalnie** usunięty!\",\n        \"remove with notes\": \"Tak, usuń wraz z powiązanymi notatkami\",\n        \"remove\": \"Tak, usuń\"\n    },\n    \"tags\": {\n        \"name\": \"Nazwa etykiety jest wymagana\",\n        \"add\": \"Dodaj nową etykietę\",\n        \"edit\": \"Edytuj etykietę\",\n        \"confirm remove\": \"Etykieta **{{name}}** zostanie **nieodwracalnie** usunięta!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Zostaniesz teraz przekierowany na stronę autoryzacyjną **Dropbox**.\\r> Kliknij przycisk **OK**.\",\n        \"auth title\": \"Dropbox - autoryzacja\",\n        \"api info 1\": \"Możesz posiadać własny klucz API\",\n        \"api info 2\": \"Kiedy tworzysz nową aplikacje na stronie Dropbox's Developer pamiętaj o:\"  ,\n        \"api info li 1\": \"Typ API powinien być **Dropbox API**\",\n        \"api info li 2\": \"Typ dostępu powinien być **App Folder**\"\n    },\n    \"help\": {\n        \"firststart title\": \"Witaj w Laverna\",\n        \"firststart import\": \"Jeżeli chcesz zaimportować swoje ustawienia Laverna klikając przycisk 'Import'.\",\n        \"firststart next\": \"Jeżeli to twój pierwszy raz z Laverna kliknij 'Dalej', aby rozpocząć proces instalacji.\",\n        \"firststart encryption\": \"Jeżeli chcesz zaszyfrować swoje notesy podaj hasło.\",\n        \"firststart sync\": \"Nie posiadamy własnego serwera synchronizacji notesów, ale możesz skorzystać z jednej z podanych usług.\",\n        \"firststart backup\": \"Wszytko już prawie gotowe. Możesz teraz pobrać kopię zapasową ustawień i przejść do ostatniego kroku.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/pt/translation.json",
    "content": "{\n    \"en\" : \"Inglês\",\n    \"ru\" : \"Russo\",\n    \"nl\" : \"Holandês\",\n    \"fr\" : \"Francês\",\n    \"pt\" : \"Português\",    \"pt_br\" : \"Português (Brasil) \",\n    \"Search\": \"Busca\",\n    \"All notes\": \"Todas as Anotações\",\n    \"Favourites\": \"Favorictos\",\n    \"Favorite\": \"Favoricto\",\n    \"Trash\": \"Balde do lixo\",\n    \"Notebooks\": \"Blocos de Anoctações\",\n    \"Settings\": \"Definições\",\n    \"About\": \"Acerca de\",\n    \"Save\": \"Salvar\",\n    \"Save & Exit\": \"Salvar e Sair\",\n    \"Cancel\": \"Cancelar\",\n    \"Full screen\": \"Ecrã inteiro\",\n    \"Preview\": \"Pré-visualizar\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Selecionar bloco de anotações\",\n    \"Title\": \"Título\",\n    \"Submit\": \"Submeter\",\n    \"Tags\": \"Marcações\",\n    \"Tag\": \"Marcação\",\n    \"Parent\": \"Pai\",\n    \"Root\": \"Raíz\",\n    \"Notebooks & tags\": \"Blocos de Anotações & marcações\",\n    \"Notebook\": \"Bloco de Anotações\",\n    \"Restore\": \"Restaurar\",\n    \"Delete\": \"Eliminar\",\n    \"New tag\": \"Nova marcação\",\n    \"Edit\": \"Editar\",\n    \"Remove\": \"Remover\",\n    \"Forever\": \"Para sempre\",\n    \"No\": \"Não\",\n    \"Yes\": \"Sim\",\n    \"Basic\": \"Básico\",\n    \"Cloud storage\": \"Armazenamento na nuvem\",\n    \"Notes per page\": \"Anotações por página\",\n    \"Default edit mode\": \"Modo de edição padrão\",\n    \"Fullscreen with preview\": \"Ecrã cheio com pré-visualização\",\n    \"Use encryption\": \"Usar criptografia\",\n    \"Encryption parameters\": \"Parâmetros de criptografia\",\n    \"Encryption Password\": \"Palavra-passe da criptografia\",\n    \"Salt\": \"Sal\",\n    \"Random\": \"Aleatório\",\n    \"Key size\": \"Tamanho da chave\",\n    \"Strengthen by a factor of\": \"Fator de reforço\",\n    \"Authentication strength\": \"Força da autenticação\",\n    \"Unlock\": \"Desbloquear\",\n    \"Your new encryption password\": \"Sua nova palavra-passe de criptografia\",\n    \"Your old encryption password\": \"Sua antiga palavra-passe de criptografia\",\n    \"Please wait until the encryption will be completed\": \"Por favor, aguarde até que a criptografia seja concluída\",\n    \"Shortcuts\": \"Atalhos\",\n    \"Newer\": \"Anterior\",\n    \"Older\": \"Próximo\",\n    \"Navigation\": \"Navegação\",\n    \"navigateTop\": \"Topo\",\n    \"navigateBottom\": \"Rodapé\",\n    \"Jump\": \"Pular\",\n    \"jumpInbox\": \"Ir para a caixa de entrada\",\n    \"jumpNotebook\": \"Ir para a lista de blocos de notas\",\n    \"jumpFavorite\": \"Ir para as notas favoritas\",\n    \"jumpRemoved\": \"Ir para as notas removidas\",\n    \"Actions\": \"Acções\",\n    \"actionsOpen\": \"Abrir\",\n    \"actionsRotateStar\": \"Remover dos favoritos\",\n    \"App\": \"Aplicação\",\n    \"appCreateNote\": \"Criar nova anotação\",\n    \"appSearch\": \"Procurar anotação\",\n    \"appKeyboardHelp\": \"Ajuda com o teclado\"\n}\n"
  },
  {
    "path": "app/locales/pt_br/translation.json",
    "content": "{\n    \"en\" : \"Ingl&ecirc;s\",\n    \"ru\" : \"Russo\",\n    \"nl\" : \"Holand&ecirc;s\",\n    \"fr\" : \"Franc&ecirc;s\",\n    \"pt_br\" : \"Portugu&ecirc;s (Brasil) \",\n    \"Search\": \"Pesquisar\",\n    \"All notes\": \"Todas as Notas\",\n    \"Favourites\": \"Favoritos\",\n    \"Favorite\": \"Favorito\",\n    \"Trash\": \"Lixeira\",\n    \"Notebooks\": \"Blocos de Notas\",\n    \"Settings\": \"Configura&ccedil;&otilde;es\",\n    \"About\": \"Sobre\",\n    \"Save\": \"Salvar\",\n    \"Save & Exit\": \"Salvar e Sair\",\n    \"Cancel\": \"Cancelar\",\n    \"Full screen\": \"Tela cheia\",\n    \"Preview\": \"Pr&eacute;-visualizar\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Selecionar bloco de notas\",\n    \"Title\": \"T&iacute;tulo\",\n    \"Submit\": \"Enviar\",\n    \"Tags\": \"Tags\",\n    \"Tag\": \"Tag\",\n    \"Parent\": \"Pai\",\n    \"Root\": \"Raiz\",\n    \"Notebooks & tags\": \"Blocos de Notas & tags\",\n    \"Notebook\": \"Bloco de Notas\",\n    \"Restore\": \"Restaurar\",\n    \"Delete\": \"Excluir\",\n    \"New tag\": \"Nova tag\",\n    \"Edit\": \"Editar\",\n    \"Remove\": \"Remover\",\n    \"Forever\": \"Para sempre\",\n    \"No\": \"N&atilde;o\",\n    \"Yes\": \"Sim\",\n    \"Basic\": \"B&aacute;sico\",\n    \"Cloud storage\": \"Armazenamento na nuvem\",\n    \"Notes per page\": \"Notas por p&aacute;gina\",\n    \"Default edit mode\": \"Modo de edi&ccedil;&atilde;o padr&atilde;o\",\n    \"Fullscreen with preview\": \"Tela cheia com pr&eacute;-visualiza&ccedil;&atilde;o\",\n    \"Use encryption\": \"Usar criptografia\",\n    \"Encryption parameters\": \"Par&acirc;metros de criptografia\",\n    \"Encryption Password\": \"Senha da criptografia\",\n    \"Salt\": \"Sal\",\n    \"Random\": \"Aleat&oacute;rio\",\n    \"Key size\": \"Tamanho da chave\",\n    \"Strengthen by a factor of\": \"Fator de refor&ccedil;o\",\n    \"Authentication strength\": \"For&ccedil;a da autentica&ccedil;&atilde;o\",\n    \"Unlock\": \"Desbloquear\",\n    \"Your new encryption password\": \"Sua nova senha de criptografia\",\n    \"Your old encryption password\": \"Sua antiga senha de criptografia\",\n    \"Please wait until the encryption will be completed\": \"Por favor, aguarde at&eacute; que a criptografia seja conclu&iacute;da\",\n    \"Shortcuts\": \"Atalhos\",\n    \"Newer\": \"Anterior\",\n    \"Older\": \"Pr&oacute;ximo\",\n    \"Navigation\": \"Navega&ccedil;&atilde;o\",\n    \"navigateTop\": \"Topo\",\n    \"navigateBottom\": \"Rodap&eacute;\",\n    \"Jump\": \"Pular\",\n    \"jumpInbox\": \"Ir para a caixa de entrada\",\n    \"jumpNotebook\": \"Ir para a lista de blocos de notas\",\n    \"jumpFavorite\": \"Ir para as notas favoritas\",\n    \"jumpRemoved\": \"Ir para as notas removidas\",\n    \"Actions\": \"A&ccedil;&otilde;es\",\n    \"actionsOpen\": \"Abrir\",\n    \"actionsRotateStar\": \"Remover dos favoritos\",\n    \"App\": \"App\",\n    \"appCreateNote\": \"Criar nova nota\",\n    \"appSearch\": \"Procurar nota\",\n    \"appKeyboardHelp\": \"Ajuda com o teclado\"\n}\n"
  },
  {
    "path": "app/locales/ru/translation.json",
    "content": "{\n    \"en\" : \"Английский\",\n    \"ru\" : \"Русский\",\n    \"nl\" : \"Голландский\",\n    \"fr\" : \"Французский\",\n    \"pt_br\" : \"Бразильский португальский\",\n    \"eo\": \"Эсперанто\",\n    \"es\": \"Испанский\",\n    \"de\": \"Немецкий\",\n    \"de_ch\": \"Швейцарский немецкий\",\n    \"se\": \"Шведский\",\n    \"el\": \"Греческий\",\n    \"nb\": \"Норвежский (Букмол)\",\n    \"nn\": \"Норвежский (Нюнорск)\",\n    \"bs_ba\": \"Боснийский\",\n    \"hi_in\": \"Хинди\",\n    \"mr_in\": \"Маратхи\",\n    \"zh_cn\": \"Упрощенный китайский\",\n    \"Search\": \"Поиск\",\n    \"All notes\": \"Все заметки\",\n    \"Favourites\": \"Избранное\",\n    \"Favorite\": \"Избранное\",\n    \"Trash\": \"Корзина\",\n    \"Notebooks\": \"Блокноты\",\n    \"Settings\": \"Настройки\",\n    \"About\": \"О программе\",\n    \"Save\": \"Сохранить\",\n    \"Save & Exit\": \"Сохранить и выйти\",\n    \"Cancel\": \"Отмена\",\n    \"Fullscreen\": \"Полный экран\",\n    \"Full screen\": \"Полный экран\",\n    \"Preview\": \"Предпросмотр\",\n    \"Normal\": \"Нормальный\",\n    \"Select notebook\": \"Выбрать блокнот\",\n    \"Title\": \"Название\",\n    \"Submit\": \"Отправить\",\n    \"Tags\": \"Теги\",\n    \"Tag\": \"Тег\",\n    \"Parent\": \"Родительский\",\n    \"Root\": \"Корень\",\n    \"Notebooks & Tags\": \"Блокноты и теги\",\n    \"Notebook\": \"Блокнот\",\n    \"Restore\": \"Восстановить\",\n    \"Delete\": \"Удалить\",\n    \"New tag\": \"Новый тег\",\n    \"Edit\": \"Изменить\",\n    \"Remove\": \"Убрать\",\n    \"Forever\": \"Навсегда\",\n    \"No\": \"Нет\",\n    \"Yes\": \"Да\",\n    \"Basic\": \"Основные\",\n    \"Cloud storage\": \"Облачное хранилище\",\n    \"Notes per page\": \"Заметок на странице\",\n    \"Sort notebooks\": \"Сортировать блокноты\",\n    \"Name\": \"По имени\",\n    \"Created\": \"По дате создания\",\n    \"Default edit mode\": \"Режим по умолчанию\",\n    \"Fullscreen with preview\": \"Полный экран с предпросмотром\",\n    \"Use encryption\": \"Использовать шифрование\",\n    \"Encryption parameters\": \"Параметры шифрования\",\n    \"Encryption Password\": \"Пароль шифрования\",\n    \"Salt\": \"Соль\",\n    \"Random\": \"Случайно\",\n    \"Key size\": \"Длина ключа\",\n    \"Strengthen by a factor of\": \"Коэффициент усиления\",\n    \"Authentication strength\": \"Надежность аутентификации\",\n    \"Unlock\": \"Разблокировать\",\n    \"Your new encryption password\": \"Новый пароль шифрования\",\n    \"Your old encryption password\": \"Старый пароль шифрования\",\n    \"Please wait until the encryption will be completed\": \"Подождите завершения шифрования\",\n    \"Shortcuts\": \"Горячие клавиши\",\n    \"Newer\": \"Назад\",\n    \"Older\": \"Вперед\",\n    \"Navigation\": \"Навигация\",\n    \"navigateTop\": \"Вверх\",\n    \"navigateBottom\": \"Вниз\",\n    \"Jump\": \"Переходы\",\n    \"jumpInbox\": \"Перейти ко всем заметкам\",\n    \"jumpNotebook\": \"К списку блокнотов\",\n    \"jumpFavorite\": \"К избранным заметкам\",\n    \"jumpRemoved\": \"К убранным заметкам\",\n    \"Actions\": \"Действия\",\n    \"actionsEdit\": \"Изменить\",\n    \"actionsOpen\": \"Открыть\",\n    \"actionsRemove\": \"Убрать\",\n    \"actionsRotateStar\": \"Поставить/убрать звезду\",\n    \"App\": \"Приложение\",\n    \"appCreateNote\": \"Создать новую заметку\",\n    \"appSearch\": \"Искать заметку\",\n    \"appKeyboardHelp\": \"Помощь по горячим клавишам\",\n    \"Other\": \"Другое\",\n    \"Profiles\": \"Профили\",\n    \"Import settings\": \"Импортировать настройки\",\n    \"Export settings\": \"Экспортировать настройки\",\n    \"Action\": \"Действие\",\n    \"Trashed\": \"Корзина\",\n    \"New notebook\": \"Новый блокнот\",\n    \"Are you sure? You have unsaved changes\": \"Вы уверены? У вас есть несохраненные изменения\",\n\t\"profile\": {\n\t\t\"profile name\": \"Имя профиля\"\n\t}\n}\n"
  },
  {
    "path": "app/locales/se/translation.json",
    "content": "{\n    \"en\" : \"Engelska\",\n    \"ru\" : \"Ryska\",\n    \"nl\" : \"Holländska\",\n    \"fr\" : \"Franska\",\n    \"pt_br\" : \"Portugisiska (Brasilien)\",\n    \"eo\": \"Esperanto\",\n    \"es\": \"Spanska\",\n    \"de\": \"Tyska\",\n    \"se\": \"Svenska\",\n    \"nb\" : \"Norska (Bokmål)\",\n    \"nn\" : \"Norska (Nynorsk)\",\n    \"Search\": \"Sök\",\n    \"All notes\": \"Alla anteckningar\",\n    \"Favourites\": \"Favoriter\",\n    \"Favorite\": \"Favoriter\",\n    \"Trash\": \"Papperskorg\",\n    \"Notebooks\": \"Anteckningsböcker\",\n    \"Settings\": \"Inställningar\",\n    \"About\": \"Om Laverna\",\n    \"Save\": \"Spara\",\n    \"Save & Exit\": \"Spara och avsluta\",\n    \"Cancel\": \"Avbryt\",\n    \"Full screen\": \"Helskärm\",\n    \"Preview\": \"Förhandsvisning\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Välj anteckningsbok\",\n    \"Title\": \"Rubrik\",\n    \"Submit\": \"Tillämpa\",\n    \"Tags\": \"Taggar\",\n    \"Tag\": \"Tagg\",\n    \"Parent\": \"Förälder\",\n    \"Root\": \"Rot\",\n    \"Notebooks & tags\": \"Anteckningsböcker & Taggar\",\n    \"Notebook\": \"Anteckningsbok\",\n    \"Restore\": \"Återskapa\",\n    \"Delete\": \"Radera\",\n    \"New tag\": \"Ny tagg\",\n    \"Edit\": \"Redigera\",\n    \"Remove\": \"Ta bort\",\n    \"Forever\": \"För alltid\",\n    \"No\": \"Nej\",\n    \"Yes\": \"Ja\",\n    \"Basic\": \"Grundläggande\",\n    \"Cloud storage\": \"Molnlagring\",\n    \"Notes per page\": \"Anteckningar per sida\",\n    \"Sort notebooks\": \"Sortera anteckningar\",\n    \"Name\": \"Namn\",\n    \"Created\": \"Skapad\",\n    \"Default edit mode\": \"Standardredigeringsläge\",\n    \"Fullscreen with preview\": \"Helskärm med förhandsvisning\",\n    \"Use encryption\": \"Använd kryptering\",\n    \"Encryption parameters\": \"Krypteringsparametrar\",\n    \"Encryption Password\": \"Lösenord\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Slumpmässig\",\n    \"Key size\": \"Nyckelstorlek\",\n    \"Strengthen by a factor of\": \"Förstärk med en faktor av\",\n    \"Authentication strength\": \"Autentiseringsstyrka\",\n    \"Unlock\": \"Lås upp\",\n    \"Your new encryption password\": \"Ditt nya lösenord\",\n    \"Your old encryption password\": \"Ditt gamla lösenord\",\n    \"Please wait until the encryption will be completed\": \"Var god vänta tills kryptering är slutförd\",\n    \"Shortcuts\": \"Genvägar\",\n    \"Previous\": \"Föregående\",\n    \"Next\": \"Nästa\",\n    \"Navigation\": \"Navigation\",\n    \"navigateTop\": \"Till toppen\",\n    \"navigateBottom\": \"Till botten\",\n    \"Jump\": \"Håppa\",\n    \"jumpInbox\": \"Gå till inkorg\",\n    \"jumpNotebook\": \"Gå till Anteckningsböker\",\n    \"jumpFavorite\": \"Gå till Favoriter\",\n    \"jumpRemoved\": \"Gå till Papperskorgen\",\n    \"Actions\": \"Handlingar\",\n    \"actionsEdit\": \"Redigera\",\n    \"actionsOpen\": \"Öppna\",\n    \"actionsRemove\": \"Radera\",\n    \"actionsRotateStar\": \"Lägg till/ta bort favorit\",\n    \"App\": \"Applikation\",\n    \"appCreateNote\": \"Skapa ny anteckning\",\n    \"appSearch\": \"Sök i anteckningar\",\n    \"appKeyboardHelp\": \"Skrivbordshjälp\"\n}\n"
  },
  {
    "path": "app/locales/sq/translation.json",
    "content": "{\n    \"Search\": \"Kërkoni\",\n    \"All notes\": \"Krejt shënimet\",\n    \"Favourites\": \"Të parapëlqyera\",\n    \"Favorite\": \"Të parapëlqyera\",\n    \"Trash\": \"Hedhurina\",\n    \"Open tasks\": \"Punë të hapura\",\n    \"Notebooks\": \"Blloqe\",\n    \"Settings\": \"Rregullime\",\n    \"About\": \"Mbi\",\n    \"Save\": \"Ruaje\",\n    \"Save & Exit\": \"Ruaje & Dil\",\n    \"Cancel\": \"Anuloje\",\n    \"Full screen\": \"Sa krejt ekrani\",\n    \"Preview\": \"Paraparje\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Përzgjidhni bllok\",\n    \"Title\": \"Titull\",\n    \"Submit\": \"Parashtroje\",\n    \"Tags\": \"Etiketa\",\n    \"Tag\": \"Etiketë\",\n    \"Parent\": \"Mëmë\",\n    \"Root\": \"Rrënjë\",\n    \"Notebooks & tags\": \"Blloqe & etiketa\",\n    \"Notebook\": \"Bllok\",\n    \"Restore\": \"Riktheje\",\n    \"Delete\": \"Fshije\",\n    \"New tag\": \"Etiketë e re\",\n    \"Edit\": \"Përpunojeni\",\n    \"Remove\": \"Hiqe\",\n    \"Forever\": \"Përgjithmonë\",\n    \"No\": \"Jo\",\n    \"Yes\": \"Po\",\n    \"Basic\": \"Bazë\",\n    \"Cloud storage\": \"Depozitë në re\",\n    \"Notes per page\": \"Shënime për faqe\",\n    \"Sort notebooks\": \"Renditni blloqet\",\n    \"Name\": \"Emër\",\n    \"Created\": \"Krijuar më\",\n    \"Default edit mode\": \"Mënyrë parazgjedhje për përpunime\",\n    \"Fullscreen with preview\": \"Sa krejt ekrani, me paraparje\",\n    \"Use encryption\": \"Përdor fshehtëzim\",\n    \"Encryption parameters\": \"Parametra fshehtëzimi\",\n    \"Encryption Password\": \"Fjalëkalim Fshehtëzimi\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Kuturu\",\n    \"Key size\": \"Madhësi kyçi\",\n    \"Strengthen by a factor of\": \"Fuqizoje me\",\n    \"Authentication strength\": \"Fuqi mirëfilltësimi\",\n    \"Unlock\": \"Shkyçe\",\n    \"Your new encryption password\": \"Fjalëkalimi juaj i ri i fshehtëzimit\",\n    \"Your old encryption password\": \"Fjalëkalimi juaj i vjetër i fshehtëzimit\",\n    \"Show sidebar\": \"Shfaqni anështyllën\",\n    \"Previous\": \"I mëparshmi\",\n    \"Next\": \"Pasuesi\",\n    \"Navigation\": \"Lëvizje\",\n    \"navigateTop\": \"Sipër\",\n    \"navigateBottom\": \"Poshtë\",\n    \"Jump\": \"Hidhu\",\n    \"jumpInbox\": \"Shko te të marrët\",\n    \"jumpNotebook\": \"Shko te listë blloqesh\",\n    \"jumpFavorite\": \"Shko te shënime të parapëlqyera\",\n    \"jumpRemoved\": \"Shko te shënime të hequra\",\n    \"jumpOpenTasks\": \"Shko te shënime me punë të pambaruara\",\n    \"Actions\": \"Veprime\",\n    \"actionsEdit\": \"Përpunoni\",\n    \"actionsOpen\": \"Hape\",\n    \"actionsRemove\": \"Hiqe\",\n    \"actionsRotateStar\": \"Rrotullo Yllin\",\n    \"App\": \"Aplikacion\",\n    \"appCreateNote\": \"Krijoni shënim të ri\",\n    \"appSearch\": \"Kërkoni shënim\",\n    \"appKeyboardHelp\": \"Ndihmë për tastierën\",\n    \"Change keybindings\": \"Ndryshoni rregullime tastesh\",\n    \"Donate\": \"Dhuroni\",\n    \"Github page\": \"Faqja Github\",\n    \"Report bugs and issues here\": \"Njoftoni këtu të meta dhe probleme\",\n    \"Report bugs through email\": \"Njoftoni të meta me email\",\n    \"Credits\": \"Falënderime\",\n    \"List of contributors\": \"Listë kontribuesish\",\n    \"List of all used libraries\": \"Listë e krejt librarive të përdorura\",\n    \"Are you sure?\": \"Jeni i sigurt?\",\n    \"You have unsaved changes\": \"Keni ndryshime të paruajtura.\",\n    \"Dropbox API key\": \"Kyç API Dropbox-i\",\n    \"Required\": \"E domosdoshme\",\n    \"Optional\": \"Opsionale\",\n    \"Language\": \"Gjuhë\",\n    \"Action\": \"Veprim\",\n    \"Select\": \"Përzgjidhni\",\n    \"General\": \"Të përgjithshme\",\n    \"Encryption\": \"Fshehtëzim\",\n    \"Keybindings\": \"Shkurtore tastiere\",\n    \"Sync\": \"Njëkohësim\",\n    \"Profiles\": \"Profile\",\n    \"Import\": \"Importim\",\n    \"Transfer data\": \"Import & eksport\",\n    \"Import settings\": \"Rregullime importimesh\",\n    \"Export settings\": \"Rregullime eksportimesh\",\n    \"Wrong format\": \"Format i gabuar\",\n    \"useDefaultConfigs\": \"Përdor rregullime nga profili parazgjedhje\",\n    \"File should be in json format\": \"Kartela duhet të jetë në format json\",\n    \"Close\": \"Mbylle\",\n    \"Hyperlink\": \"Tejlidhje\",\n    \"Editor\": \"Përpunues\",\n    \"Preview\": \"Paraparje\",\n    \"Download\": \"Shkarkim\",\n    \"Transfer everything\": \"Gjithçka\",\n    \"encryption\": {\n        \"wait\": \"Ju lutemi, pritni deri sa të plotësohet fshehtëzimi\",\n        \"error\": \"Gabim fshehtëzimi\",\n        \"errorConfirm\": \"Gabim gjatë shfshehtëzimit të të dhënave.\\r\\r Nëse i ndryshuat rregullimet mbi fshehtëzimin në një tjetër shfletues, **përditësojini rregullimet tuaja** edhe në këtë shfletues. Ose provoni t’i importoni rregullimet.\\r\\r Dhe, nëse s’keni ndryshuar gjë, **provoni të bëni hyrjen** sërish.\",\n        \"errorConfirmSettings\": \"Ndryshoni rregullime fshehtëzimi\",\n        \"errorConfirmAuth\": \"Riprovoni sërish\",\n        \"backup\": {\n            \"title\": \"Kopjeruani të Dhëna\",\n            \"content\": \"Ju lutemi, përpara se të vazhdoni me hapin pasues, shkarkoni kartelën tuaj kopjeruajtje. Ajo përmban të dhëna të shfshehtëzuara profilesh të mëparshëm të ndryshuar. Ruajeni në një vend të sigurt.\",\n            \"next\": \"Vazhdoni pa e shkarkuar kartelën kopjeruajtje\"\n        },\n        \"state\": {\n            \"decrypt\": \"Po shfshehtëzohet gjithçka\",\n            \"encrypt\": \"Po fshehtëzohet gjithçka\",\n            \"save\": \"Po ruhen ndryshimet\"\n        }\n    },\n    \"profile\": {\n        \"confirm remove\": \"Profili **{{profile}}** do të hiqet me gjithë të dhënat, përfshi shënime, etiketa dhe blloqe shënimesh. Ky veprim është i pakthyeshëm!\",\n        \"type name\": \"Shtypni emër profili\"\n    },\n    \"files\": {\n        \"file-url\": \"URL kartele ose pamjeje\",\n        \"attach\": \"Bashkëngjitni një kartelë\",\n        \"attachLink\": \"Bashkëngjiteni si lidhje\",\n        \"attachImage\": \"Bashkëngjiteni si figurë\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Shënimi **{{title}}** do të kalohet te hedhurinat.\",\n        \"confirm remove\": \"Shënimi **{{title}}** do të hiqet **përgjithmonë**!\",\n        \"create and attach\": \"Krijoni një shënim të ri dhe bashkëngjitni lidhjen e tij\",\n        \"create\": \"Krijoni një shënim të ri\",\n        \"hyperlink-dialog\": \"Titulli i një shënimi ose URL-je\"\n    },\n    \"notebooks\": {\n        \"select\": \"Përzgjidhni një Bllok Shënimesh\",\n        \"add\": \"Shtoni një bllok të ri shënimesh\",\n        \"edit\": \"Përpunoni një bllok shënimesh\",\n        \"name\": \"Ju lutemi, jepni emrin e bllokut të shënimeve\",\n        \"confirm remove\": \"Blloku i shënimeve **{{name}}** do të hiqet **përgjithmonë**!\",\n        \"remove with notes\": \"Po, hiqe me gjithë shënimet e bashkëngjitura\",\n        \"remove\": \"Po, hiqe\"\n    },\n    \"tags\": {\n        \"name\": \"Emri i etiketës është i domosdoshëm\",\n        \"add\": \"Shtoni një etiketë të re\",\n        \"edit\": \"Përpunoni etiketë\",\n        \"confirm remove\": \"Etiketa **{{name}}** do të hiqet **përgjithmonë**!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"Tani do të ridrejtoheni për te faqja e autorizimeve **Dropbox**.\\r> Ju lutemi, klikoni mbi butonin **OK**.\",\n        \"auth title\": \"Autorizim Dropbox\",\n        \"api info 1\": \"Mund të keni kyçin tuaj API\",\n        \"api info 2\": \"Kur krijoni një aplikacion të ri te sajti Dropbox's Developer duhet të keni parasysh se:\",\n        \"api info li 1\": \"Lloji i aplikacionit duhet të jetë aplikacion API Dropbox\",\n        \"api info li 2\": \"Llojet e të dhënave duhet të jenë Kartela dhe depo të dhënash\"\n    },\n    \"help\": {\n        \"firststart title\": \"Mirë se vini te Laverna\",\n        \"firststart import\": \"Nëse e keni përdorur Laverna-n më parë, mund të importoni rregullimet tuaja të vjetra duke klikuar mbi butonin 'Importo' më poshtë.\",\n        \"firststart next\": \"Nëse s’e keni përdorur Laverna-n më parë, klikoni mbi butonin 'pasuesi' që të fillojë procesi i instalimit.\",\n        \"firststart encryption\": \"Nëse dëshironi të përdorni fshehtëzim, ju lutemi, jepni fjalëkalim fshehtëzimesh.\",\n        \"firststart sync\": \"Meqë nuk depozitojmë ndonjë të dhënë në shërbyesit tanë, lypset të aktivizoni njëkohësimin me një nga përshtatësit, që të jeni në gjendje të shihni shënimet tuaja në pajisje të tjera.\",\n        \"firststart backup\": \"Gjithçka thuajse është gati. Mund të shkarkoni kopjeruajtjen e rregullimeve tuaja dhe të vazhdoni me hapin e fundit.\"\n    }\n}\n"
  },
  {
    "path": "app/locales/tr/translation.json",
    "content": "{\n    \"Search\": \"Ara\",\n    \"All notes\": \"Bütün notlar\",\n    \"Favourites\": \"Sık kullanılanlar\",\n    \"Favorite\": \"Sık kullanılan\",\n    \"Trash\": \"Çöp kutusu\",\n    \"Open tasks\": \"Yapacaklarım\",\n    \"Notebooks\": \"Not defteri\",\n    \"Settings\": \"Kaydet\",\n    \"About\": \"Hakkında\",\n    \"Save\": \"Save\",\n    \"Save & Exit\": \"Kaydet & Çık\",\n    \"Cancel\": \"İptal\",\n    \"Full screen\": \"Tam Ekran\",\n    \"Normal\": \"Normal\",\n    \"Select notebook\": \"Not defteri seç\",\n    \"Title\": \"Başlık\",\n    \"Submit\": \"Gönder\",\n    \"Tags\": \"Etiketler\",\n    \"Tag\": \"Etiket\",\n    \"Parent\": \"Üst öğe\",\n    \"Root\": \"Kök\",\n    \"Notebooks & Tags\": \"Not defterleri & Etiketler\",\n    \"Notebook\": \"Not defteri\",\n    \"Restore\": \"Geri yükle\",\n    \"Delete\": \"Sil\",\n    \"New tag\": \"Yeni etiket\",\n    \"Edit\": \"Düzenle\",\n    \"Remove\": \"Kaldır\",\n    \"Forever\": \"Kalıcı sil\",\n    \"No\": \"Hayır\",\n    \"Yes\": \"Evet\",\n    \"Basic\": \"Temel\",\n    \"Cloud storage\": \"Bulut depolama\",\n    \"Notes per page\": \"Sayfa başına not\",\n    \"Sort notebooks\": \"Not def. sıralama\",\n    \"Name\": \"Ad\",\n    \"Created\": \"Oluşturulma\",\n    \"Default edit mode\": \"Varsayılan düzenleme modu\",\n    \"Fullscreen with preview\": \"Önizlemeli tam ekran\",\n    \"Use encryption\": \"Şifreleme kullan\",\n    \"Encryption parameters\": \"Şifreleme parametreleri\",\n    \"Encryption Password\": \"Parola\",\n    \"Salt\": \"Salt\",\n    \"Random\": \"Rasgele\",\n    \"Key size\": \"Anahtar büyüklüğü\",\n    \"Strengthen by a factor of\": \"Sağlamlık faktörü\",\n    \"Authentication strength\": \"Kimlik doğrulama dayanıklılığı\",\n    \"Unlock\": \"Kilidi aç\",\n    \"Your new encryption password\": \"Yeni parolan\",\n    \"Your old encryption password\": \"Eski parolan\",\n    \"Show sidebar\": \"Kenar çubuğu göster\",\n    \"Previous\": \"Önceki\",\n    \"Next\": \"Sonraki\",\n    \"Navigation\": \"Navigasyon\",\n    \"navigateTop\": \"Üst\",\n    \"navigateBottom\": \"Alt\",\n    \"Jump\": \"Atla\",\n    \"jumpInbox\": \"Gelen kutusuna git\",\n    \"jumpNotebook\": \"Not listesine git\",\n    \"jumpFavorite\": \"Sık kullanılanlara git\",\n    \"jumpRemoved\": \"Silinen notlara git\",\n    \"jumpOpenTasks\": \"Yapacak olan notlara git\",\n    \"Actions\": \"Eylemler\",\n    \"actionsEdit\": \"Düzenle\",\n    \"actionsOpen\": \"Aç\",\n    \"actionsRemove\": \"Kaldır\",\n    \"actionsRotateStar\": \"Yıldız\",\n    \"App\": \"Uygulama\",\n    \"appCreateNote\": \"Yeni not oluştur\",\n    \"appSearch\": \"Not ara\",\n    \"appKeyboardHelp\": \"Klavye yardım\",\n    \"Change keybindings\": \"Kısayolları değiştir\",\n    \"Donate\": \"Başış Yap\",\n    \"Github page\": \"Github kaynak kod sayfası\",\n    \"Report bugs and issues here\": \"Hata ve sorunları buraya bildir\",\n    \"Report bugs through email\": \"Hataları email yolu ile bildir\",\n    \"Credits\": \"Katkılar\",\n    \"List of contributors\": \"Katkıda bulunanlar\",\n    \"List of all used libraries\": \"Kullanılan kütüphaneler\",\n    \"Are you sure?\": \"Emin misin?\",\n    \"You have unsaved changes\": \"Kaydedilmemiş değişikliklerin var\",\n    \"Dropbox API key\": \"Dropbox API anahtarı\",\n    \"Required\": \"Gerekli\",\n    \"Optional\": \"İsteğe bağlı\",\n    \"Language\": \"Dil\",\n    \"Action\": \"Eylem\",\n    \"Select\": \"Seç\",\n    \"General\": \"Genel\",\n    \"Encryption\": \"Şifre\",\n    \"Keybindings\": \"Kısayollar\",\n    \"Sync\": \"Senkronize\",\n    \"Profiles\": \"Profiller\",\n    \"Import\": \"İçe aktarım\",\n    \"Import & export\": \"İçe & Dışa Aktarım\",\n    \"Import settings\": \"İçe aktarıma ayarları\",\n    \"Export settings\": \"Dışa aktarım ayarları\",\n    \"Wrong format\": \"Yanlış biçim\",\n    \"useDefaultConfigs\": \"Varsayılar profil ayarları kullan\",\n    \"File should be in json format\": \"Dosya json formatında olmalı\",\n    \"Close\": \"Kapat\",\n    \"Hyperlink\": \"Köprü\",\n    \"Editor\": \"Editör\",\n    \"Preview\": \"Önizleme\",\n    \"Download\": \"İndir\",\n    \"Find in page\": \"Sayfada ara\",\n    \"Other\": \"Diğer\",\n    \"Default\": \"Varsayılan\",\n    \"Modules\": \"Modüller\",\n    \"Import data\": \"İçeri veri aktar\",\n    \"Export data\": \"Dışarı veri aktar\",\n    \"Everything\": \"Herşey\",\n    \"Enabled\": \"Etkin\",\n    \"Disabled\": \"Devredışı\",\n    \"Untitled\": \"Başlıksız\",\n    \"Line of\": \"Satır {{mevcutSatır}} of {{SatırSayısı}}\",\n    \"encryption\": {\n        \"provide password\": \"Lütfen şifrenizi doğrulayınız\",\n        \"change password\": \"Şifrenizi değiştirmek için buraya giriniz\",\n        \"wait\": \"Şifreleme bitene kadar lütfen bekleyiniz\",\n        \"error\": \"Encryption error\",\n        \"errorConfirm\": \"Şifreleme anında hata gerçekleşti.\\r\\r Eğer şifre ayarlarınızı başka bir tarayıcıda değiştirdiyseniz, **ayarlarınızı** bu tarayıcıda da değiştiriniz. Veya içeri veri aktarımı deneyiniz .\\r\\r Hala bir sonuç alamıyorsanız,  **tekrar giriş** yapmayı deneyiniz.\",\n        \"errorConfirmSettings\": \"Şifreleme ayarlarını değiştirin\",\n        \"errorConfirmAuth\": \"Tekrar deneyiniz\",\n        \"backup\": {\n            \"title\": \"Veri yedekle\",\n            \"content\": \"Lütfen, bir değişiklik yapmadan önce, yedek dosyanızı indirin. Yedek dosyanız bir önceki profil verilerinizi barındırır ve güvenli bir yerde tutar.\",\n            \"next\": \"Yedek almadan işleme devam et\"\n        },\n        \"state\": {\n            \"decrypt\": \"Herşeyi çöz\",\n            \"encrypt\": \"Herşeyi şifrele\",\n            \"save\": \"Değişiklikler kaydediliyor.\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"Profil ismi\",\n        \"confirm remove\": \"Profiliniz **{{profiliniz}}** bütün kayıtıları ile beraber silenecektir, notlarınız, etiketleriniz, not defterleriniz ile beraber. Bu eylem tersine çevrilemez!\",\n        \"type name\": \"Profil ismi tipi\"\n    },\n    \"files\": {\n        \"file-url\": \"Dosya veya resim URL'si\",\n        \"attach\": \"Dosya ekle\",\n        \"attachLink\": \"Link ekle\",\n        \"attachImage\": \"Resim ekle\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"Bu not **{{başlık}}** çöpe taşınacaktır.\",\n        \"confirm remove\": \"Bu not **{{başlık}}** **geri dönmemek üzere** silenecektir!\",\n        \"create and attach\": \"Yeni bir not ekle ve link ekle\",\n        \"create\": \"Yeni not ekle\",\n        \"hyperlink-dialog\": \"Notun başlığı yada URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"Not defteri seç\",\n        \"add\": \"Yeni not defteri ekle\",\n        \"edit\": \"Not defterini düzenle\",\n        \"name\": \"Lütfen not defterinin adını tanımlayınız\",\n        \"confirm remove\": \"Not defteri **{{adı}}** **geri dönmemek üzere** silinecektir!\",\n        \"remove with notes\": \"Evet, ekli dosyalar ile beraber kaldır\",\n        \"remove\": \"Evet, kaldır\"\n    },\n    \"tags\": {\n        \"name\": \"Etiket ismi gerekli\",\n        \"add\": \"Yeni etiket ekle\",\n        \"edit\": \"Etiket düzenle\",\n        \"confirm remove\": \"Etiket**{{adı}}** **geri dönmemek üzere** silinecektir!\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"**Dropbox** yetkilendirme sayfasına yönlendiriliyorsunuz.\\r> Lütfen **tamama** tıklayınız\",\n        \"auth title\": \"Dropbox yetkilendirme\",\n        \"api info 1\": \"Kendi API anahtarın olabilir\",\n        \"api info 2\": \"Dropbox's Developer sayfasında yeni bir uygulama başlattığında bunları aklında tutsan iyi olur:\",\n        \"api info li 1\": \"API tipin **Dropbox API** olmalı\",\n        \"api info li 2\": \"Erişim tipi **Uygulama Dosyası** olmalı\"\n    },\n    \"help\": {\n        \"firststart title\": \"Laverna'ya hoş geldin!\",\n        \"firststart import\": \"Eğer daha önce Laverna kullanıysan, aşağıdaki 'veri aktara' tıklayarak önceki ayarlarını kullanabilirsin.\",\n        \"firststart next\": \"Eğer daha önce Laverna kullanıysan, 'ileri' butonuna tıklayarak yükleme işlemine bağlayabilirsin\",\n        \"firststart encryption\": \"Eğer şifrelemeyi kullanmak istiyorsan, lütfen şifrenizi tanımlayınız.\",\n        \"firststart sync\": \"Sunucumuzda veri depolamadığımızdan beri, notlarını diğer cihazlarda görüntülemek için senkronizasyonu adaptörlerden biriyle çalışır kılman gerekiyor.\",\n        \"firststart backup\": \"Herşey neredeyse hazır. Ayarlarının yedeğini indirdikten sonra sonra adıma geçebilirsin\"\n    }\n}\n"
  },
  {
    "path": "app/locales/zh_cn/translation.json",
    "content": "{\n    \"en\" : \"英语\",\n    \"ru\" : \"俄语\",\n    \"nl\" : \"荷兰语\",\n    \"fr\" : \"法语\",\n    \"pt_br\" : \"巴西葡萄牙语\",\n    \"eo\": \"世界语\",\n    \"es\": \"西班牙语\",\n    \"de\": \"德语\",\n    \"se\": \"瑞典语\",\n    \"el\": \"希腊语\",\n    \"nb\": \"挪威语 (波克默尔语)\",\n    \"nn\": \"挪威语 (尼诺斯克语)\",\n    \"Search\": \"搜索\",\n    \"All notes\": \"所有笔记\",\n    \"Favourites\": \"收藏夹\",\n    \"Favorite\": \"收藏\",\n    \"Trash\": \"垃圾桶\",\n    \"Open tasks\": \"待处理任务\",\n    \"Notebooks\": \"所有笔记本\",\n    \"Settings\": \"设置\",\n    \"About\": \"关于\",\n    \"Save\": \"保存\",\n    \"Save & Exit\": \"保存退出\",\n    \"Cancel\": \"取消\",\n    \"Full screen\": \"全屏\",\n    \"Preview\": \"预览\",\n    \"Normal\": \"正常\",\n    \"Select notebook\": \"选择笔记本\",\n    \"Title\": \"标题\",\n    \"Submit\": \"提交\",\n    \"Tags\": \"所有标签\",\n    \"Tag\": \"标签\",\n    \"Parent\": \"父\",\n    \"Root\": \"根\",\n    \"Notebooks & tags\": \"笔记本和标签\",\n    \"Notebook\": \"笔记本\",\n    \"Restore\": \"重新保存\",\n    \"Delete\": \"删除\",\n    \"New tag\": \"新建标签\",\n    \"Edit\": \"编辑\",\n    \"Remove\": \"移除\",\n    \"Forever\": \"永远\",\n    \"No\": \"否\",\n    \"Yes\": \"是\",\n    \"Basic\": \"基本\",\n    \"Cloud storage\": \"云存储\",\n    \"Notes per page\": \"每页笔记数\",\n    \"Sort notebooks\": \"笔记本排序\",\n    \"Name\": \"名称\",\n    \"Created\": \"创建者\",\n    \"Default edit mode\": \"默认编辑模式\",\n    \"Fullscreen with preview\": \"全屏预览\",\n    \"Use encryption\": \"加密\",\n    \"Encryption parameters\": \"加密参数\",\n    \"Encryption Password\": \"加密密码\",\n    \"Salt\": \"加盐\",\n    \"Random\": \"随机\",\n    \"Key size\": \"密钥长度\",\n    \"Strengthen by a factor of\": \"加强元素\",\n    \"Authentication strength\": \"验证强度\",\n    \"Unlock\": \"解锁\",\n    \"Your new encryption password\": \"新加密密码\",\n    \"Your old encryption password\": \"旧加密密码\",\n    \"Show sidebar\": \"显示侧边栏\",\n    \"Shortcuts\": \"快捷方式\",\n    \"Previous\": \"上一步\",\n    \"Next\": \"下一步\",\n    \"Navigation\": \"导航\",\n    \"navigateTop\": \"顶部\",\n    \"navigateBottom\": \"底部\",\n    \"Jump\": \"跳转\",\n    \"jumpInbox\": \"跳转到收件夹\",\n    \"jumpNotebook\": \"跳转到笔记列表\",\n    \"jumpFavorite\": \"跳转到收藏夹\",\n    \"jumpRemoved\": \"跳转到已删除笔记列表\",\n    \"jumpOpenTasks\": \"跳转到待处理任务笔记列表\",\n    \"Actions\": \"动作\",\n    \"actionsEdit\": \"编辑\",\n    \"actionsOpen\": \"打开\",\n    \"actionsRemove\": \"移除\",\n    \"actionsRotateStar\": \"加入收藏\",\n    \"App\": \"应用\",\n    \"appCreateNote\": \"新建笔记\",\n    \"appSearch\": \"搜索笔记\",\n    \"appKeyboardHelp\": \"按键帮助\",\n    \"Change keybindings\": \"修改按键绑定\",\n    \"Donate\": \"捐助\",\n    \"Github page\": \"Github page\",\n    \"Report bugs and issues here\": \"Report bugs and issues here\",\n    \"Report bugs through email\": \"通过邮件报告Bug\",\n    \"Credits\": \"Credits\",\n    \"List of contributors\": \"List of contributors\",\n    \"List of all used libraries\": \"List of all used libraries\",\n    \"Are you sure?\": \"你确定 ?\",\n    \"You have unsaved changes\": \"你有未保存的修改.\",\n    \"Dropbox API key\": \"Dropbox API key\",\n    \"Required\": \"必须\",\n    \"Optional\": \"可选\",\n    \"Language\": \"语言\",\n    \"Action\": \"动作\",\n    \"Select\": \"选择\",\n    \"General\": \"通用\",\n    \"Encryption\": \"加密\",\n    \"Keybindings\": \"按键绑定\",\n    \"Sync\": \"同步\",\n    \"Profiles\": \"配置文件\",\n    \"Import\": \"导入\",\n    \"Transfer data\": \"导入 & 导出\",\n    \"Import settings\": \"导入设置\",\n    \"Export settings\": \"导出设置\",\n    \"Wrong format\": \"格式错误\",\n    \"useDefaultConfigs\": \"使用默认设置\",\n    \"File should be in json format\": \"文件应该是json格式\",\n    \"Close\": \"关闭\",\n    \"Hyperlink\": \"超链接\",\n    \"Editor\": \"编辑器\",\n    \"Preview\": \"预览\",\n    \"Download\": \"下载\",\n    \"Transfer everything\": \"所有内容\",\n    \"Find in page\": \"Find in page\",\n    \"Other\": \"其他\",\n    \"Default\": \"默认\",\n    \"Modules\": \"模块\",\n    \"Import data\": \"导入数据\",\n    \"Export data\": \"导出数据\",\n    \"Enabled\": \"启用\",\n    \"Disabled\": \"停用\",\n    \"Untitled\": \"无标题\",\n    \"Line of\": \"Line {{currentLine}} of {{numberOfLines}}\",\n    \"encryption\": {\n        \"provide password\": \"请提供你的密码\",\n        \"change password\": \"输入新密码\",\n        \"wait\": \"请等待加密完成\",\n        \"error\": \"加密错误\",\n        \"errorConfirm\": \"解密数据错误。\\r\\r 如果你在其他浏览器修改了加密设置, 请在次浏览器**更新设置**。或者试试导入设置。\\r\\r 如果你没有修改加密设置, 试试**重新登录**。\",\n        \"errorConfirmSettings\": \"修改加密设置\",\n        \"errorConfirmAuth\": \"重试一次\",\n        \"backup\": {\n            \"title\": \"备份数据\",\n            \"content\": \"请在下一步之前，下载你的备份文件。它包括配置修改之前的未加密数据，请放在安全的地方。\",\n            \"next\": \"处理，但不下载备份文件\"\n        },\n        \"state\": {\n            \"decrypt\": \"解密所有内容\",\n            \"encrypt\": \"加密所有内容\",\n            \"save\": \"保存修改\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"配置名\",\n        \"confirm remove\": \"配置 **{{profile}}** 将会和数据一块删除， 包括笔记，标签以及笔记本。该操作不可恢复。\",\n        \"type name\": \"输入配置名称\"\n    },\n    \"files\": {\n        \"file-url\": \"文件或图片URL\",\n        \"attach\": \"添加文件\",\n        \"attachLink\": \"添加为链接\",\n        \"attachImage\": \"添加为图片\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"笔记 **{{title}}** 将被移动到垃圾桶。\",\n        \"confirm remove\": \"笔记 **{{title}}** 将会**彻底**删除 !\",\n        \"create and attach\": \"创建新的笔记并且添加链接\",\n        \"create\": \"创建新的笔记\",\n        \"hyperlink-dialog\": \"笔记的名称或是URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"选择笔记本\",\n        \"add\": \"添加新的笔记本\",\n        \"edit\": \"编辑笔记本\",\n        \"name\": \"请输入笔记本名\",\n        \"confirm remove\": \"笔记本 **{{name}}** 将会**彻底**删除！\",\n        \"remove with notes\": \"是的，和笔记一块删除\",\n        \"remove\": \"是的，删除\"\n    },\n    \"tags\": {\n        \"name\": \"需要标签名称\",\n        \"add\": \"添加新的标签\",\n        \"edit\": \"编辑标签\",\n        \"confirm remove\": \"标签 **{{name}}** 将会**彻底**删除！\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"现在你会被重定向到 **Dropbox** 授权页面。\\r> 请点击 **OK** 按钮。\",\n        \"auth title\": \"Dropbox 授权\",\n        \"api info 1\": \"You can have your own API key on\",\n        \"api info 2\": \"When you create a new app at Dropbox's Developer site you should keep in mind that:\",\n        \"api info li 1\": \"Type of API should be **Dropbox API**\",\n        \"api info li 2\": \"Type of access should be **App Folder**\"\n    },\n    \"help\": {\n        \"firststart title\": \"欢迎使用 Laverna\",\n        \"firststart import\": \"如果你以前使用过 Laveran，你可以点击下面的 '导入' 按钮导入你以前的配置。\",\n        \"firststart next\": \"如果你从未用过 Laverna, 点击 '下一步' 开始安装。\",\n        \"firststart encryption\": \"如果你想使用加密，请输入密码。\",\n        \"firststart sync\": \"由于我们不在我们的服务器存储任何数据, 你需要启用一个同步方式以便在其他设备上查看你的笔记。\",\n        \"firststart backup\": \"几乎所有的东西都准备好了。 你可以下载配置备份并且进行最后一步啦！\"\n    }\n}\n"
  },
  {
    "path": "app/locales/zh_tw/translation.json",
    "content": "{\n    \"en\" : \"英語\",\n    \"ru\" : \"俄語\",\n    \"nl\" : \"荷蘭語\",\n    \"fr\" : \"法語\",\n    \"pt_br\" : \"巴西葡萄牙語\",\n    \"eo\": \"世界語\",\n    \"es\": \"西班牙語\",\n    \"de\": \"德語\",\n    \"se\": \"瑞典語\",\n    \"el\": \"希臘語\",\n    \"nb\": \"挪威語 (波克默爾語)\",\n    \"nn\": \"挪威語 (尼諾斯克語)\",\n    \"Search\": \"搜尋\",\n    \"All notes\": \"所有筆記\",\n    \"Favourites\": \"已加星號\",\n    \"Favorite\": \"星號\",\n    \"Trash\": \"垃圾桶\",\n    \"Open tasks\": \"待處理任務\",\n    \"Notebooks\": \"所有筆記本\",\n    \"Settings\": \"偏好設定\",\n    \"About\": \"關於\",\n    \"Save\": \"儲存\",\n    \"Save & Exit\": \"儲存並退出\",\n    \"Cancel\": \"取消\",\n    \"Fullscreen\": \"全螢幕編輯\",\n    \"Full screen\": \"全螢幕編輯\",\n    \"Preview\": \"編輯及預覽\",\n    \"Normal\": \"一般編輯\",\n    \"Select notebook\": \"選擇筆記本\",\n    \"Title\": \"標題\",\n    \"Submit\": \"提交\",\n    \"Tags\": \"所有標籤\",\n    \"Tag\": \"標籤\",\n    \"Parent\": \"上一層\",\n    \"Root\": \"根目錄\",\n    \"Notebooks & tags\": \"筆記本和標籤\",\n    \"Notebook\": \"筆記本\",\n    \"Restore\": \"還原\",\n    \"Delete\": \"刪除\",\n    \"New tag\": \"建立標籤\",\n    \"Edit\": \"編輯\",\n    \"Remove\": \"移除\",\n    \"Forever\": \"永遠\",\n    \"No\": \"否\",\n    \"Yes\": \"是\",\n    \"Basic\": \"基本\",\n    \"Cloud storage\": \"雲端儲存\",\n    \"Notes per page\": \"每頁筆記數\",\n    \"Sort notebooks\": \"筆記本排序\",\n    \"Name\": \"名稱\",\n    \"Created\": \"建立者\",\n    \"Default edit mode\": \"預設編輯模式\",\n    \"Fullscreen with preview\": \"編輯及預覽\",\n    \"Use encryption\": \"加密\",\n    \"Encryption parameters\": \"參數\",\n    \"Encryption Password\": \"密碼\",\n    \"Salt\": \"加鹽\",\n    \"Random\": \"隨機\",\n    \"Key size\": \"金鑰長度\",\n    \"Strengthen by a factor of\": \"加強元素\",\n    \"Authentication strength\": \"驗證長度\",\n    \"Unlock\": \"解鎖\",\n    \"Your new encryption password\": \"新密碼\",\n    \"Your old encryption password\": \"舊密碼\",\n    \"Show sidebar\": \"顯示側邊欄\",\n    \"Shortcuts\": \"熱鍵\",\n    \"Previous\": \"上一步\",\n    \"Next\": \"下一步\",\n    \"Navigation\": \"瀏覽\",\n    \"navigateTop\": \"頂部\",\n    \"navigateBottom\": \"底部\",\n    \"Jump\": \"切換\",\n    \"jumpInbox\": \"切換到收件夾\",\n    \"jumpNotebook\": \"切換到筆記列表\",\n    \"jumpFavorite\": \"切換到已加星號列表\",\n    \"jumpRemoved\": \"切換到已刪除筆記列表\",\n    \"jumpOpenTasks\": \"切換到待處理任務筆記列表\",\n    \"Actions\": \"動作\",\n    \"actionsEdit\": \"編輯\",\n    \"actionsOpen\": \"打開\",\n    \"actionsRemove\": \"移除\",\n    \"actionsRotateStar\": \"加入星號\",\n    \"App\": \"應用\",\n    \"appCreateNote\": \"新增筆記\",\n    \"appSearch\": \"搜尋筆記\",\n    \"appKeyboardHelp\": \"查看熱鍵\",\n    \"Change keybindings\": \"修改熱鍵綁定\",\n    \"Donate\": \"贊助\",\n    \"Github page\": \"Github page\",\n    \"Report bugs and issues here\": \"回報臭蟲\",\n    \"Report bugs through email\": \"寄發郵件回報臭蟲\",\n    \"Credits\": \"Credits\",\n    \"List of contributors\": \"List of contributors\",\n    \"List of all used libraries\": \"List of all used libraries\",\n    \"Are you sure?\": \"確定嗎?\",\n    \"You have unsaved changes\": \"你的修改未儲存，真的要放棄修改嗎？\",\n    \"Dropbox API key\": \"Dropbox API key\",\n    \"Required\": \"必須\",\n    \"Optional\": \"可選\",\n    \"Language\": \"語言\",\n    \"Action\": \"動作\",\n    \"Select\": \"選擇\",\n    \"General\": \"通用\",\n    \"Encryption\": \"加密\",\n    \"Keybindings\": \"熱鍵\",\n    \"Sync\": \"同步\",\n    \"Profiles\": \"使用者設定檔\",\n    \"Import\": \"匯入\",\n    \"Transfer data\": \"備份與還原\",\n    \"Transfer settings\": \"偏好設定\",\n    \"Import settings\": \"匯入偏好設定\",\n    \"Export settings\": \"匯出偏好設定\",\n    \"Wrong format\": \"格式錯誤\",\n    \"useDefaultConfigs\": \"使用預設設定\",\n    \"File should be in json format\": \"文件應該是 json 格式\",\n    \"Close\": \"關閉\",\n    \"Hyperlink\": \"超連結\",\n    \"Editor\": \"編輯器\",\n    \"Preview\": \"編輯及預覽\",\n    \"Download\": \"下載\",\n    \"Transfer everything\": \"資料\",\n    \"Find in page\": \"Find in page\",\n    \"Other\": \"其他\",\n    \"Default\": \"預設\",\n    \"Modules\": \"擴充功能\",\n    \"Import data\": \"匯入資料\",\n    \"Export data\": \"匯出資料\",\n    \"Enabled\": \"啟用\",\n    \"Disabled\": \"停用\",\n    \"Untitled\": \"無標題\",\n    \"Line of\": \"{{currentLine}} / {{numberOfLines}}\",\n    \"Drop files\": \"將檔案拖放到這裡上傳\",\n\t\"Spaces per indent\": \"Spaces per indent\",\n\t\"Sort notes\": \"筆記排序\",\n\t\"Updated date\": \"修改日期\",\n\t\"Created date\": \"新增日期\",\n\t\"Text editor\": \"文字編輯器\",\n\t\"Vim\": \"Vim\",\n\t\"Emacs\": \"Emacs\",\n\t\"Sublime\": \"Sublime\",\n    \"encryption\": {\n        \"provide password\": \"請提供你的密碼\",\n        \"change password\": \"輸入新密碼\",\n        \"wait\": \"加密中\",\n        \"error\": \"加密錯誤\",\n        \"errorConfirm\": \"資料解密錯誤。\\r\\r 如果你在其他瀏覽器調整了加密設置, 請在此瀏覽器**更新設置**。或試著匯入設置。\\r\\r 如果你沒有修改加密設置, 請嘗試**重新登入**。\",\n        \"errorConfirmSettings\": \"加密設置\",\n        \"errorConfirmAuth\": \"重試一次\",\n        \"backup\": {\n            \"title\": \"資料備份\",\n            \"content\": \"請在下一步之前，下載你的備份文件。它包括了修改設定之前的未加密數據，請放在安全的地方。\",\n            \"next\": \"執行，但不下載備份文件\"\n        },\n        \"state\": {\n            \"decrypt\": \"解密所有內容\",\n            \"encrypt\": \"加密所有內容\",\n            \"save\": \"儲存修改\"\n        }\n    },\n    \"profile\": {\n\t\t\"profile name\": \"設定檔名稱\",\n        \"confirm remove\": \"使用者設定檔 **{{profile}}** 將會連同所有資料一起刪除，包括筆記、標籤以及筆記本。此操作不可還原。\",\n        \"type name\": \"輸入設定檔名稱\"\n    },\n    \"files\": {\n        \"file-url\": \"檔案或圖片URL\",\n        \"attach\": \"附加文件\",\n        \"attachLink\": \"附加連結\",\n        \"attachImage\": \"附加圖片\"\n    },\n    \"notes\": {\n        \"confirm trash\": \"筆記 **{{title}}** 將被移動到垃圾桶。\",\n        \"confirm remove\": \"筆記 **{{title}}** 將會**徹底**刪除 !\",\n        \"create and attach\": \"建立新的筆記並且附加連結\",\n        \"create\": \"建立新的筆記\",\n        \"hyperlink-dialog\": \"筆記的名稱或是URL\"\n    },\n    \"notebooks\": {\n        \"select\": \"選擇筆記本\",\n        \"add\": \"建立新的筆記本\",\n        \"edit\": \"編輯筆記本\",\n        \"name\": \"請輸入筆記本名稱\",\n        \"confirm remove\": \"筆記本 **{{name}}** 將會**徹底**刪除！\",\n        \"remove with notes\": \"是的，和筆記一塊刪除\",\n        \"remove\": \"是的，刪除\"\n    },\n    \"tags\": {\n        \"name\": \"需要標籤名稱\",\n        \"add\": \"建立新的標籤\",\n        \"edit\": \"編輯標籤\",\n        \"confirm remove\": \"標籤 **{{name}}** 將會**徹底**刪除！\"\n    },\n    \"dropbox\": {\n        \"auth confirm\": \"現在你將會被重新導向 **Dropbox** 授權頁面。\\r> 請點擊 **OK** 按鈕。\",\n        \"auth title\": \"Dropbox 授權\",\n        \"api info 1\": \"You can have your own API key on\",\n        \"api info 2\": \"When you create a new app at Dropbox's Developer site you should keep in mind that:\",\n        \"api info li 1\": \"Type of API should be **Dropbox API**\",\n        \"api info li 2\": \"Type of access should be **App Folder**\"\n    },\n    \"help\": {\n        \"firststart title\": \"歡迎使用 Laverna\",\n        \"firststart import\": \"如果你以前使用過 Laveran，你可以點選的 '匯入' 按鈕匯入你以前的設定。\",\n        \"firststart next\": \"如果你從未用過 Laverna, 點選 '下一步' 開始安裝。\",\n        \"firststart encryption\": \"如果你想使用加密，請輸入密碼。\",\n        \"firststart sync\": \"由於我們不在服務器存儲任何數據, 你需要啟用一個同步方式以便在其他裝置上查瀏覽你的筆記。\",\n        \"firststart backup\": \"幾乎所有的東西都準備好了。 你可以下載設定備份並且進行最後一步啦！\"\n    }\n}\n"
  },
  {
    "path": "app/manifest.webapp",
    "content": "{\n    \"name\": \"laverna\",\n    \"description\": \"Open source note taking web application\",\n    \"version\": \"0.6.2\",\n    \"launch_path\": \"/index.html\",\n    \"icons\": {\n        \"512\": \"/images/icon/icon.png\",\n        \"128\": \"/images/icon/icon-128x128.png\",\n        \"16\": \"/images/icon/icon-16x16.png\",\n        \"36\": \"/images/icon/icon-36x36.png\",\n        \"48\": \"/images/icon/icon-48x48.png\",\n        \"72\": \"/images/icon/icon-72x72.png\"\n    },\n    \"developer\": {\n        \"name\": \"Laverna project\",\n        \"url\": \"https://github.com/Laverna/laverna/\"\n    },\n    \"default_locale\": \"en\",\n    \"installs_allowed_from\": [\"*\"]\n}\n"
  },
  {
    "path": "app/migrate.html",
    "content": "<!DOCTYPE html>\n<html class=\"no-js\">\n    <head>\n        <meta charset=\"utf-8\">\n        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n        <title>Laverna</title>\n        <meta name=\"description\" content=\"Note taking web app with markdown support\">\n        <meta name=\"format-detection\" content=\"telephone=no\">\n        <meta name=\"viewport\" content=\"user-scalable=no, initial-scale=1,\n                maximum-scale=1, minimum-scale=1, width=device-width, height=device-height\">\n\n        <link rel=\"stylesheet\" href=\"styles/core.css\">\n        <link rel=\"stylesheet\" href=\"styles/theme-default.css\">\n        <script src=\"bower_components/modernizr/modernizr.js\"></script>\n    </head>\n    <body class=\"-noscroll\">\n\n        <div class=\"layout\" id=\"wrapper\">\n        </div>\n\n        <!--[if lt IE 7]>\n            <p class=\"chromeframe\">You are using an <strong>outdated</strong> browser. Please <a href=\"http://browsehappy.com/\">upgrade your browser</a> or <a href=\"http://www.google.com/chromeframe/?redirect=true\">activate Google Chrome Frame</a> to improve your experience.</p>\n        <![endif]-->\n\n        <script data-main=\"scripts/migrate\" src=\"bower_components/requirejs/require.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "app/robots.txt",
    "content": "# robotstxt.org\n\nUser-agent: *\n"
  },
  {
    "path": "app/scripts/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define, Modernizr*/\ndefine([\n    'helpers/underscore-util',\n    'jquery',\n    'backbone',\n    'backbone.radio',\n    'devicejs',\n    'regions/regionManager',\n    'marionette',\n    'i18next'\n], function(_, $, Backbone, Radio, Device) {\n    'use strict';\n\n    var App = new Backbone.Marionette.Application(),\n        env = {\n            isWebkit : ('WebkitAppearance' in document.documentElement.style),\n            isMobile : (Device.mobile() === true || Device.tablet() === true),\n            platform : 'browser',\n            ua       : window.navigator.userAgent\n        },\n        render;\n\n    env.useWorkers = (Modernizr.webworkers && window.location.protocol !== 'file:' && !env.isWebkit);\n\n    if (/(palemoon|sailfish)/i.test(env.ua)) {\n        env.useWorkers = false;\n    }\n\n    if (env.isMobile) {\n        env.platform = 'mobile';\n    }\n    else if (window.requireNode) {\n        env.platform = 'electron';\n    }\n\n    // Customize underscore template\n    _.templateSettings = {\n        evaluate    : /<%([\\s\\S]+?)%>/g,\n        interpolate : /\\{=([\\s\\S]+?)\\}/g,\n        escape      : /\\{\\{([\\s\\S]+?)\\}\\}/g,\n    };\n\n    /**\n     * Overrite renderer in order to have access to\n     * additional functions in templates (like, i18n).\n     */\n    render = Backbone.Marionette.Renderer.render;\n\n    Backbone.Marionette.Renderer.render = function(template, data) {\n        data = _.extend(data || {}, {\n            i18n      : $.t,\n            cleanXSS  : _.cleanXSS,\n            stripTags : _.stripTags\n        });\n\n        return render(template, data);\n    };\n\n    // Start a module\n    App.startSubApp = function(appName, args) {\n        var currentApp = appName ? App.module(appName) : null;\n        if (App.currentApp === currentApp) { return; }\n\n        // Stop previous app if current app is not modal\n        if (App.currentApp && (!currentApp.options.modal || env.isMobile)) {\n            App.currentApp.stop();\n        }\n\n        App.currentApp = currentApp;\n        if (currentApp) {\n            App.channel.trigger('app:module', appName);\n            currentApp.start(args);\n        }\n\n        return true;\n    };\n\n    // Returns current app\n    Radio.reply('global', 'app:current', function() {\n        return App.currentApp;\n    });\n\n    // @ToMove somewhere else\n    App.channel.on('app:start', function() {\n        $('.-loading').removeClass('-loading');\n    });\n\n    Radio.reply('global', 'device', function(method) {\n        return Device[method]();\n    });\n\n    Radio.reply('global', 'platform', function() {\n        return env.platform;\n    });\n\n    Radio.reply('global', 'use:webworkers', function() {\n        return env.useWorkers;\n    });\n\n    App.on('before:start', function() {\n        Radio.trigger('global', 'app:init');\n    });\n\n    App.on('start', function() {\n        console.timeEnd('App');\n        Backbone.history.start({pushState: false});\n        Radio.trigger('global', 'app:start');\n    });\n\n    return App;\n});\n"
  },
  {
    "path": "app/scripts/apps/confirm/appConfirm.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules',\n    'apps/confirm/show/controller'\n], function(_, Marionette, Radio, Modules, Controller) {\n    'use strict';\n\n    /**\n     * Confirm module. We use it as a replacement for window.confirm.\n     *\n     * Replies on channel `Confirm` to:\n     * 1. request `start` - starts itself.\n     * 2. request `stop`  - stops itself.\n     */\n    var Confirm = Modules.module('Confirm', {startWithParent: false});\n\n    Confirm.on('start', function(options) {\n        Confirm.controller = new Controller(options);\n    });\n\n    Confirm.on('stop', function() {\n        Confirm.controller.destroy();\n        Confirm.controller = null;\n    });\n\n    // Initializer\n    Radio.request('init', 'add', 'app', function() {\n        Radio.reply('Confirm', 'start', Confirm.start, Confirm);\n        Radio.reply('Confirm', 'stop', Confirm.stop, Confirm);\n    });\n\n    return Confirm;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/confirm/show/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'apps/confirm/show/view'\n], function(_, Q, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * Confirm controller.\n     *\n     * For default it triggers the following events on `Confirm` channel:\n     * 1. `confirm` - when a user clicks on \"OK\" button.\n     * 2. `cancel`  - when a user clicks on \"Cancel\" button.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            var self = this;\n\n            if (typeof options === 'string') {\n                options = {content: options};\n            }\n\n            this.options = options;\n            new Q((function() {\n\n                // If instead of text a view was provided, render it\n                if (typeof options.content === 'object') {\n                    return options.content.render().$el.html();\n                }\n\n                // Try to make HTML from supposedly Markdown string\n                return Radio.request('markdown', 'render', options.content);\n            })())\n            .then(function(content) {\n                self.options.content = content;\n                return self.show();\n            });\n\n        },\n\n        onDestroy: function() {\n            this.stopListening(this.view);\n            this.view.trigger('destroy');\n        },\n\n        show: function() {\n            // Instantiate a view\n            this.view = new View(this.options);\n            Radio.request('global', 'region:show', 'modal', this.view);\n\n            // Events\n            this.listenTo(this.view, 'click', this.triggerEvent);\n        },\n\n        triggerEvent: function(event) {\n            if (this.options['on' + event]) {\n                this.options['on' + event]();\n            }\n            Radio.trigger('Confirm', event);\n\n            // Stop itself\n            Radio.request('Confirm', 'stop');\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/confirm/show/template.html",
    "content": "<div class=\"modal-dialog modal-mini\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h3 class=\"modal-title\">{{ getTitle() }}</h3>\n        </div>\n        <div class=\"modal-body\">\n            <p>{= cleanXSS(content) }</p>\n        </div>\n        <div class=\"modal-footer\">\n            <button type=\"button\" class=\"btn btn-default\" data-event=\"cancel\">\n                {{ i18n('Cancel') }}\n            </button>\n            <button type=\"button\" class=\"btn btn-success\" data-event=\"confirm\">\n                {{ i18n('OK') }}\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/confirm/show/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/confirm/show/template.html',\n    'mousetrap'\n], function(_, Marionette, Radio, Tmpl, Mousetrap) {\n    'use strict';\n\n    /**\n     * Confirm view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'modal fade',\n\n        events: {\n            'click .modal-footer .btn': 'triggerEvent'\n        },\n\n        serializeData: function() {\n            return this.options;\n        },\n\n        templateHelpers: function() {\n            return {\n                getTitle: function() {\n                    return this.i18n(this.title || 'Are you sure?');\n                }\n            };\n        },\n\n        initialize: function() {\n            if (this.options.template) {\n                this.template = _.template(this.options.template);\n            }\n\n            _.bindAll(this, 'focusNextBtn');\n            Mousetrap.bind('tab', this.focusNextBtn);\n\n            // Events\n            this.on('shown.modal', this.onShown, this);\n            this.on('hidden.modal', this.refuseOnHide, this);\n        },\n\n        onDestroy: function() {\n            Mousetrap.unbind('tab');\n        },\n\n        onShown: function() {\n            var $btn = this.$('.btn:last');\n            $btn.focus();\n        },\n\n        triggerEvent: function(e) {\n            var $btn = $(e.currentTarget);\n            this.trigger('click', $btn.attr('data-event'));\n        },\n\n        refuseOnHide: function() {\n            this.trigger('click', 'cancel');\n        },\n\n        focusNextBtn: function(e) {\n            var $btn = this.$('.modal-footer .btn:focus').next();\n            $btn = $btn.length ? $btn : this.$('.modal-footer .btn:first');\n            $btn.focus();\n            e.preventDefault();\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/appEncrypt.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'jquery',\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'text!apps/encryption/auth/errorConfirm.html'\n], function($, _, Q, Marionette, Radio, App, ConfirmTmpl) {\n    'use strict';\n\n    /**\n     * Encryption module.\n     *\n     * Listens to events:\n     * 1. channel: `encrypt`, event: `changed`\n     *    navigate to re-encryption page.\n     *\n     * Requests:\n     * 1. channel: `encrypt`, request: `check:auth`\n     *    in order to check whether the user is authorized.\n     */\n    var Encrypt = App.module('AppEncrypt', {startWithParent: false}),\n        controller;\n\n    Encrypt.Router = Marionette.AppRouter.extend({\n        appRoutes: {\n            '(p/:profile/)auth'        : 'showAuth',\n            '(p/:profile/)encrypt/all' : 'showEncrypt'\n        },\n\n        // Starts itself\n        onRoute: function() {\n            if (!Encrypt._isInitialized) {\n                App.startSubApp('AppEncrypt', {profile: arguments[2][0]});\n            }\n        }\n    });\n\n    function startModule(module, args) {\n        if (!module) {\n            return;\n        }\n\n        $('.-loading').removeClass('-loading');\n\n        // Stop previous module\n        if (Encrypt.currentApp) {\n            Encrypt.currentApp.stop();\n        }\n\n        Encrypt.currentApp = module;\n        args.profile       = args.profile || Radio.request('uri', 'profile');\n        module.start(args);\n\n        // If module has stopped, remove the variable\n        module.on('stop', function() {\n            Encrypt.currentApp = null;\n        });\n    }\n\n    controller = {\n        showAuth: function(profile) {\n            requirejs(['apps/encryption/auth/app'], function(Module) {\n                startModule(Module, {profile: profile});\n            });\n        },\n\n        showEncrypt: function(profile) {\n            requirejs(['apps/encryption/encrypt/app'], function(Module) {\n                startModule(Module, {profile: profile});\n            });\n        },\n\n        // On encrypt/decrypt error, remove PBKDF2 key from the session\n        _confirmAuth: function() {\n            Radio.once('Confirm', 'auth', function() {\n                Radio.request('encrypt', 'delete:secureKey');\n                window.location.reload();\n            });\n\n            Radio.once('Confirm', 'openSettings', function() {\n                Radio.request('uri', 'navigate', '/settings/encryption', {\n                    trigger       : true,\n                    includeProfile: true\n                });\n            });\n\n            Radio.request('Confirm', 'start', {\n                title     : 'encryption.error',\n                content   : $.t('encryption.errorConfirm'),\n                template  : ConfirmTmpl\n            });\n        },\n\n        _checkAuth: function() {\n            var isAuthed = Radio.request('encrypt', 'check:auth');\n\n            if (isAuthed === true) {\n                return Radio.trigger('appEncrypt', 'auth:success');\n            }\n            else if (isAuthed && isAuthed.isChanged === true) {\n                return;\n            }\n\n            // Show auth form\n            controller.showAuth();\n        }\n    };\n\n    /**\n     * Initializers and finalizers\n     */\n    Encrypt.on('before:start', function() {\n    });\n\n    Encrypt.on('before:stop', function() {\n        Encrypt.currentApp.stop();\n        Encrypt.currentApp = null;\n    });\n\n    // Check whether a user is authorized when everything is ready\n    Radio.request('init', 'add', 'auth', function() {\n        var defer = Q.defer();\n\n        Radio.on('encrypt', 'changed', controller.showEncrypt, controller);\n        Radio.on('encrypt', 'decrypt:error', controller._confirmAuth, controller);\n\n        Radio.on('appEncrypt', 'auth:success', function() {\n            if (Encrypt.currentApp) {\n                Encrypt.currentApp.stop();\n            }\n            Encrypt.stop();\n            defer.resolve();\n        });\n\n        controller._checkAuth();\n        return defer.promise;\n    });\n\n    // Register the router\n    App.on('before:start', function() {\n        new Encrypt.Router({\n            controller: controller\n        });\n    });\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/auth/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/encryption/auth/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    var Auth = App.module('AppEncrypt.Auth', {startWithParent: false});\n\n    /**\n     * Initializers and finalizers\n     */\n    Auth.on('before:start', function(options) {\n        Auth.controller = new Controller(options);\n    });\n\n    Auth.on('before:stop', function() {\n        Auth.controller.destroy();\n        Auth.controller = null;\n    });\n\n    return Auth;\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/auth/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/encryption/auth/view'\n], function(_, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * Auth controller. It shows authorization form.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            this.options = options;\n            this.show();\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'brand');\n        },\n\n        show: function() {\n            this.view = new View();\n\n            // Show auth form\n            Radio.request('global', 'region:show', 'brand', this.view);\n            this.view.trigger('shown');\n\n            // Events\n            this.listenTo(this.view, 'login', this.login);\n        },\n\n        login: function(pwd) {\n            var self = this;\n\n            Radio.request('encrypt', 'check:password', pwd)\n            .then(function(isAuth) {\n                if (!isAuth) {\n                    return self.view.trigger('invalid:password');\n                }\n\n                self.onAuth(pwd);\n            });\n        },\n\n        onAuth: function(pwd) {\n            Radio.request('encrypt', 'save:secureKey', pwd)\n            .then(function() {\n                Radio.trigger('appEncrypt', 'auth:success');\n\n                if (document.location.hash.search('auth') !== -1) {\n                    Radio.request('uri', 'back');\n                }\n            });\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/auth/errorConfirm.html",
    "content": "<div><div class=\"modal-dialog modal-mini\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h3 class=\"modal-title\">{{ getTitle() }}</h3>\n        </div>\n        <div class=\"modal-body\">\n            <p>{{content}}</p>\n        </div>\n        <div class=\"modal-footer\">\n            <button type=\"button\" class=\"btn btn-default\" data-event=\"openSettings\">\n                <i class=\"icon-cog\"></i>\n                {{ i18n('encryption.errorConfirmSettings') }}\n            </button>\n            <button type=\"button\" class=\"btn btn-success\" data-event=\"auth\">\n                <i class=\"icon-lock\"></i>\n                {{ i18n('encryption.errorConfirmAuth') }}\n            </button>\n        </div>\n    </div>\n</div></div>\n"
  },
  {
    "path": "app/scripts/apps/encryption/auth/template.html",
    "content": "<div class=\"container text-center -auth\">\n    <form action=\"#\" class=\"form-wrapper\" role=\"form\" method=\"post\">\n        <header>\n            <h1 class=\"header--brand\"><i class=\"icon-lock\"></i></h1>\n            <h1 class=\"header--brand\">Laverna</h1>\n        </header>\n\n        <div class=\"form-group form--icon\">\n            <input class=\"form-control input-lg input--brand\" type=\"password\" name=\"password\"\n            placeholder=\"{{ i18n('Encryption Password') }}\" required=\"\" autofocus />\n        </div>\n\n        <div class=\"form-group\">\n            <button class=\"btn btn-lg btn-block btn--brand\" type=\"submit\">\n                {{ i18n('Unlock') }}\n            </button>\n        </div>\n    </form>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/encryption/auth/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'text!apps/encryption/auth/template.html'\n], function (_, $, Marionette, Tmpl) {\n    'use strict';\n\n    /**\n     * Auth view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        events: {\n            'submit .form-wrapper'  : 'login'\n        },\n\n        ui: {\n            password : 'input[name=password]'\n        },\n\n        initialize: function () {\n            this.on('shown', this.focusPassword, this);\n\t\t\tthis.on('invalid:password', this.wrongPwd, this);\n        },\n\n        focusPassword: function () {\n            this.ui.password.focus();\n        },\n\n        login: function (e) {\n            e.preventDefault();\n            this.trigger('login', this.ui.password.val());\n        },\n\n\t\twrongPwd: function(){\n\t\t\t// Create the shake function from jQuery-UI\n\t\t\t// without using the whole package\n\t\t\tjQuery.fn.shake = function(duration, shakes, distance) {\n\t\t\t    duration = duration || 10;\n\t\t\t    shakes = shakes || 10;\n\t\t\t    distance = distance || 5;\n\t\t\t\tthis.css('position', 'relative');\n\t\t\t\tfor (var x=1; x<=shakes; x++) {\n\t\t\t\t\tthis.animate({left:(distance*-1)},(((duration/shakes)/4)))\n\t\t\t\t\t\t.animate({left:distance}, ((duration/shakes)/2))\n\t\t\t\t\t\t.animate({left:0}, (((duration/shakes)/4)));\n\t\t\t\t}\n\t\t\treturn this;\n\t\t\t};\n\n\t\t\t//Clear password form\n\t\t\tvar pwdForm = $('.form-control.input-lg.input--brand');\n\t\t\tpwdForm.val('');\n\n\t\t\t//Shake button\n\t\t\tvar button = $('.btn.btn-lg.btn-block.btn--brand');\n\t\t\tbutton.shake();\n\t\t}\n\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/encrypt/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/encryption/encrypt/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    /**\n     * Sub app which handles encryption and re-encryption.\n     */\n    var Encrypt = App.module('AppEncrypt.Encrypt', {startWithParent: false});\n\n    /**\n     * Initializers and finalizers\n     */\n    Encrypt.on('before:start', function(options) {\n        Encrypt.controller = new Controller(options);\n    });\n\n    Encrypt.on('before:stop', function() {\n        Encrypt.controller.destroy();\n        Encrypt.controller = null;\n    });\n\n    return Encrypt;\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/encrypt/backup.html",
    "content": "<div class=\"container text-center -auth -backup\">\n    <header>\n        <h3 class=\"header--brand\"><i class=\"icon-save\"></i></h3>\n        <h1 class=\"header--brand\">{{i18n('encryption.backup.title')}}</h1>\n    </header>\n\n    <div>\n        <p class=\"-text-small -text-justify\">{{i18n('encryption.backup.content')}}</p>\n    </div>\n\n    <div>\n        <button class=\"btn btn-lg btn-block btn--brand\" id=\"btn--download\">{{i18n('Download')}}</button>\n    </div>\n    <div>\n        <button class=\"btn btn-lg btn-block btn--brand\" id=\"btn--next\"\n            title=\"{{i18n('encryption.backup.next')}}\">\n            {{i18n('Next')}}\n        </button>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/encryption/encrypt/backupView.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'text!apps/encryption/encrypt/backup.html'\n], function(_, Marionette, Tmpl) {\n    'use strict';\n\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        triggers: {\n            'click #btn--download': 'confirm:download',\n            'click #btn--next'    : 'next:step'\n        }\n    });\n\n    return View;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/encrypt/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/encryption/encrypt/view',\n    'apps/encryption/encrypt/backupView'\n], function(Q, _, Marionette, Radio, View, BackupView) {\n    'use strict';\n\n    /**\n     * Encryption controller.\n     *\n     * Listens to events:\n     * 1. channel: `Encryption`, event: `password:valid`\n     *    initilizes encryption.\n     * 2. channel: this.view, event: `check:passwords`\n     *    checks passwords\n     *\n     * Triggers:\n     * 1. channel: `configs`, request: `get:object`\n     * 2. channel: `configs`, request: `reset:encrypt`\n     * 3. channel: `global`, request: `region:show`\n     * 4. channel: `encrypt`, request: `change:configs`\n     * 5. channel: `encrypt`, request: `save:secureKey`\n     * 6. channel: `encrypt`, request: `decrypt:models`\n     * 7. channel: `encrypt`, request: `encrypt:models`\n     */\n    var Controller = Marionette.Object.extend({\n\n        // Collections to encrypt\n        collectionNames : ['notes', 'tags', 'notebooks'],\n        collections     : {},\n\n        initialize: function(options) {\n            _.bindAll(this, 'saveChanges', 'encrypt', 'redirect', 'show', 'encryptProfile', 'showBackup');\n\n            this.options = options;\n            this.vent    = Radio.channel('encrypt');\n\n            // Configs\n            this.configs = Radio.request('configs', 'get:object');\n            this.backup  = _.extend({}, this.configs, this.configs.encryptBackup);\n\n            // Just to be save remove current secure key from the session\n            this.vent.request('delete:secureKey');\n\n            // Show the view\n            Radio.request('configs', 'get:profiles')\n            .then(this.show)\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n\n            // Events\n            this.listenTo(Radio.channel('Encryption'), 'password:valid', this.initEncrypt);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'brand');\n        },\n\n        show: function(profiles) {\n            this.profiles = profiles;\n\n            // Instantiate and show the view\n            this.view = new View({\n                collections : this.collectionNames,\n                configs     : this.configs\n            });\n            Radio.request('global', 'region:show', 'brand', this.view);\n\n            // Events\n            this.listenTo(this.view, 'check:passwords', this.checkPasswords);\n        },\n\n        checkPasswords: function(data) {\n            var self     = this,\n                promises = [];\n\n            /*\n             * If encryption was enabled in old configs but the old password\n             * was not provided by the user, try to use the new password instead.\n             */\n            if (Number(this.backup.encrypt) && (!data.old && data.password)) {\n                data.old = data.password;\n            }\n\n            // Switch to backup configs and check old password\n            if (data.old) {\n                this.vent.request('change:configs', this.backup);\n                promises.push(this.vent.request('check:password', data.old));\n            }\n            // Switch to new configs and check new password\n            if (data.password) {\n                this.vent.request('change:configs', this.configs);\n                promises.push(this.vent.request('check:password', data.password));\n            }\n\n            return Q.all(promises)\n            .then(function(results) {\n                if (!results.length || _.indexOf(results, false) > -1) {\n                    return self.view.trigger('password:invalid', results);\n                }\n\n                self.passwords = data;\n                Radio.trigger('Encryption', 'password:valid');\n            });\n        },\n\n        /**\n         * Initialize encryption.\n         */\n        initEncrypt: function() {\n            var promises = [],\n                profile  = (this.profiles.length === 1 ? this.profiles[0] : 'notes-db'),\n                self     = this;\n\n            this.rawData = {};\n            this.rawData[profile] = {configs: _.map(this.configs, function(item, key) {\n                if (key === 'encrypt') {\n                    item = '0';\n                }\n                if (key === 'encryptBackup') {\n                    item = {};\n                }\n                if (key === 'appProfiles') {\n                    item = JSON.stringify(item);\n                }\n                return {name: key, value: item};\n            })};\n\n            // Re-encrypt every profile\n            _.each(this.profiles, function(profile) {\n                promises.push(function() {\n                    // Use backup configs\n                    self.vent.request('change:configs', self.backup);\n\n                    // Generate PBKDF2 before starting re-encryption\n                    return self.vent.request('save:secureKey', self.passwords.old)\n                    .then(function() {\n                        return self.encryptProfile({\n                            profile: profile\n                        });\n                    });\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(this.resetBackup)\n            .then(this.showBackup)\n            .then(this.redirect)\n            .fail(function() {\n                console.error('Error!', arguments);\n            });\n        },\n\n        /**\n         * Start encryption process\n         */\n        encryptProfile: function(options) {\n            var promises = [],\n                self     = this;\n\n            // Fetch options\n            options          = options || this.options;\n            options.pageSize = 0;\n\n            this.rawData[options.profile] = this.rawData[options.profile] || {};\n\n            // Fetch all collections in a profile\n            _.each(this.collectionNames, function(name) {\n                promises.push(\n                    new Q(Radio.request(name, 'fetch', options))\n                );\n            });\n\n            /**\n             * After the collections are fetched, start re-encryption process.\n             */\n            return Q.all(promises)\n            .spread(function() {\n                // Re-encrypt the collections that are not empty\n                self.collections = _.filter(arguments, function(collection) {\n                    self.rawData[options.profile][collection.storeName] = collection.toJSON();\n                    return collection.length > 0;\n                });\n                self.view.trigger('encrypt:init', self.collections.length);\n            })\n            .then(this.encrypt)\n            .then(this.saveChanges);\n        },\n\n        /**\n         * Encrypt every collection with new encryption configs.\n         */\n        encrypt: function() {\n\n            // Encryption is disabled\n            if (Number(this.configs.encrypt) === 0) {\n                _.each(this.collections, function(collection) {\n                    collection.each(function(model) {\n                        model.set('encryptedData', null);\n                    });\n                });\n                return;\n            }\n\n            var promises = [],\n                self     = this;\n\n            // Use new encryption configs\n            this.vent.request('change:configs', this.configs);\n\n            // Encrypt every collection\n            _.each(this.collections, function(collection) {\n                promises.push(function() {\n                    return self.vent.request(\n                        'encrypt:models', collection\n                    ).then(function() {\n                        return self.checkEncryption(collection);\n                    });\n                });\n            });\n\n            return this.vent.request('save:secureKey', this.passwords.password)\n            .then(function() {\n                return _.reduce(promises, Q.when, new Q());\n            });\n        },\n\n        /**\n         * Validate encryption by picking one of the models in a collection,\n         * decrypting it, and comparing to the original value.\n         */\n        checkEncryption: function(collection) {\n            if (!collection.length) {\n                return new Q();\n            }\n\n            var model = collection.at(0);\n\n            return this.vent.request('decrypt:model', model)\n            .fail(function(e) {\n                console.error('Encryption error:', e);\n                throw new Error('Error with encryption');\n            });\n        },\n\n        /**\n         * Save all changes in every collection.\n         */\n        saveChanges: function() {\n            var promises = [];\n\n            _.each(this.collections, function(collection) {\n                promises.push(function() {\n                    return new Q(Radio.request(collection.storeName, 'save:collection', collection));\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q());\n        },\n\n        /**\n         * Probably we don't need backup configs and we can safely remove them.\n         */\n        resetBackup: function() {\n            return new Q(Radio.request('configs', 'reset:encrypt'));\n        },\n\n        /**\n         * Advice to download backup with data.\n         */\n        showBackup: function() {\n            var defer = Q.defer();\n\n            this.view = new BackupView({\n                data: this.rawData\n            });\n\n            this.view.once('confirm:download', this.downloadBackup, this);\n            this.view.once('next:step', defer.resolve, defer);\n            Radio.request('global', 'region:show', 'brand', this.view);\n\n            return defer.promise;\n        },\n\n        downloadBackup: function() {\n            Radio.request('importExport', 'export', this.rawData);\n        },\n\n        /**\n         * Delete current secure key from session storage and reload the page.\n         */\n        redirect: function() {\n            this.vent.request('delete:secureKey');\n\n            Radio.request('uri', 'navigate', '/notes', {\n                includeProfile : true,\n                trigger        : false\n            });\n            window.location.reload();\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/encryption/encrypt/template.html",
    "content": "<div class=\"container text-center -auth hide\" id=\"encryption-progress\">\n    <div class=\"page-progress\">\n        <header>\n            <h1 class=\"header--brand\"><i class=\"icon-lock\"></i></h1>\n            <h1 class=\"header--brand\">{{ i18n('Encryption') }}</h1>\n        </header>\n\n        <div class=\"progress progress-striped active\">\n            <div class=\"progress-bar progress-bar-success\" id=\"progress\"\n                aria-valuenow=\"2\" aria-valuemin=\"0\" aria-valuemax=\"\"\n                role=\"progressbar\" style=\"width: 0%\"></div>\n        </div>\n\n        <h4 class=\"text-center\">{{ i18n('encryption.wait') }}</h4>\n        <p class=\"text-center\" id=\"state\"></p>\n    </div>\n</div>\n\n<div class=\"container text-center -auth\" id=\"encryption-password\">\n    <form action=\"#\" class=\"form-wrapper\" role=\"form\" method=\"post\">\n        <header>\n            <h1 class=\"header--brand\"><i class=\"icon-lock\"></i></h1>\n            <h1 class=\"header--brand\">Laverna</h1>\n        </header>\n\n        <% if (needOldPassword()) { %>\n        <div class=\"form-group form--icon\">\n            <input class=\"form-control input-lg input--brand\" type=\"password\" name=\"oldpass\"\n            placeholder=\"{{ i18n('Your old encryption password') }}\" autofocus />\n        </div>\n        <% } %>\n        <% if (needNewPassword()) { %>\n        <div class=\"form-group form--icon\">\n            <input class=\"form-control input-lg input--brand\" type=\"password\" name=\"password\"\n            placeholder=\"{{ i18n('Your new encryption password') }}\" autofocus />\n        </div>\n        <% } %>\n\n        <div class=\"form-group\">\n            <input class=\"btn btn-lg btn-block btn--brand\" type=\"submit\"\n            value=\"{{ i18n('Submit') }}\"/>\n        </div>\n    </form>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/encryption/encrypt/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/encryption/encrypt/template.html'\n], function($, _, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Re-encryption view.\n     * Shows auth form or progress bar.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        events: {\n            'submit form': 'checkPasswords'\n        },\n\n        ui: {\n            containerProgress : '#encryption-progress',\n            containerForm     : '#encryption-password',\n\n            progress          : '#progress',\n            state             : '#state',\n\n            // Passwords\n            oldPassword       : 'input[name=oldpass]',\n            password          : 'input[name=password]',\n        },\n\n        initialize: function() {\n            this.progress = {count: 0, max: 0};\n            this.vent    = Radio.channel('encrypt');\n\n            // Events\n            this.listenTo(Radio.channel('Encryption'), 'password:valid', this.showProgress);\n            this.listenTo(this, 'encrypt:init', this.changeMax);\n\n            this.listenTo(Radio.channel('collection'), 'saved:all', this.saveProgress);\n            this.listenTo(this.vent, 'decrypting:models', this.decryptProgress);\n            this.listenTo(this.vent, 'encrypting:models', this.encryptProgress);\n        },\n\n        changeMax: function(max) {\n            this.progress.max = max;\n        },\n\n        saveProgress: function() {\n            this._setProgress('encryption.state.save');\n        },\n\n        decryptProgress: function() {\n            this._setProgress('encryption.state.decrypt');\n        },\n\n        encryptProgress: function() {\n            this._setProgress('encryption.state.encrypt');\n        },\n\n        /**\n         * Hide auth form and show progress bar.\n         */\n        showProgress: function() {\n            this.ui.containerForm.hide();\n            this.ui.containerProgress.removeClass('hide');\n        },\n\n        _setProgress: function(state) {\n            var max = this.progress.max,\n                width;\n\n            // Change progress bar\n            this.progress.count = (\n                this.progress.count >= max ? 1 : this.progress.count + 1\n            );\n            width = Math.floor((this.progress.count * 100) / max);\n            this.ui.progress.css('width', width + '%');\n\n            // Change the status\n            this.ui.state.text($.t(state));\n        },\n\n        checkPasswords: function(e) {\n            e.preventDefault();\n\n            this.trigger('check:passwords', {\n                password : this.ui.password.length ? this.ui.password.val().trim() : null,\n                old      : this.ui.oldPassword.length ? this.ui.oldPassword.val().trim() : null\n            });\n        },\n\n        serializeData: function() {\n            return {\n                configs: this.options.configs\n            };\n        },\n\n        templateHelpers: function() {\n            return {\n                needOldPassword: function() {\n                    return (\n                        (\n                            // Backup password shouldn't be empty\n                            (\n                                !_.isUndefined(this.configs.encryptBackup.encryptPass) &&\n                                this.configs.encryptBackup.encryptPass.length !== 0\n                            ) &&\n                            // Encryption should be enabled\n                            (\n                                _.isUndefined(this.configs.encryptBackup.encrypt) ||\n                                Number(this.configs.encryptBackup.encrypt)\n                            )\n                        ) ||\n                        // Encryption was disabled in new configs\n                        (\n                            !Number(this.configs.encrypt) &&\n                            Number(this.configs.encryptBackup.encrypt)\n                        )\n                    );\n                },\n\n                needNewPassword: function() {\n                    return Number(this.configs.encrypt);\n                }\n            };\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/about/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'app',\n    'apps/help/about/controller'\n], function(_, Marionette, App, Controller) {\n    'use strict';\n\n    /**\n     * Sub module shows information about the app.\n     */\n    var About = App.module('AppHelp.About', {startWithParent: false});\n\n    /**\n     * Initializers and finalizers\n     */\n    About.on('before:start', function(options) {\n        About.controller = new Controller(options);\n\n        // Stop module if controller stops\n        this.listenTo(About.controller, 'destroy', About.stop);\n    });\n\n    About.on('before:stop', function() {\n        this.stopListening();\n        About.controller = null;\n    });\n\n    return About;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/about/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/help/about/view',\n    'constants'\n], function(_, Marionette, Radio, View, constants) {\n    'use strict';\n\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n            this.view = new View({\n                appVersion: constants.VERSION\n            });\n\n            Radio.request('global', 'region:show', 'modal', this.view);\n            this.listenTo(this.view, 'redirect', this.destroy);\n        },\n\n        onDestroy: function() {\n            Radio.request('global', 'region:empty', 'modal');\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/about/template.html",
    "content": "<div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h2 class=\"modal-title\">\n                Laverna <small>Beta v{{appVersion}}</small>\n            </h2>\n        </div>\n        <div class=\"modal-body\">\n            <h3>{{ i18n('About') }}:</h3>\n            <ul class=\"list-unstyled\">\n                <li><a target=\"_blank\" href=\"https://github.com/Laverna/laverna\">{{ i18n('Github Page') }}</a></li>\n                <li><a target=\"_blank\" href=\"https://www.mozilla.org/en-US/MPL/2.0/\">MPL-2.0 {{ i18n('License') }}</a></li>\n                <li><a target=\"_blank\" href=\"https://github.com/Laverna/laverna/issues\">{{ i18n('Report bugs and issues here') }}</a></li>\n                <li><a href=\"mailto:lavernaproject@gmail.com\">{{ i18n('Report bugs through email') }}</a></li>\n            </ul>\n            <hr/>\n            <h3>{{ i18n('Donate') }}</h3>\n            <div class=\"btn-toolbar\">\n                <div class=\"btn-group\">\n                    <a class=\"btn btn-success\" target=\"_blank\" href=\"https://www.bountysource.com/teams/laverna\">\n                        Bountysource\n                    </a>\n                </div>\n                <div class=\"btn-group\">\n                    <a class=\"btn btn-warning\" href=\"bitcoin:1Q68HfLjNvWbLFr3KGK6nfXg7vc3hpDr11\">\n                        Bitcoin\n                    </a>\n                </div>\n                <div class=\"btn-group\">\n                    <a class=\"btn btn-default\" href=\"litecoin:LVXArmrqVCJW58mEuLWK4hDVoCaQaiPNgy\">\n                        Litecoin\n                    </a>\n                </div>\n            </div>\n            <hr/>\n            <h3>{{ i18n('Credits') }}:</h3>\n            <ul class=\"list-unstyled\">\n                <li><a target=\"_blank\" href=\"https://github.com/Laverna/laverna/graphs/contributors\">{{ i18n('List of contributors') }}</a></li>\n                <li><a target=\"_blank\" href=\"https://github.com/Laverna/laverna/blob/master/bower.json\">{{ i18n('List of all used libraries') }}</a></li>\n            </ul>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/help/about/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'behaviors/modal',\n    'text!apps/help/about/template.html'\n], function (_, $, Marionette, ModalBehavior, Tmpl) {\n    'use strict';\n\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'modal fade',\n\n        behaviors: {\n            ModalBehavior: {\n                behaviorClass: ModalBehavior\n            }\n        },\n\n        serializeData: function () {\n            return {\n                appVersion : this.options.appVersion\n            };\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/appHelp.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app'\n], function(_, Marionette, Radio, App) {\n    'use strict';\n\n    var Help = App.module('AppHelp', {startWithParent: false}),\n        controller;\n\n    function startModule(module, args) {\n        if (!module) {\n            return;\n        }\n\n        // Stop previous module\n        if (Help.currentApp) {\n            Help.currentApp.stop();\n        }\n        // Start this subapp\n        else {\n            Help.start();\n        }\n\n        Help.currentApp = module;\n        module.start(args);\n\n        // If module has stopped, remove the variable and stop itself\n        module.on('stop', function() {\n            Help.stop();\n            Help.currentApp = null;\n        });\n    }\n\n    controller = {\n        keybindings: function() {\n            requirejs(['apps/help/show/app'], function(Module) {\n                startModule(Module);\n            });\n        },\n\n        about: function() {\n            requirejs(['apps/help/about/app'], function(Module) {\n                startModule(Module);\n            });\n        },\n\n        firstStart: function() {\n            if (!Number(Radio.request('configs', 'get:config', 'firstStart'))) {\n                return;\n            }\n\n            requirejs(['apps/help/firstStart/app'], function(Module) {\n                startModule(Module);\n            });\n        }\n    };\n\n    Help.on('before:start', function() {\n    });\n\n    Help.on('before:stop', function() {\n    });\n\n    // Add initializer\n    Radio.request('init', 'add', 'app', function() {\n        Radio.once('global', 'app:start', controller.firstStart, controller);\n\n        Radio.reply('Help', {\n            'show:about'        : controller.about,\n            'show:keybindings'  : controller.keybindings\n        }, controller);\n    });\n\n    return Help;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/firstStart/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'app',\n    'apps/help/firstStart/controller'\n], function(_, App, Controller) {\n    'use strict';\n\n    /**\n     * Submodule shows first-start guide.\n     */\n    var FirstStart = App.module('AppHelp.FirstStart', {startWithParent: false});\n\n    /**\n     * Initializers and finalizers\n     */\n    FirstStart.on('before:start', function(options) {\n        FirstStart.controller = new Controller(options);\n\n        // Stop module if controller stops\n        this.listenTo(FirstStart.controller, 'destroy', FirstStart.stop);\n    });\n\n    FirstStart.on('before:stop', function() {\n        this.stopListening();\n        FirstStart.controller = null;\n    });\n\n    return FirstStart;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/firstStart/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'apps/help/firstStart/view',\n    'fileSaver',\n], function(_, Q, Marionette, Radio, View, fileSaver) {\n    'use strict';\n\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n            _.bindAll(this, 'show', 'destroy', 'save', 'mark', 'close');\n\n            this.profile = Radio.request('uri', 'profile');\n\n            Q.all([\n                Radio.request('configs', 'get:config', 'encrypt'),\n                Radio.request('notes', 'fetch', {profile: this.profile}),\n                Radio.request('notebooks', 'fetch', {profile: this.profile}),\n                Radio.request('tags', 'fetch', {profile: this.profile})\n            ])\n            .spread(this.show)\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        onDestroy: function() {\n            Radio.request('global', 'region:empty', 'modal');\n            this.view = null;\n        },\n\n        show: function(encrypt, notes, notebooks, tags) {\n            /**\n             * If encryption is enabled or there is some data, showing\n             * installation process is not necessary.\n             */\n            if (Number(encrypt) ||\n                (notes.length || notebooks.length || tags.length)) {\n                return this.close();\n            }\n\n            // Clear old encryption secure from session storage\n            window.sessionStorage.clear();\n\n            this.view = new View();\n            Radio.request('global', 'region:show', 'modal', this.view);\n\n            this.listenTo(this.view, 'save', this.save);\n            this.listenTo(this.view, 'import', this.import);\n            this.listenTo(this.view, 'close', this.reload);\n            this.listenTo(this.view, 'redirect', this.close);\n            this.listenTo(this.view, 'download', this.download);\n        },\n\n        import: function() {\n            Radio.request('uri', 'navigate', '/settings/importExport', {\n                trigger       : true,\n                includeProfile: true\n            });\n            this.close();\n        },\n\n        /**\n         * Export user's settings.\n         */\n        download: function() {\n            Radio.request('configs', 'get:all', {profile: this.profile})\n            .then(function(configs) {\n                var blob = new Blob(\n                    [JSON.stringify(configs)],\n                    {type: 'text/plain;charset=utf8'}\n                );\n                fileSaver(blob, 'laverna-settings.json');\n\n                window.location.reload();\n            });\n        },\n\n        /**\n         * Save settings.\n         */\n        save: function() {\n            var password     = this.view.ui.password.val().trim(),\n                cloudStorage = this.view.ui.cloudStorage.val().trim(),\n                promises     = [],\n                self         = this;\n\n            if (password.length) {\n                promises.push(this.savePassword(password));\n            }\n\n            if (cloudStorage !== '0') {\n                promises.push(this.saveCloud(cloudStorage));\n            }\n\n            return Q.all(promises)\n            .then(this.mark)\n            .then(function() {\n                if (!promises.length) {\n                    return self.close();\n                }\n\n                self.view.trigger('save:after');\n            });\n        },\n\n        savePassword: function(password) {\n            var encryptSalt = Radio.request('encrypt', 'randomize', 5, 0, true);\n\n            return Q.all([\n                Radio.request('configs', 'save:object', {\n                    name: 'encrypt',\n                    value: '1'\n                }, undefined, {profile: this.profile}),\n                Radio.request('configs', 'save:object', {\n                    name: 'encryptSalt',\n                    value: encryptSalt\n                }, undefined, {profile: this.profile}),\n                Radio.request('configs', 'save:object', {\n                    name: 'encryptPass',\n                    value: password\n                }, undefined, {profile: this.profile}),\n            ]);\n        },\n\n        saveCloud: function(cloudStorage) {\n            return Radio.request('configs', 'save:object', {\n                name: 'cloudStorage',\n                value: cloudStorage\n            }, undefined, {profile: this.profile});\n        },\n\n        /**\n         * Mark that installation process was done.\n         */\n        mark: function() {\n            return Radio.request('configs', 'save:object', {\n                name  : 'firstStart',\n                value : '0'\n            }, undefined, {profile: this.profile});\n        },\n\n        reload: function() {\n            window.location.reload();\n        },\n\n        close: function() {\n            this.mark()\n            .then(this.destroy);\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/firstStart/template.html",
    "content": "<div class=\"modal-dialog modal-lg\" id=\"welcome--page\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h2 class=\"modal-title text-center\">\n                {{i18n('help.firststart title')}}\n            </h2>\n        </div>\n        <div class=\"modal-body\">\n            <div class=\"container-fluid\">\n                <div class=\"col-xs-6\">\n                    <p>{{i18n('help.firststart import')}}</p>\n                </div>\n                <div class=\"col-xs-6\">\n                    <p>{{i18n('help.firststart next')}}</p>\n                </div>\n            </div>\n        </div>\n        <div class=\"modal-footer\">\n            <div class=\"col-xs-6\">\n                <button id=\"welcome--import\" class=\"btn btn-lg btn-default pull-right\">\n                    <i class=\"icon-cog\"></i>\n                    {{i18n('Import')}}\n                </button>\n            </div>\n            <div class=\"col-xs-6\">\n                <button id=\"welcome--next\" class=\"btn btn-lg btn-success pull-left\">\n                    {{i18n('Next')}}\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n\n<div class=\"modal-dialog modal-lg hidden\" id=\"welcome--settings\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h2 class=\"modal-title text-center\">\n                {{i18n('help.firststart title')}}\n            </h2>\n        </div>\n        <div class=\"modal-body\">\n            <div class=\"container-fluid\">\n                <form class=\"form\">\n                    <div class=\"col-xs-12 form-group\">\n                        <legend class=\"text-center\">{{i18n('Encryption Password')}}</legend>\n                        <input type=\"password\" placeholder=\"{{i18n('Encryption Password')}}\" class=\"form-control col-xs-12\" name=\"password\"/>\n                        <p class=\"help-block\">\n                            {{i18n('help.firststart encryption')}}\n                        </p>\n                    </div>\n                    <div class=\"col-xs-12 form-group\">\n                        <legend class=\"text-center\">{{i18n('Synchronization')}}</legend>\n                        <select class=\"form-control col-xs-12\" name=\"cloudStorage\">\n                            <option value=\"0\">{{ i18n('No') }}</option>\n                            <option value=\"dropbox\">Dropbox</option>\n                            <option value=\"remotestorage\">RemoteStorage</option>\n                        </select>\n                        <p class=\"help-block\">\n                            {{i18n('help.firststart sync')}}\n                        </p>\n                    </div>\n                </form>\n            </div>\n        </div>\n        <div class=\"modal-footer\">\n            <div class=\"text-center\">\n                <button id=\"welcome--save\" class=\"btn btn-lg btn-success\">\n                    {{i18n('Next')}}\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n\n<div class=\"modal-dialog modal-lg hidden\" id=\"welcome--backup\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h2 class=\"modal-title text-center\">\n                {{i18n('help.firststart title')}}\n            </h2>\n        </div>\n        <div class=\"modal-body\">\n            <div class=\"container-fluid\">\n                {{i18n('help.firststart backup')}}\n            </div>\n        </div>\n        <div class=\"modal-footer\">\n            <div class=\"text-center\">\n                <button id=\"welcome--export\" class=\"btn btn-lg btn-default\">\n                    {{i18n('Download')}}\n                </button>\n                <button id=\"welcome--last\" class=\"btn btn-lg btn-success\">\n                    {{i18n('Next')}}\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/help/firstStart/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'behaviors/modal',\n    'text!apps/help/firstStart/template.html'\n], function ( _, $, Marionette, ModalBehavior, Tmpl) {\n    'use strict';\n\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'modal fade',\n\n        behaviors: {\n            ModalBehavior: {\n                behaviorClass: ModalBehavior\n            }\n        },\n\n        ui: {\n            'settings'     : '#welcome--settings',\n            'page'         : '#welcome--page',\n            'backup'       : '#welcome--backup',\n            'password'     : 'input[name=\"password\"]',\n            'cloudStorage' : 'select[name=\"cloudStorage\"]'\n        },\n\n        triggers: {\n            'click #welcome--import' : 'import',\n            'click #welcome--save'   : 'save',\n            'click #welcome--last'   : 'close',\n            'click #welcome--export' : 'download',\n        },\n\n        events: {\n            'click #welcome--next': 'onNext',\n        },\n\n        initialize: function() {\n            this.listenTo(this, 'save:after', this.onSave);\n        },\n\n        onNext: function() {\n            this.ui.page.addClass('hidden');\n            this.ui.settings.removeClass('hidden');\n        },\n\n        onSave: function() {\n            this.ui.settings.addClass('hidden');\n            this.ui.backup.removeClass('hidden');\n        },\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/show/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'app',\n    'apps/help/show/controller'\n], function(_, Marionette, App, Controller) {\n    'use strict';\n\n    /**\n     * Sub module shows keybindings list.\n     */\n    var Keybindings = App.module('AppHelp.Keybindings', {startWithParent: false});\n\n    /**\n     * Initializers and finalizers\n     */\n    Keybindings.on('before:start', function(options) {\n        Keybindings.controller = new Controller(options);\n\n        // Stop module if controller stops\n        this.listenTo(Keybindings.controller, 'destroy', Keybindings.stop);\n    });\n\n    Keybindings.on('before:stop', function() {\n        this.stopListening();\n        Keybindings.controller = null;\n    });\n\n    return Keybindings;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/show/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/help/show/view'\n], function(_, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * Keybindings help controller.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n            _.bindAll(this, 'show');\n\n            // Fetch configs\n            Radio.request('configs', 'get:all', {\n                profile: Radio.request('uri', 'profile')\n            }).then(this.show);\n        },\n\n        onDestroy: function() {\n            Radio.request('global', 'region:empty', 'modal');\n        },\n\n        show: function(configs) {\n            configs = configs.clone();\n            configs.reset(configs.shortcuts());\n\n            this.view = new View({\n                collection: configs\n            });\n\n            Radio.request('global', 'region:show', 'modal', this.view);\n            this.listenTo(this.view, 'redirect', this.destroy);\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/help/show/template.html",
    "content": "<div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h3 class=\"modal-title\">\n                {{ i18n('Keybindings') }}\n                <a href=\"#/settings/keybindings\" title=\"{{ i18n('Change keybindings') }}\">\n                    <small class=\"icon-cog\"></small>\n                </a>\n            </h3>\n        </div>\n        <div class=\"modal-body\">\n            <table class=\"table table-striped table-hover\">\n                <thead>\n                    <tr>\n                        <th>{{i18n('Action')}}</th>\n                        <th>{{i18n('Keybindings')}}</th>\n                    </tr>\n                </thead>\n                <tbody>\n                    <% _.forEach(items, function(conf) { %>\n                    <tr>\n                        <td>{{ i18n(conf.name) }}</td>\n                        <td><kbd>{{ conf.value }}</kbd></td>\n                    </tr>\n                    <% }); %>\n                </tbody>\n            </table>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/help/show/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'behaviors/modal',\n    'text!apps/help/show/template.html'\n], function ( _, $, Marionette, ModalBehavior, Tmpl) {\n    'use strict';\n\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'modal fade',\n\n        behaviors: {\n            ModalBehavior: {\n                behaviorClass: ModalBehavior\n            }\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/navbar/appNavbar.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules',\n    'apps/navbar/show/controller'\n], function(_, Marionette, Radio, Modules, Controller) {\n    'use strict';\n\n    /**\n     * Navbar module.\n     *\n     * Replies to requests:\n     * 1. channel: `navbar`, reply: `start`\n     *    starts itself.\n     */\n    var Navbar = Modules.module('Navbar', {startWithParent: false});\n\n    Navbar.on('start', function(options) {\n        Navbar.controller = new Controller(options);\n    });\n\n    Navbar.on('stop', function() {\n        Navbar.controller.destroy();\n        Navbar.controller = null;\n    });\n\n    // Initializer\n    Radio.request('init', 'add', 'app:before', function() {\n        Radio.reply('navbar', 'stop', Navbar.stop, Navbar);\n\n        Radio.reply('navbar', 'start', function(options) {\n            // Just trigger an event\n            if (Navbar._isInitialized) {\n                return Navbar.controller.trigger('change:title', options);\n            }\n\n            Navbar.start(options);\n        });\n\n    });\n\n    return Navbar;\n});\n"
  },
  {
    "path": "app/scripts/apps/navbar/show/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'apps/navbar/show/view'\n], function($, Q, _, Radio, Marionette, View) {\n    'use strict';\n\n    /**\n     * Navbar controller.\n     *\n     * Listens to events:\n     * ------------------\n     * 1. channel: `global`, event: `filter:change`\n     *    re-renders the view on this event.\n     * 3. this.view, event: `search:submit`\n     *    navigates to search page\n     *\n     * Triggers:\n     * ------------------\n     * Requests:\n     * 1. channel: `global`, request: `app:current`\n     * 2. channel: `uri`, request: `link:profile`\n     * 3. channel: `global`, request: `get:title`\n     *\n     * requests:\n     * 1. channel: `uri`, request: `navigate`\n     * 2. channel: `global`, request: `region:show`\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            var profile = {profile: Radio.request('uri', 'profile')};\n            _.bindAll(this, 'show');\n\n            this.options = options;\n\n            // Request notebooks and title\n            Q.all([\n                Radio.request('configs', 'get:all', profile),\n                Radio.request('notebooks', 'get:all', profile),\n                Radio.request('configs', 'get:model', {name: 'appProfiles'}),\n                Radio.request('global', 'get:title', options)\n            ])\n            .spread(this.show);\n\n            // Events\n            this.listenTo(this, 'change:title', this.changeTitle);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'sidebarNavbar');\n        },\n\n        show: function(configs, notebooks, profiles, title) {\n            var args;\n\n            args = _.extend({title: title}, this.options);\n\n            this.view = new View({\n                args       : args,\n                notebooks  : notebooks,\n                collection : configs,\n                profiles   : profiles\n            });\n\n            // Render the view\n            Radio.request('global', 'region:show', 'sidebarNavbar', this.view);\n\n            // Listen to view events\n            this.listenTo(this.view, 'search:submit', this.navigateSearch, this);\n        },\n\n        /**\n         * Changes current title\n         */\n        changeTitle: function(options) {\n            var self = this;\n\n            Radio.request('global', 'get:title', options)\n            .then(function(title) {\n                self.view.trigger('change:title', {title: title, args: options});\n            });\n        },\n\n        /**\n         * Navigate to the search page.\n         */\n        navigateSearch: function(text) {\n            Radio.request('uri', 'navigate', {\n                filter : 'search',\n                query  : text\n            });\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/navbar/show/template.html",
    "content": "<nav class=\"header navbar navbar-default navbar-static-top -left\" id=\"sidebar--nav\">\n<div class=\"container-fluid header--container -left\">\n\n    <ul class=\"nav navbar-nav navbar-left header--col--left\">\n        <li class=\"header--col--left--drawer\">\n            <a href=\"#{{args.currentUrl}}\" class=\"btn-link header--title sidemenu--open -text-elipsis\">\n                <i class=\"icon-menu header--icon\"></i>\n                <span id=\"header--title\" class=\"hidden-xs hidden-sm\">\n                    {{i18n(args.title)}}\n                </span>\n            </a>\n        </li>\n        <li class=\"header--col--left--search\">\n            <button id=\"header--sbtn\" class=\"btn navbar-btn header--sbtn\" title=\"{{i18n('Search')}}\">\n                <i class=\"icon-search\"></i>\n            </button>\n        </li>\n    </ul>\n\n    <form id=\"header--search\" class=\"header--search\" role=\"search\">\n        <input id=\"header--search--input\" class=\"header--search--input form-control\"\n            type=\"text\" placeholder=\"{{i18n('Search')}}\" value=\"{{args.query}}\" />\n    </form>\n\n    <div class=\"navbar-right header--right navbar-btn\">\n        <% if (isSyncEnabled()) { %>\n        <button class=\"btn header--sbtn col-xs-6\" id=\"header--sync\">\n            <span id=\"header--sync--icon\" class=\"icon-arrows\"></span>\n        </button>\n        <% } %>\n        <button id=\"header--add\" class=\"header--add btn btn-danger\">\n            <span class=\"icon-doc-new\"></span>\n        </button>\n    </div>\n\n</div>\n</nav>\n\n<div class=\"sidemenu\">\n    <div class=\"list-group\">\n        <div class=\"list-group-item sidemenu--item -disabled sidemenu--close\">\n            <i class=\"icon-left-open-big\"></i>\n            {{i18n('Close')}}\n            <button type=\"button\" class=\"close\" aria-hidden=\"true\">\n                &times;\n            </button>\n        </div>\n\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{uri}}notes\">\n            <i class=\"icon-note\"></i> {{i18n('All notes')}}\n        </a>\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{uri}}notes/f/favorite\">\n            <i class=\"icon-favorite\"></i> {{i18n('Favourites')}}\n        </a>\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{uri}}notes/f/trashed\">\n            <i class=\"icon-trashed\"></i> {{i18n('Trash')}}\n        </a>\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{uri}}notebooks\">\n            <i class=\"icon-notebook\"></i> {{i18n('Notebooks & tags')}}\n        </a>\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{ uri }}notes/f/task\">\n            <i class=\"icon-ok\"></i> {{ i18n('Open tasks') }}\n        </a>\n\n        <% if (notebooks !== null && notebooks.length) { %>\n        <div class=\"list-group-item sidemenu--item -disabled\">{{i18n('Notebooks')}}</div>\n        <% notebooks.forEach(function(notebook) { %>\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{uri}}notes/f/notebook/q/{{notebook.id}}\">\n            <i class=\"icon-notebook\"></i>  {=cleanXSS(notebook.name)}\n        </a>\n        <% }); } %>\n\n        <div class=\"list-group-item sidemenu--item -disabled\">{{i18n('Profiles')}}</div>\n        <a class=\"list-group-item sidemenu--item<% if (profile === 'notes-db' || !profile) { %> active<% } %>\" href=\"#/notes\">\n            <i class=\"icon-user\"></i>  {{i18n('Default')}}\n        </a>\n        <% _.forEach(profiles, function(prof) { %>\n        <% if (prof !== 'notes-db') { %>\n        <a class=\"list-group-item sidemenu--item<% if (profile === prof) { %> active<% } %>\" href=\"#{{profileLink(prof)}}\">\n            <i class=\"icon-user\"></i>  {{prof}}\n        </a>\n        <% } }); %>\n\n        <div class=\"list-group-item sidemenu--item -disabled\">{{i18n('Other')}}</div>\n        <a class=\"list-group-item sidemenu--item\" href=\"#{{uri}}settings\">\n            <i class=\"icon-cog\"></i>  {{i18n('Settings')}}\n        </a>\n        <a id=\"header--about\" class=\"list-group-item sidemenu--item\" href=\"#\">\n            <i class=\"icon-help-circled\"></i>  {{i18n('About')}}\n        </a>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/navbar/show/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'behaviors/sidemenu',\n    'text!apps/navbar/show/template.html'\n], function($, _, Marionette, Radio, Sidemenu, Tmpl) {\n    'use strict';\n\n    /**\n     * Navbar view.\n     *\n     * Listens to:\n     * ----------\n     * Events:\n     * 1. channel: `global`, event: `show:search`\n     *    focuses on search form\n     *\n     * Triggers events:\n     * 1. channel: `global`, event: `search:shown`\n     *    when the user opens the search form.\n     * 2. channel: `global`, event: `search:hidden`\n     *    when the search form is hidden or the navbar view is destroyed.\n     * 3. event: `search:submit` to itself\n     *    when the search form is submitted.\n     * 4. channel: `global`, event: `search:change`\n     *    every time when the user types something on the search form.\n     *\n     * Requests:\n     * 1. channel: `uri`, request: `link:profile`\n     * 2. channel: `uri`, request: `profile`\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        keyboardEvents:  {},\n\n        behaviors: {\n            Sidemenu: {\n                behaviorClass: Sidemenu\n            }\n        },\n\n        ui: {\n            navbar : '#sidebar--nav',\n            search : '#header--search--input',\n            title  : '#header--title',\n            icon   : '#header--icon',\n            sync   : '#header--sync--icon'\n        },\n\n        events: {\n            'click #header--add'     : 'navigateAdd',\n            'click #header--sbtn'    : 'showSearch',\n            'click #header--about'   : 'showAbout',\n            'blur @ui.search'        : 'hideSearch',\n            'keyup @ui.search'       : 'searchKeyup',\n            'submit #header--search' : 'searchSubmit',\n            'click #header--sync'    : 'triggerSync'\n        },\n\n        collectionEvents: {\n            'change': 'render'\n        },\n\n        initialize: function() {\n            this.listenTo(this, 'change:title', this.changeTitle);\n            this.listenTo(Radio.channel('global'), 'show:search', this.showSearch);\n\n            this.listenTo(Radio.channel('sync'), 'start', this.onSyncStart);\n            this.listenTo(Radio.channel('sync'), 'stop', this.onSyncStop);\n\n            // Re-render the view when notebooks collection has changed\n            this.listenTo(this.options.notebooks, 'change add remove', this.render);\n        },\n\n        onDestroy: function() {\n            Radio.trigger('global', 'search:hidden');\n        },\n\n        triggerSync: function() {\n            Radio.request('sync', 'start');\n        },\n\n        onSyncStart: function() {\n            console.log('start spin');\n            this.ui.sync.toggleClass('animate-spin', true);\n        },\n\n        onSyncStop: function() {\n            console.log('stop spin');\n            this.ui.sync.toggleClass('animate-spin', false);\n        },\n\n        /**\n         * Trigger form:show event when add button is clicked.\n         */\n        navigateAdd: function() {\n            Radio.trigger('global', 'form:show');\n            return false;\n        },\n\n        /**\n         * Change navbar title\n         */\n        changeTitle: function(options) {\n            var icon = this.templateHelpers().getIcon.apply(options);\n\n            this.ui.title.text($.t(options.title));\n            this.ui.icon.attr('class', icon);\n            this.options.args = options.args;\n        },\n\n        searchSubmit: function() {\n            this.ui.search.blur();\n\n            if (this.ui.search.val().trim().length) {\n                this.trigger('search:submit', this.ui.search.val().trim());\n            }\n\n            Radio.trigger('global', 'search:hidden');\n\n            return false;\n        },\n\n        showSearch: function() {\n            this.ui.navbar.addClass('-search');\n            this.ui.search.focus().select();\n            Radio.trigger('global', 'search:shown');\n\n            return false;\n        },\n\n        hideSearch: function() {\n            this.ui.navbar.removeClass('-search');\n        },\n\n        showAbout: function(e) {\n            e.preventDefault();\n            Radio.request('Help', 'show:about');\n        },\n\n        searchKeyup: function(e) {\n            if (e.which === 27) {\n                Radio.trigger('global', 'search:hidden');\n                return this.ui.search.blur();\n            }\n            Radio.trigger('global', 'search:change', this.ui.search.val());\n        },\n\n        serializeData: function() {\n            var maxNotebooks = parseInt(Radio.request('configs', 'get:config', 'navbarNotebooksMax'), 10);\n            return {\n                args      : this.options.args,\n                configs   : this.collection.getConfigs(),\n                profiles  : this.options.profiles.getValueJSON(),\n                notebooks : _.first(this.options.notebooks.toJSON(), maxNotebooks),\n                uri       : Radio.request('uri', 'link:profile', '/'),\n                profile   : Radio.request('uri', 'profile')\n            };\n        },\n\n        templateHelpers: function() {\n            return {\n                getIcon: function() {\n                    return 'icon-' + (\n                        !this.args.filter ? 'note' : this.args.filter\n                    );\n                },\n\n                isSyncEnabled: function() {\n                    return this.configs.cloudStorage === 'dropbox';\n                },\n\n                profileLink: function(profileName) {\n                    return Radio.request('uri', 'link:profile', '/notes', profileName);\n                }\n            };\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/appNotebooks.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/notebooks/list/app'\n], function(Q, _, Marionette, Radio, App, SidebarApp) {\n    'use strict';\n\n    /**\n     * AppNotebooks module. The module shows a list of notebooks and tags\n     * in sidebar. It also handles adding, updating, and removing of notebooks\n     * and tags.\n     *\n     * Listens to events:\n     * 1. channel: `global`, event: `form:show`\n     *    shows notebooks form\n     *\n     * Replies to requests on channel `appNotebooks`:\n     * 1. request: `notebooks:remove`\n     *    removes specified notebook.\n     * 2. request: `tags:remove`\n     *    removes specified tag.\n     * 3. request: `show:form`\n     *    it always replies to this request. After receiving the request, it\n     *    shows notebook form without starting this module.\n     *\n     * Triggers requests:\n     * 1. channel: `navbar`, request: `start`\n     */\n    var Notebooks = App.module('AppNotebooks', {startWithParent: false}),\n        startModule,\n        controller;\n\n    /**\n     * The router.\n     */\n    Notebooks.Router = Marionette.AppRouter.extend({\n        appRoutes: {\n            // Notebooks\n            '(p/:profile/)notebooks'            : 'showList',\n            '(p/:profile/)notebooks/add'        : 'notebookForm',\n            '(p/:profile/)notebooks/edit/:id'   : 'notebookForm',\n\n            // Tags\n            '(p/:profile/)tags/add'             : 'tagForm',\n            '(p/:profile/)tags/edit/:id'        : 'tagForm',\n        },\n\n        // Starts itself\n        onRoute: function() {\n            if (!Notebooks._isInitialized) {\n                App.startSubApp('AppNotebooks', {profile: arguments[2][0]});\n            }\n        }\n    });\n\n    /**\n     * Starts submodules\n     */\n    startModule = function(module, args) {\n        if (!module) {\n            return;\n        }\n\n        // Stop previous module\n        if (Notebooks.currentApp) {\n            Notebooks.currentApp.stop();\n        }\n\n        Notebooks.currentApp = module;\n        module.start(args);\n\n        // If module has stopped, remove the variable\n        module.on('stop', function() {\n            Notebooks.currentApp = null;\n        });\n    };\n\n    controller = {\n\n        /**\n         * Shows a list of notebooks and tags.\n         * Sidebar module starts when this module starts.\n         * That is why we do not have to do anything here.\n         */\n        showList: function() {\n        },\n\n        // Edit or add notebooks\n        notebookForm: function(profile, id) {\n\n            /*\n             * Return a promise that gets resolved when the new notebook is\n             * successfully created.\n             */\n            var defer = Q.defer();\n\n            requirejs(['apps/notebooks/form/notebook/app'], function(Module) {\n                startModule(Module, {profile: profile, id: id, promise: defer});\n            });\n\n            return defer.promise;\n        },\n\n        // Edit or add tags\n        tagForm: function(profile, id) {\n            requirejs(['apps/notebooks/form/tag/app'], function(Module) {\n                startModule(Module, {profile: profile, id: id});\n            });\n        },\n\n        // Remove an existing notebook\n        _removeNotebook: function(profile, id) {\n            requirejs(['apps/notebooks/remove/controller'], function(Controller) {\n                new Controller('notebooks', profile, id);\n            });\n        },\n\n        // Remove an existing tag\n        _removeTag: function(profile, id) {\n            requirejs(['apps/notebooks/remove/controller'], function(Controller) {\n                new Controller('tags', profile, id);\n            });\n        },\n\n        _navigateForm: function() {\n            Radio.request('uri', 'navigate', '/notebooks/add', {includeProfile: true});\n        }\n    };\n\n    /**\n     * Initializers and finalizers\n     */\n    Notebooks.on('before:start', function(options) {\n        // Start the sidebar module\n        SidebarApp.start(options);\n\n        // Reply to requests\n        Radio.channel('appNotebooks')\n        .reply('notebooks:remove', controller._removeNotebook, controller)\n        .reply('tags:remove', controller._removeTag, controller);\n\n        // Listen to events\n        this.listenTo(Radio.channel('global'), 'form:show', controller._navigateForm);\n    });\n\n    Notebooks.on('before:stop', function() {\n        // Stop the sidebar module\n        SidebarApp.stop();\n\n        // Stop the current module\n        if (Notebooks.currentApp) {\n            Notebooks.currentApp.stop();\n            Notebooks.currentApp = null;\n        }\n\n        // Stop responding to requests and requests\n        Radio.channel('appNotebooks')\n        .stopReplying('notebooks:remove tags:remove');\n\n        // Stop listening to events\n        this.stopListening();\n    });\n\n    Radio.request('init', 'add', 'app', function() {\n        Radio.reply('appNotebooks', 'show:form', controller.notebookForm, controller);\n    });\n\n    App.on('before:start', function() {\n        new Notebooks.Router({\n            controller: controller\n        });\n    });\n\n    return Notebooks;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/notebook/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/notebooks/form/notebook/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    /**\n     * Notebook form module.\n     *\n     * Replies to requests on channel `appNotebooks`\n     * 1. request: `form:stop` - stops itself.\n     */\n    var Form = App.module('AppNotebooks.Form.Notebook', {startWithParent: false});\n\n    Form.on('before:start', function(options) {\n        Form.controller = new Controller(options);\n\n        Radio.reply('appNotebooks', 'form:stop', Form.stop, Form);\n    });\n\n    Form.on('before:stop', function() {\n        Radio.stopReplying('appNotebooks', 'form:stop');\n\n        Form.controller.destroy();\n        Form.controller = null;\n    });\n\n    return Form;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/notebook/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notebooks/form/notebook/formView'\n], function(Q, _, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * Notebook form controller.\n     *\n     * Listens to events:\n     * 1. channel: `notebooks`, event: `update:model`\n     *    triggers `close` event on the view.\n     * 2. this.view, event: `save`\n     *    saves the changes.\n     * 3. this.view, event: `redirect`\n     *    stops the module and redirects.\n     *\n     * requests:\n     * 1. channel: `notebooks`, event: `save`\n     * 2. channel: `uri`, event: `back`\n     * 3. channel: `appNotebooks`, event: `form:stop`\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            options.profile = options.profile || Radio.request('uri', 'profile');\n\n            _.bindAll(this, 'show');\n            this.options = options;\n\n            // Events\n            this.listenTo(Radio.channel('notebooks'), 'update:model', this.onSaveAfter);\n\n            // Fetch notebooks\n            Q.all([\n                Radio.request('notebooks', 'get:all', options),\n                Radio.request('notebooks', 'get:model', options)\n            ])\n            .spread(this.show);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'modal');\n\n            // If we still got an unresolved promise, resolve it with null-value.\n            if (this.options.promise) {\n               this.options.promise.resolve(null);\n            }\n        },\n\n        show: function(collection, model) {\n            // Show only notebooks which are not related to the current model.\n            collection = collection.clone();\n            collection.reset(collection.rejectTree(model.get('id')));\n\n            // Instantiate and show the form view\n            this.view = new View({\n                collection : collection,\n                model      : model\n            });\n\n            Radio.request('global', 'region:show', 'modal', this.view);\n\n            // Listen to events\n            this.listenTo(this.view, 'save'    , this.save);\n            this.listenTo(this.view, 'redirect', this.redirect);\n        },\n\n        save: function() {\n            var self = this,\n                data = {\n                    name     : this.view.ui.name.val(),\n                    parentId : this.view.ui.parentId.val()\n                };\n\n            Radio.request('notebooks', 'save', this.view.model, data)\n            .then(function() {\n                // Resolve the promise.\n                if (self.options.promise) {\n                    self.options.promise.resolve({\n                        title: self.view.model.title,\n                        id: self.view.model.id\n                    });\n                    self.options.promise = null;\n                }\n            })\n            .fail(function(e) {\n                console.error('Error:', e);\n                if (self.options.promise) {\n                    self.options.promise.reject(e);\n                    self.options.promise = null;\n                }\n            });\n        },\n\n        onSaveAfter: function() {\n            this.view.trigger('close');\n        },\n\n        redirect: function() {\n            var moduleName = Radio.request('global', 'app:current').moduleName;\n\n            // Stop itself\n            Radio.request('appNotebooks', 'form:stop');\n\n            // Redirect only if current active module is AppNotebooks\n            if (moduleName === 'AppNotebooks') {\n                Radio.request('uri', 'back', '/notebooks', {\n                    includeProfile : true\n                });\n            }\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/notebook/formView.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'behaviors/modalForm',\n    'models/notebook',\n    'text!apps/notebooks/form/notebook/templates/form.html'\n], function(_, $, Marionette, ModalForm, Notebook, Tmpl) {\n    'use strict';\n\n    /**\n     * Notebook form view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'modal fade',\n\n        ui: {\n            name     : 'input[name=\"name\"]',\n            parentId : 'select[name=\"parentId\"]'\n        },\n\n        behaviors: {\n            ModalForm: {\n                behaviorClass: ModalForm\n            }\n        },\n\n        serializeData: function() {\n            return _.extend(this.model.toJSON(), {\n                notebooks: this.collection.toJSON()\n            });\n        },\n\n        templateHelpers: function() {\n            return {\n                isParent: function(notebookId) {\n                    if (this.parentId === notebookId) {\n                        return ' selected=\"selected\"';\n                    }\n                }\n            };\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/notebook/templates/form.html",
    "content": "<div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h3 class=\"modal-title\">\n                <% if (id) { %>\n                {{ i18n('notebooks.edit') }}\n                <% } else { %>\n                {{ i18n('notebooks.add') }}\n                <% } %>\n            </h3>\n        </div>\n\n        <div class=\"modal-body modal--body\">\n            <div class=\"container-fluid modal--container\">\n                <form action=\"#\" class=\"form-horizontal modal--form\" method=\"post\">\n                    <div class=\"form-group modal--group\">\n                        <input name=\"name\" class=\"form-control modal--input\"\n                            type=\"text\" value=\"{=cleanXSS(name)}\" placeholder=\"{{ i18n('Title') }}\" />\n                    </div>\n\n                    <div class=\"form-group modal--group\">\n                        <select name=\"parentId\" class=\"form-control modal--input\">\n                            <option value=\"0\">{{ i18n('Parent') }}: {{ i18n('Root') }}</option>\n                            <% _.each(notebooks, function (notebook) { %>\n                            <option value=\"{{notebook.id}}\"{{isParent(notebook.id) }}>\n                                {{ i18n('Parent') }}: {=cleanXSS(notebook.name)}\n                            </option>\n                            <% }); %>\n                        </select>\n                    </div>\n                </form>\n            </div>\n        </div>\n\n        <div class=\"modal-footer\">\n            <button class=\"btn btn-default cancelBtn\" href=\"#\">\n                {{ i18n('Cancel') }}\n            </button>\n            <button class=\"btn btn-danger ok\" href=\"#\">\n                {{ i18n('Save') }}\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/tag/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/notebooks/form/tag/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    /**\n     * Tag form module.\n     *\n     * Replies to requests on channel `appNotebooks`\n     * 1. request: `form:stop` - stops itself.\n     */\n    var Form = App.module('AppNotebooks.Form.Tag', {startWithParent: false});\n\n    Form.on('before:start', function(options) {\n        Form.controller = new Controller(options);\n\n        Radio.reply('appNotebooks', 'form:stop', Form.stop, Form);\n    });\n\n    Form.on('before:stop', function() {\n        Radio.stopReplying('appNotebooks', 'form:stop');\n\n        Form.controller.destroy();\n        Form.controller = null;\n    });\n\n    return Form;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/tag/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notebooks/form/tag/formView'\n], function(Q, _, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * Tag form controller.\n     *\n     * Listens to events:\n     * 1. channel: `tags`, event: `update:model`\n     *    triggers `close` event on the view.\n     * 2. this.view, event: `save`\n     *    saves the changes.\n     * 3. this.view, event: `redirect`\n     *    stops the module and redirects.\n     *\n     * requests:\n     * 1. channel: `tags`, event: `save`\n     * 2. channel: `uri`, event: `back`\n     * 3. channel: `appNotebooks`, event: `form:stop`\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            _.bindAll(this, 'show');\n\n            // Events\n            this.listenTo(Radio.channel('tags'), 'update:model', this.onSaveAfter);\n\n            // Fetch the model and render the view\n            Radio.request('tags', 'get:model', options)\n            .then(this.show);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'modal');\n        },\n\n        show: function(model) {\n            // Instantiate and show the form view\n            this.view = new View({\n                model: model\n            });\n\n            Radio.request('global', 'region:show', 'modal', this.view);\n\n            // Listen to events\n            this.listenTo(this.view, 'save'    , this.save);\n            this.listenTo(this.view, 'redirect', this.redirect);\n        },\n\n        save: function() {\n            var data = {\n                name: this.view.ui.name.val().trim()\n            };\n\n            Radio.request('tags', 'save', this.view.model, data)\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        onSaveAfter: function() {\n            this.view.trigger('close');\n        },\n\n        redirect: function() {\n            Radio.request('appNotebooks', 'form:stop');\n\n            Radio.request('uri', 'back', '/notebooks', {\n                includeProfile : true\n            });\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/tag/formView.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'behaviors/modalForm',\n    'text!apps/notebooks/form/tag/templates/form.html'\n], function(_, Marionette, ModalForm, Templ) {\n    'use strict';\n\n    /**\n     * Tag form view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Templ),\n\n        className: 'modal fade',\n\n        ui: {\n            name : 'input[name=\"name\"]'\n        },\n\n        behaviors: {\n            ModalForm: {\n                behaviorClass: ModalForm\n            }\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/form/tag/templates/form.html",
    "content": "<div class=\"modal-dialog\"><div class=\"modal-content\">\n    <div class=\"modal-header\">\n        <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n        <h3 class=\"modal-title\">\n            <% if (id) { %>\n            {{ i18n('tags.edit') }}\n            <% } else { %>\n            {{ i18n('tags.add') }}\n            <% } %>\n        </h3>\n    </div>\n    <div class=\"modal-body modal--body\">\n        <div class=\"container-fluid modal--container\">\n            <form action=\"#\" class=\"form-horizontal modal--form\" method=\"POST\">\n                <div class=\"form-group modal--group\">\n                    <input name=\"name\" class=\"form-control modal--input\"\n                        type=\"text\" value=\"{=cleanXSS(name)}\" placeholder=\"{{ i18n('Title') }}\" />\n                </div>\n            </form>\n        </div>\n    </div>\n    <div class=\"modal-footer\">\n        <button class=\"btn btn-default cancelBtn\" href=\"#\">{{ i18n('Cancel') }}</button>\n        <button class=\"btn btn-danger ok\" href=\"#\">{{ i18n('Save') }}</button>\n    </div>\n</div></div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/notebooks/list/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    /**\n     * Notebooks list sub module.\n     * It shows notebooks and tags list.\n     */\n    var List = App.module('AppNotebooks.List', {startWithParent: false});\n\n    List.on('before:start', function(options) {\n        List.controller = new Controller(options);\n    });\n\n    List.on('before:stop', function() {\n        List.controller.destroy();\n        List.controller = null;\n    });\n\n    return List;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/behaviors/compositeBehavior.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio'\n], function(_, Marionette, Radio) {\n    'use strict';\n\n    /**\n     * Composite behavior class for notebooks and tags views.\n     *\n     * Triggers the following events:\n     * 1. channel: `appNotebooks`, event: `change:region`\n     *    when the user has reached the last or the first model\n     */\n    var CompositeBehavior = Marionette.Behavior.extend({\n\n        defaults: {\n            channel        : 'notebooks',\n            regionToChange : 'tags',\n            activeModel    : null\n        },\n\n        initialize: function() {\n            this.collection = this.view.options.collection;\n            this.uiBody = $('.-scroll');\n\n            this.channel = Radio.channel(this.options.channel);\n\n            // Listen to events on a channel [notebooks|tags]\n            this.listenTo(this.channel, 'model:navigate', this.modelFocus);\n\n            // View events\n            this.listenTo(this.view, 'navigate:next', this.navigateNext);\n            this.listenTo(this.view, 'navigate:previous', this.navigatePrevious);\n            this.listenTo(this.view, 'childview:scroll:top', this.changeScrollTop);\n\n            // Collection events\n            this.listenTo(this.collection, 'page:end', this.onPageEnd);\n            this.listenTo(this.collection, 'page:start', this.onPageStart);\n        },\n\n        onBeforeDestroy: function() {\n            this.view.collection.trigger('reset:all');\n            this.stopListening();\n\n            this.collection = null;\n            this.channel = null;\n            this.uiBody = null;\n        },\n\n        /**\n         * Change scroll position.\n         */\n        changeScrollTop: function(view, scrollTop) {\n            this.uiBody.scrollTop(\n                scrollTop -\n                this.uiBody.offset().top +\n                this.uiBody.scrollTop() - 100\n            );\n        },\n\n        /**\n         * Trigger `focus` event on the received model\n         */\n        modelFocus: _.debounce(function(model) {\n            this.view.options.activeModel = model.id;\n            model.trigger('focus');\n        }, 10),\n\n        /**\n         * If a user has reached the first model in a collection\n         * and it is notebooks composite view, trigger change:region event\n         */\n        onPageEnd: function() {\n            if (this.options.regionToChange === 'tags') {\n                Radio.trigger('appNotebooks', 'change:region', this.options.regionToChange, 'Next');\n            }\n        },\n\n        /**\n         * If a user has reached the first model in a collection\n         * and it is tags composite view, trigger change:region event\n         */\n        onPageStart: function() {\n            if (this.options.regionToChange === 'notebooks') {\n                Radio.trigger('appNotebooks', 'change:region', this.options.regionToChange, 'Previous');\n            }\n        },\n\n        /**\n         * Navigate to the next model\n         */\n        navigateNext: function() {\n            this.view.collection.getNextItem(this.view.options.activeModel);\n        },\n\n        /**\n         * Navigate to the previous model\n         */\n        navigatePrevious: function() {\n            this.view.collection.getPreviousItem(this.view.options.activeModel);\n        }\n\n    });\n\n    return CompositeBehavior;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/behaviors/itemBehavior.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'marionette',\n    'backbone.radio'\n], function($, Marionette, Radio) {\n    'use strict';\n\n    /**\n     * Item behavior class for notebooks and tags views.\n     *\n     * Triggers requests:\n     * 1. channel: `appNotebooks`, request: `[notebooks|tags]:remove`\n     *    expects that the provided model will be removed.\n     */\n    var ItemBehavior = Marionette.Behavior.extend({\n        modelEvents: {\n            'focus': 'makeActive'\n        },\n\n        events: {\n            'click .remove-link': 'triggerRemove'\n        },\n\n        triggerRemove: function() {\n            var event = this.view.model.storeName + ':remove';\n            Radio.request('appNotebooks', event, null, this.view.model.id);\n            return false;\n        },\n\n        makeActive: function() {\n            // Search by data-id attribute because child objects have the same class name\n            var $item = this.view.$('.list-group-item[data-id=' + this.view.model.get('id') + ']');\n\n            $('.list-group-item.active').removeClass('active');\n            $item.addClass('active');\n\n            this.view.trigger('scroll:top', $item.offset().top);\n        }\n    });\n\n    return ItemBehavior;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'apps/notebooks/list/views/layout',\n    'apps/notebooks/list/views/notebooksComposite',\n    'apps/notebooks/list/views/tagsComposite'\n], function(_, Q, Marionette, Radio, View, NotebooksView, TagsView) {\n    'use strict';\n\n    /**\n     * List controller.\n     *\n     * Triggers:\n     * 1. channel: `global`, event: `filter:change`\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            _.bindAll(this, 'show');\n\n            this.options = options;\n\n            // Show the navbar and change document title\n            Radio.request('navbar', 'start', {\n                title  : 'Notebooks & tags',\n                filter : 'notebook'\n            });\n\n            // Fetch\n            Q.all([\n                Radio.request('notebooks', 'get:all', options),\n                Radio.request('tags', 'get:all', options)\n            ]).spread(this.show)\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        onDestroy: function() {\n            Radio.request('global', 'region:empty', 'sidebar');\n        },\n\n        show: function(notebooks, tags) {\n            this.view = new View({\n                notebooks : notebooks,\n                tags      : tags,\n                configs   : Radio.request('configs', 'get:object')\n            });\n\n            Radio.request('global', 'region:show', 'sidebar', this.view);\n\n            // Show notebooks\n            this.view.notebooks.show(new NotebooksView({\n                collection: notebooks\n            }));\n\n            // Show tags\n            this.view.tags.show(new TagsView({\n                collection: tags\n            }));\n        }\n\n    });\n\n    return Controller;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/templates/layout.html",
    "content": "<!-- Notebooks list -->\n<div id=\"notebooks\"></div>\n<div id=\"tags\"></div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/templates/notebooksItem.html",
    "content": "<a class=\"list--item -notebook list-group-item\" href=\"#{{ uri }}notes/f/notebook/q/{{id}}\" data-id=\"{{id}}\">\n    <i class=\"icon-notebook\"></i>\n    {=cleanXSS(name)}\n</a>\n\n<div class=\"list--buttons btn-group dropdown\">\n    <button class=\"drop-edit btn btn-default btn-xs dropdown-toggle\" data-toggle=\"dropdown\">\n        <span class=\"caret\"></span>\n    </button>\n    <ul class=\"dropdown-menu pull-right\" role=\"menu\" aria-labelledby=\"dropdownMenu{{id}}\">\n        <li><a class=\"edit-link\" href=\"#{{ uri }}notebooks/edit/{{id}}\">{{ i18n('Edit') }}</a></li>\n        <li><a class=\"remove-link\" href=\"#\">{{ i18n('Remove') }}</a></li>\n    </ul>\n</div>\n\n<div class=\"list--nested\" data-id=\"{{id}}\" id=\"{{id}}\"></div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/templates/notebooksList.html",
    "content": "<div class=\"list-notebooks\"></div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/templates/tagsItem.html",
    "content": "<a class=\"list--item -tag list-group-item\" href=\"#{{ uri }}notes/f/tag/q/{{name}}\" data-id=\"{{id}}\">\n    <i class=\"icon-tag\"></i>\n    {=cleanXSS(name)}\n    <span class=\"pull-right badge\">{{count}}</span>\n</a>\n\n<div class=\"list--buttons btn-group dropdown\">\n    <button class=\"drop-edit btn btn-default btn-xs dropdown-toggle\" data-toggle=\"dropdown\">\n        <span class=\"caret\"></span>\n    </button>\n    <ul class=\"dropdown-menu pull-right\" role=\"menu\" aria-labelledby=\"dropdownMenu{{id}}\">\n        <li><a class=\"edit-link\" href=\"#{{ uri }}tags/edit/{{id}}\">{{ i18n('Edit') }}</a></li>\n        <li><a class=\"remove-link\" href=\"#\">{{ i18n('Remove') }}</a></li>\n    </ul>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/templates/tagsList.html",
    "content": "<header class=\"list--navbar navbar navbar-default navbar-static-top\">\n    <span class=\"navbar-brand list--brand\">{{ i18n('Tags') }}</span>\n    <ul class=\"nav navbar-nav navbar-right header--right list--nav\">\n        <li>\n            <span>\n            <a class=\"btn btn-danger\" href=\"#{{ uri('') }}tags/add\" title=\"{{ i18n('New tag') }}\">\n                <i class=\"icon-doc-new\"></i>\n            </a>\n            </span>\n        </li>\n    </ul>\n</header>\n\n<div class=\"list list--tags\"></div>\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/views/layout.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'behaviors/sidebar',\n    'text!apps/notebooks/list/templates/layout.html',\n    'mousetrap'\n], function(_, Marionette, Radio, Behavior, Tmpl, Mousetrap) {\n    'use strict';\n\n    /**\n     * Notebooks layout view.\n     * It shows lists of tags and notebooks.\n     *\n     * Listens to events:\n     * 1. channel: `appNotebooks`, event: `change:region`\n     *    switches to another region.\n     *\n     * Triggers events:\n     * 1. `navigate:next` to currently active region\n     * 2. `navigate:previous` to currently active region\n     *\n     * requests:\n     * 1. channel: `uri`, request: `navigate`\n     */\n    var View = Marionette.LayoutView.extend({\n        template: _.template(Tmpl),\n\n        regions: {\n            notebooks :  '#notebooks',\n            tags      :  '#tags'\n        },\n\n        behaviors: {\n            SidebarBehavior: {\n                behaviorClass: Behavior\n            }\n        },\n\n        // Default active region is `notebooks`\n        activeRegion: 'notebooks',\n\n        initialize: function() {\n            _.bindAll(this, 'triggerNext', 'triggerPrevious', 'openActive', 'openEdit', 'triggerRemove', 'focusRegion');\n\n            // Make tags active region if there aren't any notebooks\n            if (!this.options.notebooks.length) {\n                this.activeRegion = 'tags';\n            }\n\n            this.listenTo(Radio.channel('notebooks'), 'model:navigate', this.focusRegion);\n            this.listenTo(Radio.channel('tags'), 'model:navigate', this.focusRegion);\n\n            // Register keyboard events\n            Mousetrap.bind(this.options.configs.navigateBottom, this.triggerNext);\n            Mousetrap.bind(this.options.configs.navigateTop, this.triggerPrevious);\n            Mousetrap.bind(this.options.configs.actionsOpen, this.openActive);\n            Mousetrap.bind(this.options.configs.actionsEdit, this.openEdit);\n            Mousetrap.bind(this.options.configs.actionsRemove, this.triggerRemove);\n\n            // Listen to events\n            this.listenTo(Radio.channel('appNotebooks'), 'change:region', this.changeRegion);\n        },\n\n        onBeforeDestroy: function() {\n            Mousetrap.unbind([\n                this.options.configs.navigateBottom,\n                this.options.configs.navigateTop,\n                this.options.configs.actionsOpen,\n                this.options.configs.actionsEdit,\n                this.options.configs.actionsRemove\n            ]);\n            this.stopListening();\n        },\n\n        triggerNext: function() {\n            this[this.activeRegion].currentView.trigger('navigate:next');\n        },\n\n        triggerPrevious: function() {\n            this[this.activeRegion].currentView.trigger('navigate:previous');\n        },\n\n        triggerRemove: function() {\n            var $a = this.$('.list-group-item.active').parent().find('.remove-link:first');\n            $a.trigger('click');\n            return false;\n        },\n\n        openActive: function() {\n            var $a = this.$('.list-group-item.active');\n            Radio.request('uri', 'navigate', $a.attr('href'));\n        },\n\n        openEdit: function() {\n            var $a = this.$('.list-group-item.active').parent().find('.edit-link:first');\n            Radio.request('uri', 'navigate', $a.attr('href'));\n        },\n\n        /**\n         * Make sure a model's region is active.\n         * For example, if a user navigated to a tag, tags region should be\n         * active.\n         */\n        focusRegion: _.debounce(function(model) {\n            this.activeRegion = model.storeName;\n        }, 100),\n\n        /**\n         * Switch from one region to another. For example, from `tags` to\n         * `notebooks`.\n         */\n        changeRegion: function(regionName, direction) {\n            // Don't change active region\n            if (!this.options[regionName].length) {\n                return;\n            }\n\n            this.activeRegion = regionName;\n\n            /*\n             * Reset active model variable and\n             * call either triggerNext or triggerPrevious method\n             */\n            this[this.activeRegion].currentView.options.activeModel = null;\n            this['trigger' + direction]();\n        }\n    });\n\n    return View;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/views/notebooksComposite.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'apps/notebooks/list/behaviors/compositeBehavior',\n    'apps/notebooks/list/views/notebooksItem',\n    'text!apps/notebooks/list/templates/notebooksList.html'\n], function(_, Marionette, Behavior, ItemView, Tmpl) {\n    'use strict';\n\n    /**\n     * Notebooks composite view.\n     * Everything happens in its behavior class.\n     */\n    var View = Marionette.CompositeView.extend({\n        template: _.template(Tmpl),\n\n        childView          : ItemView,\n        childViewContainer : '.list-notebooks',\n\n        behaviors: {\n            CompositeBehavior: {\n                behaviorClass: Behavior\n            }\n        },\n\n        /**\n         * Build a tree structure\n         */\n        showCollection: function() {\n            Marionette.CompositeView.prototype.showCollection.apply(this, arguments);\n\n            var fragment = document.createDocumentFragment();\n\n            this.children.each(function(view) {\n                this.attachFragment(this, view, fragment);\n            }, this);\n\n            this.$(this.childViewContainer).append(fragment);\n        },\n\n        /**\n         * Don't use the default method of attaching items\n         */\n        attachHtml: function() {},\n\n        /**\n         * For performance's sake attach items into fragment.\n         */\n        attachFragment: function(colView, itemView, fragment) {\n            var parentId = String(itemView.model.get('parentId'));\n\n            // It isn't a child notebook\n            if (parentId === '0' || parentId === '' ||\n                !fragment.getElementById(parentId)) {\n                fragment.appendChild(itemView.el);\n            }\n            else {\n                fragment.getElementById(parentId).appendChild(itemView.el);\n            }\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/views/notebooksItem.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notebooks/list/behaviors/itemBehavior',\n    'text!apps/notebooks/list/templates/notebooksItem.html'\n], function(_, Marionette, Radio, ItemBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Notebooks item view.\n     * Everything happens in its behaviour.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'list--group list-group',\n\n        behaviors: {\n            ItemBehavior: {\n                behaviorClass: ItemBehavior\n            }\n        },\n\n        serializeData: function() {\n            return _.extend(this.model.toJSON(), {\n                uri  : Radio.request('uri', 'link:profile', '')\n            });\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/views/tagsComposite.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notebooks/list/behaviors/compositeBehavior',\n    'apps/notebooks/list/views/tagsItem',\n    'text!apps/notebooks/list/templates/tagsList.html'\n], function(_, Marionette, Radio, Behavior, ItemView, Tmpl) {\n    'use strict';\n\n    /**\n     * Tags composite view.\n     * Everything happens in its behavior class.\n     */\n    var View = Marionette.CompositeView.extend({\n        template: _.template(Tmpl),\n\n        childView: ItemView,\n        childViewContainer: '.list--tags',\n\n        behaviors: {\n            CompositeBehavior: {\n                behaviorClass  : Behavior,\n                channel        : 'tags',\n                regionToChange : 'notebooks'\n            }\n        },\n\n        collectionEvents: {\n            'page:next': 'onPageNext'\n        },\n\n        onPageNext: function() {\n            this.collection.getPage(this.collection.state.currentPage + 1);\n        },\n\n        templateHelpers: function() {\n            return {\n                uri : function() {\n                    return Radio.request('uri', 'link:profile', '');\n                }\n            };\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/list/views/tagsItem.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notebooks/list/behaviors/itemBehavior',\n    'text!apps/notebooks/list/templates/tagsItem.html'\n], function(_, Marionette, Radio, ItemBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Tags item view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'list--group list-group',\n\n        behaviors: {\n            ItemBehavior: {\n                behaviorClass: ItemBehavior\n            }\n        },\n\n        serializeData: function() {\n            return _.extend(this.model.toJSON(), {\n                uri : Radio.request('uri', 'link:profile', '')\n            });\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/remove/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/notebooks/remove/notebooks.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Remove controller. Handles removing of notebooks and tags.\n     *\n     * Listens to events on channel `[notebooks|tags]`:\n     * 1. event: `destroy:model`\n     *\n     * requests:\n     * 1. channel: [notebooks|tags], request: `remove`\n     *    expects that the model will be destroyed.\n     * 2. channel: `Confirm`, request: `start`\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(modelType, profile, id) {\n            _.bindAll(this, 'showConfirm');\n\n            this.channel = Radio.channel(modelType);\n            profile = profile || Radio.request('uri', 'profile');\n\n            // Events\n            this.listenTo(this.channel, 'destroy:model', this.destroy);\n            this.listenTo(Radio.channel('Confirm'), 'cancel', this.destroy);\n            this.listenTo(Radio.channel('Confirm'), 'confirm', this.remove);\n            this.listenTo(Radio.channel('Confirm'), 'confirmNotes', this.removeWithNotes);\n\n            // Fetch a tag or a notebook by ID\n            this.channel.request('get:model', {profile: profile, id: id})\n            .then(this.showConfirm);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            this.channel = null;\n        },\n\n        showConfirm: function(model) {\n            this.model = model;\n\n            Radio.request('Confirm', 'start', {\n                content : $.t(model.storeName + '.confirm remove', model.toJSON()),\n                template: model.storeName === 'notebooks' ? Tmpl : undefined\n            });\n        },\n\n        remove: function() {\n            this.channel.request('remove', this.model, {profile: this.model.profileId}, false);\n        },\n\n        removeWithNotes: function() {\n            this.channel.request('remove', this.model, {profile: this.model.profileId}, true);\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notebooks/remove/notebooks.html",
    "content": "<div><div class=\"modal-dialog modal-mini\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h3 class=\"modal-title\">{{ getTitle() }}</h3>\n        </div>\n        <div class=\"modal-body\">\n            <p>{{content}}</p>\n        </div>\n        <div class=\"modal-footer\">\n            <button type=\"button\" class=\"btn btn-default\" data-event=\"cancel\">\n                {{ i18n('Cancel') }}\n            </button>\n            <button type=\"button\" class=\"btn btn-danger\" data-event=\"confirmNotes\">\n                {{ i18n('notebooks.remove with notes') }}\n            </button>\n            <button type=\"button\" class=\"btn btn-success\" data-event=\"confirm\">\n                {{ i18n('notebooks.remove') }}\n            </button>\n        </div>\n    </div>\n</div></div>\n"
  },
  {
    "path": "app/scripts/apps/notes/appNote.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'app',\n    'backbone.radio',\n    'enquire',\n    'apps/notes/list/listApp'\n], function(_, $, Marionette, App, Radio, enquire, SidebarApp) {\n    'use strict';\n\n    /**\n     * AppNote module.\n     *\n     * Listens to\n     * --------\n     * Events on channel `appNote`:\n     * 1. `form:show`    - shows a form where a user can add/edit new notes\n     * 2. `notes:toggle` - make the sidebar region active\n     *\n     * Replies on channel `appNote`:\n     * 1. `route:args`   - returns current route arguments\n     */\n    var AppNote = App.module('AppNote', { startWithParent: false }),\n        executeAction,\n        API;\n\n    /**\n     * The router\n     */\n    AppNote.Router = Marionette.AppRouter.extend({\n        appRoutes: {\n            ''           : 'showIndex',\n            'p/:profile' : 'filterNotes',\n\n            // Edit/Add notes\n            '(p/:profile/)notes/add'      : 'noteForm',\n            '(p/:profile/)notes/edit/:id' : 'noteForm',\n\n            // Filter notes\n            '(p/:profile/)notes(/f/:filter)(/q/:query)(/p:page)'            : 'filterNotes',\n            '(p/:profile/)notes(/f/:filter)(/q/:query)(/p:page)(/show/:id)' : 'showNote'\n        },\n\n        // Start this module\n        onRoute: function() {\n            if (!AppNote._isInitialized) {\n                var args = arguments[0] === 'noteForm' ? {} : arguments[2];\n                App.startSubApp('AppNote', API._getArgs.apply(this, args));\n            }\n        }\n    });\n\n    /**\n     * Starts a submodule\n     */\n    executeAction = function(module, args) {\n        if (!module) {\n            return;\n        }\n\n        // Stop previous module\n        if (AppNote.currentApp) {\n            AppNote.currentApp.stop();\n        }\n\n        AppNote.currentApp = module;\n        module.start(args);\n\n        // If module has stopped, remove the variable\n        module.on('stop', function() {\n            AppNote.currentApp = null;\n        });\n    };\n\n    /**\n     * Router's controller\n     */\n    API = {\n\n        // Index page\n        showIndex: function() {\n            this.filterNotes();\n        },\n\n        // Filter collection\n        filterNotes: function() {\n            var args = this._getArgs.apply(this, arguments);\n\n            // Wait until the SidebarApp has started\n            if (!SidebarApp._isInitialized) {\n                return SidebarApp.on('start', function() {\n                    API.filterNotes(args);\n                });\n            }\n\n            Radio.request('appNote', 'filter', args);\n        },\n\n        // Show a note\n        showNote: function() {\n            var args = this._getArgs.apply(this, arguments);\n\n            requirejs(['apps/notes/show/app'], function(Module) {\n                executeAction(Module, args);\n            });\n        },\n\n        // Shows a form for editing or adding notes\n        noteForm: function(profile, id) {\n            var args = _.extend({}, this.notesArg || {}, {\n                id      : id,\n                profile : profile\n            });\n\n            // Start 'Form' module\n            requirejs(['apps/notes/form/app'], function(Module) {\n                args.method = id ? 'edit' : 'add';\n                executeAction(Module, args);\n            });\n        },\n\n        // Remove an existing note\n        removeNote: function(id) {\n            requirejs(['apps/notes/remove/controller'], function(Controller) {\n                API.notesArg.id = null;\n                new Controller(_.extend({}, API.notesArg || {}, {id: id}));\n            });\n        },\n\n        // Make sidebar active\n        _toggleSidebar: function(args) {\n            this.$content = this.$content || $(App.content.el);\n            this.$content.removeClass('active-row');\n            this.filterNotes.apply(this, args);\n        },\n\n        // Builds an object from router arguments\n        _getArgs: function(profile, filter, query, page, id) {\n            if (arguments.length === 1 && typeof arguments[0] === 'object') {\n                return arguments[0];\n            }\n\n            this.notesArg = {\n                id      : id,\n                page    : Number(page || 0),\n                query   : query,\n                filter  : filter,\n                profile : profile || Radio.request('uri', 'profile'),\n            };\n\n            return this.notesArg;\n        }\n    };\n\n    /**\n     * Module's initializer/finalizer\n     */\n    AppNote.on('before:start', function(options) {\n        // Show the sidebar\n        SidebarApp.start(options);\n\n        // Listen to events\n        this.listenTo(Radio.channel('appNote'), 'notes:toggle', API._toggleSidebar);\n        this.listenTo(Radio.channel('global'), 'form:show', function() {\n            Radio.request('uri', 'navigate', '/notes/add', {\n                trigger       : true,\n                includeProfile: true\n            });\n        });\n\n        // Respond to requests and requests\n        Radio.channel('appNote')\n        .reply('remove:note', API.removeNote, API)\n        .reply('route:args', function() {return API.notesArg;}, API);\n    });\n\n    AppNote.on('before:stop', function() {\n        // Stop the sidebar app\n        SidebarApp.stop();\n\n        // Stop the current module\n        if (AppNote.currentApp) {\n            AppNote.currentApp.stop();\n            AppNote.currentApp = null;\n        }\n\n        // Stop listenning to events\n        this.stopListening();\n\n        // Stop responding to requests and requests\n        Radio.channel('appNote')\n        .stopReplying('remove:note')\n        .stopReplying('route:args');\n    });\n\n    /**\n     * Register the router\n     */\n    App.on('before:start', function() {\n        new AppNote.Router({\n            controller: API\n        });\n    });\n\n    return AppNote;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'app',\n    'backbone.radio',\n    'marionette',\n    'apps/notes/form/controller'\n], function(_, $, App, Radio, Marionette, Controller) {\n    'use strict';\n\n    /**\n     * Form app. Instantiates form controller.\n     *\n     * Listens to the following events:\n     * 1. channel: notesForm, event: stop\n     *    stops itself\n     */\n    var Form = App.module('AppNote.Form', {\n        startWithParent: false\n    });\n\n    Form.on('before:start', function(options) {\n        Form.controller = new Controller(options);\n        Radio.on('notesForm', 'stop', Form.stop, Form);\n    });\n\n    Form.on('before:stop', function() {\n        Radio.off('notesForm', 'stop');\n        Form.controller.destroy();\n        Form.controller = null;\n    });\n\n    return Form;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/behaviors/desktop.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'marionette'\n], function(Marionette) {\n    'use strict';\n\n    var Desktop = Marionette.Behavior.extend({\n        initialize: function() {\n            console.warn('Hullo', 'desktop');\n        },\n    });\n\n    return Desktop;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/behaviors/mobile.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'marionette'\n], function(Marionette) {\n    'use strict';\n\n    var Desktop = Marionette.Behavior.extend({\n        initialize: function() {\n            console.warn('Hullo', 'mobile');\n        },\n    });\n\n    return Desktop;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'underscore',\n    'q',\n    'jquery',\n    'backbone.radio',\n    'marionette',\n    'i18next',\n    'apps/notes/form/views/formView',\n    'apps/notes/form/views/notebooks'\n], function (_, Q, $, Radio, Marionette, i18n, View, NotebooksView) {\n    'use strict';\n\n    /**\n     * Note form controller.\n     *\n     * Triggers the following events:\n     * 1. channel: notesForm, event: stop\n     *\n     * Listens to the following events:\n     * 1. channel: notes, event: save:after\n     *\n     * requests:\n     * 1. channel: notes, request: save\n     *    to save the changes.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            this.options = options;\n\n            // Saves data before you change anything, in case you cancel editing\n            this.dataBeforeChange = null;\n            // Data should be deleted if user wants to cancel editing\n            this.deleteData = false;\n\n            _.bindAll(this, 'show', 'redirect');\n\n            // Fetch everything\n            Q.all([\n                Radio.request('notes', 'get:model:full', options),\n                Radio.request('notebooks', 'get:all', _.pick(options, 'profile'))\n            ])\n            .spread(this.show)\n            .catch(function() {\n                console.error('Editor error', arguments);\n            });\n\n            // Events\n            this.listenTo(Radio.channel('notes'), 'update:model', this.redirect);\n            this.listenTo(Radio.channel('Confirm'), 'confirm', this.redirect);\n            this.listenTo(Radio.channel('Confirm'), 'cancel', this.onConfirmCancel);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            this.view.trigger('destroy');\n        },\n\n        show: function(note, notebooks) {\n            var notebooksView;\n            note = note[0];\n\n            // Set document title\n            Radio.request('global', 'set:title', note.get('title'));\n\n            // Use behaviours that are appropriate for a device.\n            if (Radio.request('global', 'platform') === 'mobile') {\n                delete View.prototype.behaviors.Desktop;\n            }\n            else {\n                delete View.prototype.behaviors.Mobile;\n            }\n\n            this.view = new View({\n                model     : note,\n                profile   : note.profileId\n            });\n\n            // Show the view and trigger an event\n            Radio.request('global', 'region:show', 'content', this.view);\n            this.view.trigger('rendered');\n\n            /*\n             * Resolve the notebook ID.\n             * If the current note doesn't have a notebook attached,\n             * try to use one from the filter if it specifies a notebook.\n             */\n            var activeId = note.get('notebookId');\n            if (activeId === '0' && this.options.filter === 'notebook') {\n                activeId = this.options.query;\n            }\n\n            // Show notebooks selector\n            notebooksView = new NotebooksView({\n                collection : notebooks,\n                activeId   : activeId\n            });\n            this.view.notebooks.show(notebooksView);\n\n            // Listen to view events\n            this.listenTo(this.view, 'save', this.save);\n            this.listenTo(this.view, 'cancel', this.showConfirm);\n\n            // Get data before any change is made\n            // so that it can be reset when you cancel editing\n            var self = this;\n            this.getContent()\n            .then(function(data) {\n                self.dataBeforeChange = data;\n            })\n            .fail(function(e) {\n                console.error('Error getting data on start', e);\n            });\n        },\n\n        save: function() {\n            var self = this;\n\n            return this.getContent()\n            .then(function(data) {\n                if (data.title === '') {\n                    var title = i18n.t('Untitled');\n                    data.title = title;\n                    Radio.request('global', 'set:title', title);\n                }\n\n                return Radio.request('notes', 'save', self.view.model, data, self.view.options.saveTags);\n            })\n            .fail(function(e) {\n                console.error('Error', e);\n            });\n        },\n\n        getContent: function() {\n            var self = this;\n\n            return Radio.request('editor', 'get:data')\n            .then(function(data) {\n                return _.extend(data, {\n                    title      : self.view.ui.title.val().trim(),\n                    notebookId : self.view.notebooks.currentView.ui.notebookId.val().trim(),\n                });\n            });\n        },\n\n        /**\n         * Warn a user that they have made some changes.\n         */\n        showConfirm: function() {\n            var self = this;\n\n            return this.getContent()\n            .then(function(data) {\n                // Redirect if data wasn't changed\n                if (_.isEqual(self.dataBeforeChange, data)) {\n                    return self.redirect();\n                }\n\n                // User perhaps wants to cancel editing,\n                // if not, deleteData will be set false again in onConfirmCancel\n                self.deleteData = true;\n                Radio.request('Confirm', 'start', $.t('You have unsaved changes'));\n            })\n            .fail(function(e) {\n                console.error('form ShowConfirm', e);\n            });\n        },\n\n        // Called when the cancel dialog was accepted\n        redirect: function() {\n            if (!this.view.getOption('redirect')) {\n                return;\n            }\n\n            // Stop the module and navigate back\n            if(this.deleteData){\n                this.deleteData = false;\n                if (this.options.method === 'add') {\n                    // Delete the note if editing of a new note was canceled\n                    var self = this;\n                    requirejs(['apps/notes/remove/controller'], function(Controller) {\n                        new Controller(_.extend({},\n                                {id: self.view.model.id, deleteDirect: true}));\n                    });\n                }\n                else if (this.options.method === 'edit') {\n                    // Save the note without any change\n                    // if editing of an existing note was canceled\n                    Radio.request('notes', 'save', this.view.model, this.dataBeforeChange);\n                }\n            }\n\n            Radio.trigger('notesForm', 'stop');\n            Radio.request('uri', 'back');\n        },\n\n        // Called when the cancel dialog was canceled\n        onConfirmCancel: function() {\n            // Rebind keybindings again because TW bootstrap modal overrites ESC.\n            this.view.trigger('bind:keys');\n            this.view.options.isClosed = false;\n\n            if (this.view.options.focus !== 'editor') {\n                return this.view.ui[this.view.options.focus].focus();\n            }\n            Radio.trigger('editor', 'focus');\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/templates/form.html",
    "content": "<nav class=\"layout--navbar navbar navbar-default navbar-static-top header -form\">\n<div class=\"container-fluid\">\n    <div class=\"navbar-left header--left navbar-form\">\n        <div id=\"editor--notebooks\"></div>\n    </div>\n\n    <ul class=\"nav navbar-nav navbar-right header--right\">\n        <li class=\"dropdown\">\n            <button type=\"button\" title=\"Mode\" class=\"btn header--btn btn-default\" class=\"dropdown-toggle\" data-toggle=\"dropdown\" role=\"button\" aria-haspopup=\"true\" aria-expanded=\"false\">\n                <i class=\"icon-fullscreen\"></i> <b class=\"caret hidden-xs\"></b>\n            </button>\n            <ul class=\"editor--mode dropdown-menu dropdown-menu-left\" role=\"menu\">\n                <li>\n                    <a href=\"#\" data-mode=\"fullscreen\">\n                        <i class=\"icon-fullscreen\"></i> {{ i18n('Full screen') }}\n                    </a>\n                </li>\n                <li>\n                    <a href=\"#\" data-mode=\"preview\">\n                        <i class=\"icon-eye\"></i> {{ i18n('Preview') }}\n                    </a>\n                </li>\n                <li>\n                    <a href=\"#\" data-mode=\"normal\">\n                        <i class=\"icon-squares\"></i> {{ i18n('Normal') }}\n                    </a>\n                </li>\n            </ul>\n        </li>\n        <li>\n            <button class=\"editor--cancel btn header--btn btn-default \">\n                <i class=\"icon-block\"></i>\n                <span class=\"hidden-xs\">{{ i18n('Cancel') }}<span>\n            </button>\n        </li>\n        <li>\n            <button class=\"editor--save btn header--btn btn-success \">\n                <i class=\"icon-save\"></i>\n                <span class=\"hidden-xs\">{{ i18n('Save') }}</span>\n            </button>\n        </li>\n    </ul>\n</div>\n</nav>\n\n<div class=\"layout--body -scroll -form\">\n    <div class=\"container-fluid editor--container\">\n        <form class=\"editor--form form-horizontal col-xs-12\" action=\"#\" method=\"post\">\n            <div class=\"form-group\">\n                <input id=\"editor--input--title\" class=\"form-control -borderless\" name=\"title\"\n                    type=\"text\" value=\"{=cleanXSS(title, true)}\" placeholder=\"{{ i18n('Title') }}\" />\n            </div>\n        </form>\n    </div>\n    <div id=\"editor\" class=\"layout--body editor--container\"></div>\n\t<footer id=\"editor--footer\"></footer>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/notes/form/templates/notebooks.html",
    "content": "<div class=\"editor--notebooks\">\n    <select class=\"icons form-control editor--notebooks--list\" name=\"notebookId\">\n        <option class=\"addNotebook\" value=\"\">\n            {{i18n('notebooks.add')}}\n        </option>\n\n        <option value=\"0\">\n            {{i18n('Select notebook')}}\n        </option>\n    </select>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/notes/form/views/formView.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'mousetrap',\n    'text!apps/notes/form/templates/form.html',\n    'behaviors/content',\n    'apps/notes/form/behaviors/desktop',\n    'apps/notes/form/behaviors/mobile',\n    'mousetrap.global'\n], function($, _, Marionette, Radio, Mousetrap, Tmpl, Behavior, Desktop, Mobile) {\n    'use strict';\n\n    /**\n     * Note form view.\n     *\n     * Triggers the following\n     * Events:\n     * 1. channel: notesForm, event: view:ready\n     *    when the view is ready.\n     * 2. channel: notesForm, event: view:destroy\n     *    before the view is destroyed.\n     * 3. channel: notesForm, event: set:mode\n     *    when \"Edit mode\" has changed.\n     *\n     * Responds to the following\n     * Requests:\n     * 1. channel: notesForm, request: model\n     *    returns current model.\n     * 2. channel: notesForm, request: show:editor\n     *    shows the provided view in the `editor` region.\n     *\n     * Listens to the following events:\n     * 1. channel: notesForm, event: save:auto\n     *    saves then the note automaticaly\n     */\n    var View = Marionette.LayoutView.extend({\n        template: _.template(Tmpl),\n\n        className: 'layout--body',\n\n        regions: {\n            editor    : '#editor',\n            notebooks : '#editor--notebooks'\n        },\n\n        behaviors: {\n            Content: {\n                behaviorClass: Behavior\n            },\n            Desktop: {\n                behaviorClass: Desktop\n            },\n            Mobile: {\n                behaviorClass: Mobile\n            }\n        },\n\n        ui: {\n            // Form\n            form       : '.editor--form',\n            saveBtn    :  '.editor--save',\n            title      : '#editor--input--title'\n        },\n\n        events: {\n            'click .editor--mode a' : 'switchMode',\n\n            // Handle saving\n            'submit @ui.form'      : 'save',\n            'click @ui.saveBtn'    : 'save',\n            'click .editor--cancel'  : 'cancel'\n        },\n\n        initialize: function() {\n            _.bindAll(this, 'autoSave', 'save', 'cancel');\n\n            this.configs = Radio.request('configs', 'get:object');\n            this.$body = $('body');\n\n            // Events and replies\n            Radio.channel('notesForm')\n            .reply('model', this.model, this)\n            .reply('show:editor', this.showEditor, this)\n            .on('save:auto', this.autoSave, this);\n\n            // Register keybindings\n            this.bindKeys();\n\n            // The view is ready\n            this.listenTo(this, 'rendered', this.onRendered);\n            this.listenTo(this, 'bind:keys', this.bindKeys);\n        },\n\n        bindKeys: function() {\n            Mousetrap.bindGlobal(['ctrl+s', 'command+s'], this.save);\n            Mousetrap.bindGlobal(['esc'], this.cancel);\n        },\n\n        onRendered: function() {\n            Radio.trigger('notesForm', 'view:ready');\n\n            // Focus on the 'title'\n            this.ui.title.trigger('focus');\n\n            // Change edit mode\n            if (this.configs.editMode !== 'normal') {\n                this.switchMode(this.configs.editMode);\n            }\n        },\n\n        onBeforeDestroy: function() {\n            this._normalMode();\n\n            // Trigger an event\n            Radio.trigger('notesForm', 'view:destroy');\n\n            // Stop listening to events\n            Radio.channel('notesForm')\n            .stopReplying('model show:editor')\n            .off('save:auto');\n\n            // Destroy shortcuts\n            Mousetrap.unbind(['ctrl+s', 'command+s', 'esc']);\n        },\n\n        showEditor: function(view) {\n            this.editor.show(view);\n        },\n\n        /**\n         * Close the form without saving.\n         */\n        cancel: function() {\n            // Save which element was under focus\n            this.options.focus = this.ui.title.is(':focus') ? 'title' : 'editor';\n\n            this.options.isClosed = true;\n            this.options.redirect = true;\n\n            this.trigger('cancel');\n            return false;\n        },\n\n        autoSave: function() {\n            if (this.options.isClosed) {\n                return;\n            }\n\n\t\t\t// Don't save tags when auto save notes\n\t\t\t// so that no unfinished tags are saved\n\t\t\tthis.options.saveTags = false;\n\n            this.options.redirect = false;\n            console.log('Auto saving the note...');\n            this.trigger('save');\n        },\n\n        save: function(e) {\n            if (e.preventDefault) {\n                e.preventDefault();\n            }\n\n\t\t\tthis.options.saveTags = true;\n            this.options.isClosed = true;\n            this.options.redirect = true;\n            this.trigger('save');\n\n            return false;\n        },\n\n        switchMode: function(e) {\n            var mode = (typeof e !== 'string' ? $(e.currentTarget).attr('data-mode') : e);\n            if (!mode) {\n                return;\n            }\n\n            // Close a dropdown menu\n            this.ui.form.trigger('click');\n\n            // Switch to another mode\n            this['_' + mode + 'Mode'].apply(this);\n\n            // Trigger an event\n            Radio.trigger('notesForm', 'set:mode', mode);\n\n            return false;\n        },\n\n        _fullscreenMode: function() {\n            this.$body\n            .removeClass('-preview')\n            .addClass('editor--fullscreen');\n        },\n\n        _previewMode: function() {\n            this.$body.addClass('editor--fullscreen -preview');\n        },\n\n        _normalMode: function() {\n            this.$body.removeClass('editor--fullscreen -preview');\n        }\n    });\n\n    return View;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/views/notebook.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette'\n], function(_, Marionette) {\n    'use strict';\n\n    var View = Marionette.ItemView.extend({\n        template : _.template('{=cleanXSS(name)}'),\n        tagName  : 'option',\n\n        onRender : function() {\n            this.$el.attr('value', this.model.get('id'));\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/form/views/notebooks.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notes/form/views/notebook',\n    'text!apps/notes/form/templates/notebooks.html'\n], function(_, Marionette, Radio, ItemView, Tmpl) {\n    'use strict';\n\n    /**\n     * Notebooks view. It shows a selector of notebooks.\n     *\n     * requests:\n     * 1. channel: `appNotebooks`, request: `show:form`\n     *    in order to show the notebook form.\n     */\n    var View = Marionette.CompositeView.extend({\n        template: _.template(Tmpl),\n\n        childView          :  ItemView,\n        childViewContainer :  '.editor--notebooks--list',\n\n        ui: {\n            notebookId : '[name=\"notebookId\"]'\n        },\n\n        events: {\n            'change @ui.notebookId': 'addNotebook'\n        },\n\n        collectionEvents: {\n            'change'   : 'render',\n            'add:model': 'selectModel'\n        },\n\n        onRender: function() {\n            this.ui.notebookId.val(this.options.activeId);\n        },\n\n        selectModel: function(model) {\n            this.ui.notebookId.val(model.id);\n        },\n\n        addNotebook: function() {\n            if (this.ui.notebookId.find('.addNotebook').is(':selected')) {\n                var self = this;\n                Radio.request('appNotebooks', 'show:form')\n                    .then(function(notebook) {\n\n                        // Set the active notebook if one was created by the user.\n                        if (notebook) {\n                            self.ui.notebookId.val(notebook.id);\n                        }\n                    });\n\n                this.ui.notebookId.val(this.options.activeId);\n            }\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/list/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'jquery',\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'apps/notes/list/views/noteSidebar'\n], function ($, _, Radio, Marionette, Sidebar) {\n    'use strict';\n\n    /**\n     * Notes list controller - shows notes list in the sidebar\n     *\n     * Listens to\n     * ----------\n     * Events:\n     * 1. channel: `appNote`, event: `model:active`\n     *    triggers `focus` event on the passed model.\n     * 2. channel: `notes`, event: `model:navigate`\n     *    navigates to the model which was passed with the event.\n     *\n     * Triggers\n     * --------\n     * Events:\n     * 1. channel: `global`, event: `navigate`\n     *    to navigate to a note page\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            this.radio = Radio.channel;\n            this.options = options;\n            _.bindAll(this, 'show', 'filter', 'navigate');\n\n            // Get configs\n            this.configs = Radio.request('configs', 'get:object');\n\n            // Listen to events\n            this.listenTo(this.radio('appNote'), 'model:active', this.onModelActive, this);\n            this.listenTo(this.radio('notes'), 'model:navigate', _.debounce(this.navigate, 420));\n\n            // Fetch notes and show them\n            this.filter(options);\n        },\n\n        onDestroy: function() {\n            this.view.trigger('destroy');\n            this.view.collection.trigger('reset:all');\n        },\n\n        /**\n         * Renders the sidebar view\n         */\n        show: function(notes) {\n            // Destroy old view\n            if (this.view) {\n                this.view.trigger('destroy');\n                this.view = null;\n            }\n\n            // Render the view\n            this.view = new Sidebar({\n                collection: notes,\n                args      : this.options\n            });\n            Radio.request('global', 'region:show', 'sidebar', this.view);\n        },\n\n        /**\n         * Fetches data from `Notes` collection.\n         */\n        filter: _.debounce(function(options) {\n            options.profile = options.profile || Radio.request('uri', 'profile');\n            var tOptions    = this.view ? this.view.options.args : {},\n                isEqual     = _.isEqual(\n                    _.pick((tOptions), 'filter', 'profile', 'query', 'page'),\n                    _.pick(options   , 'filter', 'profile', 'query', 'page')\n                );\n\n            // Do not fetch anything because nothing has changed\n            if (isEqual) {\n                return;\n            }\n\n            // Show the navbar\n            Radio.request('navbar', 'start', options);\n            options.pageSize = this.configs.pagination;\n            this.options = options;\n\n            // Fetch data\n            Radio.request('notes', 'get:all', options)\n            .then(this.show);\n        }, 100),\n\n        onModelActive: function(model) {\n            // The view was not rendered or the model is already active\n            if (!this.view || !model || model.id === this.view.options.args.id) {\n                return;\n            }\n\n            // Trigger `focus` event to a model.\n            this.view.options.args.id = model.id;\n            model = this.view.collection.get(model.id);\n            if (model) {\n                model.trigger('focus');\n            }\n        },\n\n        navigate: function(model) {\n            var args = Radio.request('appNote', 'route:args');\n\n            /**\n             * Before navigating to a note, change URI.\n             * It is done because if a user navigates back to the same page\n             * a note might not appear at all.\n             */\n            Radio.request('uri', 'navigate', {options: args}, {trigger: false});\n\n            // Navigate to a note page\n            Radio.request(\n                'uri', 'navigate',\n                {options: args, model: model}, {trigger: true}\n            );\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/list/listApp.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'app',\n    'backbone.radio',\n    'apps/notes/list/controller'\n], function(_, App, Radio, Controller) {\n    'use strict';\n\n    /**\n     * List module - shows notes list in the sidebar.\n     *\n     * Listens to events on channel `appNote`:\n     * 1. `filter` - filters notes\n     */\n    var List = App.module('AppNote.List', {\n        startWithParent: false\n    });\n\n    List.on('before:start', function(options) {\n        List.controller = new Controller(options);\n        Radio.reply('appNote', 'filter', List.controller.filter, List.controller);\n    });\n\n    List.on('before:stop', function() {\n        Radio.channel('appNote').stopReplying('filter');\n        List.controller.destroy();\n        List.controller = null;\n    });\n\n    return List;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/list/templates/sidebarList.html",
    "content": "<!-- List of notes here -->\n<div class=\"list\">\n</div>\n\n<% if (collection.state.totalPages > 1) { %>\n<nav class=\"navbar navbar-static-bottom navbar-bottom list--pager\" id=\"pageNav\">\n    <div class=\"btn-group btn-group-justified\" role=\"button\">\n        <div class=\"btn-group\">\n            <button class=\"list--pager--btn btn btn-default<% if(!collection.hasPreviousPage()) { %> disabled<% } %>\"\n                type=\"button\" id=\"prevPage\">\n                {{ i18n('Newer') }}\n            </button>\n        </div>\n        <div class=\"btn-group\">\n            <button class=\"list--pager--btn btn btn-default<% if(!collection.hasNextPage()) { %> disabled<% } %>\"\n                type=\"button\" id=\"nextPage\">\n                {{ i18n('Older') }}\n            </button>\n        </div>\n    </div>\n</nav>\n<% } %>\n"
  },
  {
    "path": "app/scripts/apps/notes/list/templates/sidebarListItem.html",
    "content": "<a href=\"#{{ link() }}\" class=\"list--item -note list-group-item {{ isActive() }}\" id=\"note-{{ id }}\" data-id=\"{{ id }}\">\n    <h4 class=\"list-group-item-heading clearfix\">\n        <span class=\"list--item--title -note\">{= cleanXSS(title, false, true) }</span>\n        <span class=\"favorite list--item--favorite icon-star-empty <% if (isFavorite === 1) {%>icon-favorite<% } %>\"></span>\n    </h4>\n    <p class=\"list-group-item-text list--item--text\">{{ getContent() }}</p>\n</a>\n"
  },
  {
    "path": "app/scripts/apps/notes/list/views/noteSidebar.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'behaviors/sidebar',\n    'apps/notes/list/views/noteSidebarItem',\n    'text!apps/notes/list/templates/sidebarList.html',\n    'mousetrap'\n], function(_, Marionette, Radio, Behavior, NoteSidebarItem, Tmpl, Mousetrap) {\n    'use strict';\n\n    /**\n     * Sidebar composite view.\n     *\n     * Listens to\n     * -----------\n     * Events:\n     * 1. channel: `notes`, event: `model:navigate`\n     *    Makes the provided model active.\n     */\n    var View = Marionette.CompositeView.extend({\n        template           :  _.template(Tmpl),\n\n        childView          :  NoteSidebarItem,\n        childViewContainer :  '.list',\n        childViewOptions   :  {},\n\n        behaviors: {\n            SidebarBehavior: {\n                behaviorClass: Behavior\n            }\n        },\n\n        ui: {\n            pageNav  : '#pageNav',\n            prevPage : '#prevPage',\n            nextPage : '#nextPage'\n        },\n\n        events: {\n            'click @ui.nextPage': 'nextPage',\n            'click @ui.prevPage': 'previousPage'\n        },\n\n        collectionEvents: {\n            'page:next'     : 'nextPage',\n            'page:previous' : 'previousPage',\n            'reset'         : 'updatePagination'\n        },\n\n        childEvents: {\n            'scroll:top': 'changeScrollTop'\n        },\n\n        initialize: function() {\n            _.bindAll(this, 'toNextNote', 'toPreviousNote');\n\n            this.$scroll  = $('#sidebar .-scroll');\n            this.configs = Radio.request('configs', 'get:object');\n\n            // Shortcuts\n            Mousetrap.bind(this.configs.navigateBottom, this.toNextNote);\n            Mousetrap.bind(this.configs.navigateTop, this.toPreviousNote);\n\n            // Events\n            this.listenTo(Radio.channel('notes'), 'model:navigate', this.modelFocus, this);\n\n            // Pass options to childView\n            this.childViewOptions.args = this.options.args;\n        },\n\n        onDestroy: function() {\n            Mousetrap.unbind([this.configs.navigateBottom, this.configs.navigateTop]);\n        },\n\n        onBeforeRender: function() {\n            this.options.args = Radio.request('appNote', 'route:args') || this.options.args;\n            this.childViewOptions.args = this.options.args;\n        },\n\n        /**\n         * Makes the provided model active.\n         */\n        modelFocus: _.debounce(function(model) {\n            this.options.args.id = model.id;\n            model.trigger('focus');\n        }, 10),\n\n        toNextNote: function() {\n            this.collection.getNextItem(this.options.args.id);\n            return false;\n        },\n\n        toPreviousNote: function() {\n            this.collection.getPreviousItem(this.options.args.id);\n            return false;\n        },\n\n        /**\n         * Updates pagination buttons\n         */\n        updatePagination: function() {\n            this.ui.pageNav.toggleClass('hidden', this.collection.state.totalPages <= 1);\n            this.ui.prevPage.toggleClass('disabled', !this.collection.hasPreviousPage());\n            this.ui.nextPage.toggleClass('disabled', !this.collection.hasNextPage());\n        },\n\n        /**\n         * Gets next page's models and resets the collection\n         */\n        nextPage: function() {\n            if (this.ui.nextPage.hasClass('disabled')) {\n                return false;\n            }\n\n            this.navigatePage(1);\n            this.collection.getNextPage();\n        },\n\n        /**\n         * Gets previous page's models and resets the collection\n         */\n        previousPage: function() {\n            if (this.ui.prevPage.hasClass('disabled')) {\n                return false;\n            }\n\n            this.navigatePage(-1);\n            this.collection.getPreviousPage();\n        },\n\n        /**\n         * Saves page status in window.location\n         */\n        navigatePage: function(number) {\n            this.options.args.page = this.collection.state.currentPage + number;\n\n            Radio.request(\n                'uri', 'navigate',\n                {options: this.options.args}, {trigger: false}\n            );\n        },\n\n        /**\n         * Changes scroll position.\n         */\n        changeScrollTop: function(view, scrollTop) {\n            this.$scroll.scrollTop(\n                scrollTop -\n                this.$scroll.offset().top +\n                this.$scroll.scrollTop() - 100\n            );\n        },\n\n        serializeData: function() {\n            var viewData = {\n                collection  : this.collection,\n                args        : this.options.args\n            };\n            return viewData;\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/list/views/noteSidebarItem.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'text!apps/notes/list/templates/sidebarListItem.html',\n], function(_, Radio, Marionette, Tmpl) {\n    'use strict';\n\n    /**\n     * Sidebar item view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'list-group list--group',\n\n        ui: {\n            favorite : '.favorite',\n        },\n\n        events: {\n            'click @ui.favorite': 'toggleFavorite'\n        },\n\n        modelEvents: {\n            'change'            : 'render',\n            'change:trash'      : 'remove',\n            'focus'             : 'onChangeFocus'\n        },\n\n        initialize: function() {\n            this.options.args.page = this.model.collection.state.currentPage;\n        },\n\n        toggleFavorite: function() {\n            Radio.request('notes', 'save', this.model, this.model.toggleFavorite());\n            return false;\n        },\n\n        onChangeFocus: function() {\n            var $listGroup = this.$('.list-group-item');\n\n            $('.list-group-item.active').removeClass('active');\n            $listGroup.addClass('active');\n\n            this.trigger('scroll:top', $listGroup.offset().top);\n        },\n\n        serializeData: function() {\n            // Decrypting\n            return _.extend(this.model.toJSON(), {\n                args    : this.options.args\n            });\n        },\n\n        templateHelpers: function() {\n            return {\n                // Show only first 50 characters of the content\n                getContent: function() {\n                    return _.unescape(this.content).substring(0, 50);\n                },\n\n                // Generate link\n                link: function() {\n                    return Radio.request('uri', 'link', this.args, this);\n                },\n\n                isActive: function() {\n                    return this.args.id === this.id ? 'active' : '';\n                }\n            };\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/remove/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette'\n], function(_, Radio, Marionette) {\n    'use strict';\n\n    /**\n     * A controller which removes a note.\n     *\n     * Requests:\n     * 1. channel: `notes`, request: `get:model`\n     *    expects to receive a model with provided ID\n     * 2. channel: `notes`, request: `remove`\n     * 3. channel: `Confirm`, request: `start`\n     */\n    var Controller = Marionette.Object.extend({\n\n        labels: [\n            'notes.confirm trash',\n            'notes.confirm remove'\n        ],\n\n        initialize: function(options) {\n            this.options = options;\n            _.bindAll(this, 'showConfirm');\n\n            // Events\n            this.listenTo(Radio.channel('notes'), 'destroy:model', this.destroy);\n            this.listenTo(Radio.channel('Confirm'), 'cancel', this.destroy);\n            this.listenTo(Radio.channel('Confirm'), 'confirm', this.remove);\n\n            // Fetch the note by ID\n            var self = this;\n            Radio.request('notes', 'get:model', options)\n            .then(function(model){\n                // Delete note without dialog when new note was canceled\n                if(self.options.deleteDirect){\n                    Radio.request('notes', 'remove',model);\n                }\n                // Or else show the dialog\n                else{\n                    self.showConfirm(model);\n                }\n            });\n        },\n\n        /**\n         * Show a confirmation dialog before removing a note.\n         */\n        showConfirm: function(model) {\n            var content = this.labels[Number(model.get('trash'))];\n            this.model = model;\n\n            Radio.request('Confirm', 'start', {\n                content : $.t(content, model.toJSON())\n            });\n        },\n\n        remove: function() {\n            Radio.request('notes', 'remove', this.model);\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/show/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'app',\n    'backbone.radio',\n    'marionette',\n    'apps/notes/show/controller'\n], function(_, $, App, Radio, Marionette, Controller) {\n    'use strict';\n\n    /**\n     * A module which instantiates the controller that shows a note.\n     *\n     * Listens to\n     * ----------\n     * Events:\n     * 1. channel: `notes`, event: `model:navigate`\n     *    stops itself after this event.\n     */\n    var Show = App.module('AppNote.Show', {\n        startWithParent: false\n    });\n\n    Show.on('before:start', function(options) {\n        Show.controller = new Controller(options);\n        this.listenTo(Radio.channel('notes'), 'model:navigate', Show.stop, Show);\n    });\n\n    Show.on('before:stop', function() {\n        this.stopListening(Radio.channel('notes'));\n        Show.controller.destroy();\n        Show.controller = null;\n    });\n\n    return Show;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/show/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/notes/show/noteView'\n], function(_, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * The controller that shows a note.\n     *\n     * Triggers the following:\n     * Events:\n     * 1. channel: `appNote`, event: `model:active`\n     * Request:\n     * 1. channel: editor, request: task:toggle\n     * request:\n     * 1. channel: notes, request: save\n     * 2. channel: appNote, request: `remove:note`\n     *    in order to destroy a model\n     * 3. channel: global, request: `set:title`\n     *\n     * Requests:\n     * 1. channel: markdown, request: render\n     *    it expects to receive HTML.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            _.bindAll(this, '_show');\n            this.options = options;\n\n            // Fetch the note by ID\n            Radio.request('notes', 'get:model:full', options)\n            .spread(this._show);\n\n            this.listenTo(Radio.channel('notes'), 'destroy:model', this.onModelDestroy, this);\n        },\n\n        onDestroy: function() {\n            this.stopListening(this.view);\n            this.stopListening(Radio.channel('notes'));\n            Radio.request('global', 'region:empty', 'content');\n        },\n\n        _show: function(note, notebook) {\n            var self = this;\n\n            Radio.request('markdown', 'render', note)\n            .then(function(content) {\n                return self.render(note, content, notebook);\n            });\n        },\n\n        render: function(note, content, notebook) {\n            // Trigger an event that the model is active\n            Radio.trigger('appNote', 'model:active', note);\n\n            this.view = new View({\n                model    : note,\n                content  : content,\n                notebook : notebook,\n                args     : this.options,\n                files    : [],\n            });\n\n            // Show the view in the `content` region\n            Radio.request('global', 'region:show', 'content', this.view);\n\n            // Set document title\n            Radio.request('global', 'set:title', note.get('title'));\n\n            // Events\n            this.listenTo(Radio.channel('notes'), 'synced:' + note.id, this.onSync);\n            this.listenTo(this.view, 'toggle:task', this.toggleTask);\n            this.listenTo(this.view, 'model:remove', this.modelRemove);\n            this.listenTo(this.view, 'model:restore', this.modelRestore);\n        },\n\n        /**\n         * After a model is synchronized, refetch the model again.\n         */\n        onSync: function() {\n            var self = this;\n\n            Radio.request('notes', 'get:model:full', this.options)\n            .spread(function(note, notebook) {\n                Radio.request('markdown', 'render', note)\n                .then(function(content) {\n                    self.view.options.notebook = notebook;\n                    self.view.options.content = content;\n                    self.view.model.set(note.attributes);\n                    self.view.model.trigger('synced');\n                });\n            })\n            .fail(function(e) {\n                console.error('After sync error:', e);\n            });\n        },\n\n        modelRestore: function() {\n            Radio.request('notes', 'restore', this.view.model);\n        },\n\n        /**\n         * Triggers an event and expects that a model will be destroyed\n         */\n        modelRemove: function() {\n            Radio.request('appNote', 'remove:note', this.view.model.id);\n        },\n\n        /**\n         * Tries to get new content with toggled task.\n         * It is expected that such editor modules as Pagedown\n         * replies to the request `task:toggle` and returns an object with\n         * counts of completed tasks and content with toggled task.\n         */\n        toggleTask: function(taskId) {\n            var model = this.view.model;\n\n            return Radio.request('markdown', 'task:toggle', {\n                content : model.get('content'),\n                taskId  : taskId\n            })\n            .then(function(data) {\n                if (!data) {\n                    return;\n                }\n\n                // Save the note\n                Radio.request('notes', 'save', model, _.pick(data, 'content', 'taskCompleted', 'taskAll'));\n            });\n        },\n\n        /**\n         * Destroy this controller if the model is destroyed.\n         */\n        onModelDestroy: function(model) {\n            if (this.view.model.attributes.id === model.attributes.id) {\n                this.destroy();\n            }\n        }\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/show/noteView.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'backbone.radio',\n    'behaviors/content',\n    'text!apps/notes/show/templates/item.html',\n    'mousetrap'\n], function(_, $, Marionette, Radio, Behavior, Tmpl, Mousetrap) {\n    'use strict';\n\n    /**\n     * Note view.\n     *\n     * Triggers the following\n     * Events:\n     * 1. channel: noteView, event: view:render\n     *    when the view is rendered and ready.\n     * 2. channel: noteView, event: view:destroy\n     *    before the view is destroyed.\n     * Requests:\n     * 1. channel: global, request: configs\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'layout--body',\n\n        behaviors: {\n            ContentBehavior: {\n                behaviorClass: Behavior\n            }\n        },\n\n        ui: {\n            favorite : '.btn--favourite--icon',\n            body     : '.-scroll',\n\n            // Tasks\n            tasks    : '.task [type=\"checkbox\"]',\n            progress : '.progress-bar',\n            percent  : '.progress-percent',\n\n            // Action buttons\n            editBtn  : '.note--edit',\n            rmBtn    : '.note--remove'\n        },\n\n        events: {\n            'click .btn--favourite' : 'favorite',\n            'click @ui.tasks'       : 'onClickTask',\n            'click @ui.rmBtn'       : 'rmNote'\n        },\n\n        triggers: {\n            'click .note--restore' : 'model:restore'\n        },\n\n        modelEvents: {\n            'synced'               : 'render',\n            'change:trash'         : 'render',\n            'change:isFavorite'    : 'onChangeFavorite',\n            'change:taskCompleted' : 'onTaskCompleted'\n        },\n\n        initialize: function() {\n            _.bindAll(this, 'scrollTop', 'scrollDown', 'editNote', 'rmNote', 'favorite');\n\n            this.configs = Radio.request('configs', 'get:object');\n\n            Mousetrap.bind('up', this.scrollTop);\n            Mousetrap.bind('down', this.scrollDown);\n            Mousetrap.bind(this.configs.actionsEdit, this.editNote);\n            Mousetrap.bind(this.configs.actionsRemove, this.rmNote);\n            Mousetrap.bind(this.configs.actionsRotateStar, this.favorite);\n        },\n\n        onRender: function() {\n            Radio.trigger('noteView', 'view:render', this);\n        },\n\n        onBeforeDestroy: function() {\n            Mousetrap.unbind(['up', 'down', this.configs.actionsEdit, this.configs.actionsRemove, this.configs.actionsRotateStar]);\n            Radio.trigger('noteView', 'view:destroy');\n        },\n\n        scrollTop: function() {\n            this.ui.body.scrollTop(this.ui.body.scrollTop() - 50);\n            return false;\n        },\n\n        scrollDown: function() {\n            this.ui.body.scrollTop(this.ui.body.scrollTop() + 50);\n            return false;\n        },\n\n        editNote: function() {\n            Radio.request('uri', 'navigate', this.ui.editBtn.attr('href'));\n        },\n\n        rmNote: function() {\n            this.trigger('model:remove');\n            return false;\n        },\n\n        /**\n         * Changes favorite status of the note\n         */\n        favorite: _.throttle(function() {\n            Radio.request('notes', 'save', this.model, this.model.toggleFavorite());\n            return false;\n        }, 300, {leading: false}),\n\n        onChangeFavorite: function() {\n            this.ui.favorite.toggleClass('icon-favorite', this.model.get('isFavorite'));\n        },\n\n        onClickTask: function(e) {\n            e.preventDefault();\n            this.toggleTask(e);\n        },\n\n        /**\n         * Toggle the status of a task\n         */\n        toggleTask: _.debounce(function(e) {\n            var $task  = $(e.target),\n                taskId = Number($task.attr('data-task'));\n\n            $task.blur();\n            $task.prop('checked', $task.is(':checked') === false);\n            this.trigger('toggle:task', taskId);\n        }, 200),\n\n        /**\n         * Update progress bar when the status of a task was changed\n         */\n        onTaskCompleted: function() {\n            var percent = Math.floor(\n                this.model.get('taskCompleted') * 100 / this.model.get('taskAll')\n            );\n            this.ui.progress.css({width: percent + '%'});\n            this.ui.percent.html(percent + '%');\n        },\n\n        serializeData: function() {\n            // var content = Radio.request('markdown', 'render', this.model);\n            return _.extend(this.model.toJSON(), {\n                content  : this.options.content || this.model.get('content'),\n                notebook : this.options.notebook.toJSON(),\n                uri      : Radio.request('uri', 'link:profile', '/')\n            });\n        },\n\n        templateHelpers: function() {\n            return {\n                createdDate: function() {\n                    return new Date(this.created).toLocaleDateString();\n                },\n\n                getProgress: function() {\n                    return Math.floor(this.taskCompleted * 100 / this.taskAll);\n                }\n            };\n        }\n    });\n\n    return View;\n\n});\n"
  },
  {
    "path": "app/scripts/apps/notes/show/templates/item.html",
    "content": "<nav class=\"layout--navbar header navbar navbar-static-top -note\">\n<div class=\"container-fluid\">\n    <div class=\"navbar-left header--left navbar-btn\">\n        <button title=\"{{i18n('Show sidebar')}}\" id=\"show--sidebar\" class=\"btn -note btn-default visible-xs\">\n            <span class=\"icon-left-open\"></span>\n        </button>\n    </div>\n\n    <div class=\"navbar-right header--right navbar-btn dropdown\">\n        <a href=\"#{{uri}}notes/edit/{{id}}\" class=\"btn -note btn-default note--edit\">\n            <span class=\"icon-pencil\"></span>\n            <span class=\"hidden-xs\">{{i18n('Edit')}}</span>\n        </a>\n        <a href=\"#\" class=\"btn -note btn-default note--remove\">\n            <span class=\"icon-trashed\"></span>\n            <span class=\"hidden-xs\"><% if (trash === 0) { %>{{i18n('Delete')}}<% } else { %> {{i18n('Forever')}}<% } %></span>\n        </a>\n        <% if (trash !== 1) { %>\n        <a href=\"#{{uri}}settings\" class=\"btn -note btn-default\">\n            <span class=\"icon-cog\"></span>\n            <span>{{i18n('Settings')}}</span>\n        </a>\n        <% } else { %>\n        <a href=\"#\" class=\"btn -note btn-default\" data-toggle=\"dropdown\" aria-haspopup=\"true\" aria-expanded=\"false\">\n            <b class=\"caret\"></b>\n        </a>\n        <div class=\"dropdown-menu\">\n            <div class=\"btn-group\">\n                <a href=\"#\" class=\"note--restore btn\">\n                    <span class=\"icon-undo icon--block\"></span>\n                    {{i18n('Restore')}}\n                </a>\n                <a href=\"#{{uri}}settings\" class=\"btn\">\n                    <span class=\"icon-cog icon--block\"></span>\n                    {{i18n('Settings')}}\n                </a>\n            </div>\n        </div>\n        <% } %>\n    </div>\n</div>\n</nav>\n\n<div class=\"layout--body -scroll -note\">\n    <div class=\"content container-fluid -note\" data-id=\"{{id}}\">\n        <header>\n            <h1 class=\"page-header -note\">\n                {=cleanXSS(title)}\n                <small class=\"note--date\">{{createdDate()}}</small>\n                <button type=\"button\" class=\"btn btn-link btn--favourite pull-right\">\n                    <span class=\"btn--favourite--icon icon-star-empty <% if (isFavorite === 1) {%>icon-favorite<% } %>\"></span>\n                </button>\n            </h1>\n        </header>\n\n        <% if (taskAll !== 0) { %>\n        <div class=\"progress\">\n            <div class=\"progress-bar progress-bar-info\" role=\"progressbar\"\n                aria-valuenow=\"{{taskCompleted}}\" aria-valuemin=\"0\" aria-valuemax=\"{{taskAll}}\" style=\"width: {{getProgress(taskCompleted, taskAll)}}%\">\n                <span class=\"progress-percent\">\n                    {{getProgress()}}%\n                </span>\n            </div>\n        </div>\n        <% } %>\n\n        <p class=\"note--notebook\">\n            <% if (notebook && notebook.id) { %>\n            <a href=\"#{{uri}}notes/f/notebook/q/{{notebook.id}}\">\n                <i class=\"icon-notebook\"></i> {=cleanXSS(notebook.name)}\n            </a>\n            <% } %>\n        </p>\n\n        <div>\n            {=content}\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/appSettings.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/settings/sidebar/app'\n], function(_, Marionette, Radio, App, SidebarApp) {\n    'use strict';\n\n    /**\n     * Settings sub app.\n     */\n    var Settings = App.module('AppSettings', {startWithParent: false}),\n        controller;\n\n    /**\n     * The router\n     */\n    Settings.Router = Marionette.AppRouter.extend({\n\n        appRoutes: {\n            '(p/:profile/)settings(/:tab)'      : 'showSettings',\n            '(p/:profile/)settings/module/:tab' : 'showModule'\n        },\n\n        // Starts itself\n        onRoute: function() {\n            if (!Settings._isInitialized) {\n                App.startSubApp(\n                    'AppSettings',\n                    controller.getOptions.apply(controller, arguments[2])\n                );\n            }\n        }\n    });\n\n    controller = {\n        showSettings: function(profile, tab) {\n            requirejs(['apps/settings/show/app'], function(Module) {\n                // Stop previously started module\n                if (Settings.currentApp && controller.args.tab !== tab) {\n                    Settings.currentApp.stop();\n                }\n\n                Settings.currentApp = Module;\n                Module.start(controller.getOptions(profile, tab));\n            });\n        },\n\n        showModule: function(profile, module) {\n            requirejs(['apps/settings/module/app'], function(Module) {\n                // Stop previously started module\n                if (Settings.currentApp) {\n                    Settings.currentApp.stop();\n                }\n\n                Settings.currentApp = Module;\n                Module.start({profile: profile, module: module});\n            });\n        },\n\n        getOptions: function() {\n            controller.args = {\n                profile : arguments[0],\n                tab     : arguments[1] || 'general',\n            };\n            return controller.args;\n        }\n    };\n\n    /**\n     * Initializer and finalizer\n     */\n    Settings.on('before:start', function(options) {\n        SidebarApp.start(options);\n    });\n\n    Settings.on('before:stop', function() {\n        Settings.currentApp.stop();\n        Settings.currentApp = null;\n        controller.args = null;\n\n        SidebarApp.stop();\n    });\n\n    // Register the router\n    App.on('before:start', function() {\n        new Settings.Router({\n            controller: controller\n        });\n    });\n\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/settings/show/views/showView'\n], function(Q, _, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * The main controller.\n     *\n     * Replies:\n     * 1. channel: `AppSettings`, replies: `has:changes`\n     *    if there are some changes, show a confirm message\n     *\n     * requests:\n     * 1. channel: `AppSettings`, request: `activate`\n     * 2. channel: `global`, request: `region:empty`\n     * 3. channel: `global`, request: `region:show`\n     * 4. channel: `configs`, request: `create:profile`\n     * 5. channel: `configs`, request: `remove:profile`\n     * 6. channel: `configs`, request: `save:objects`\n     * 7. channel: `Confirm`, request: `start`\n     * 8. channel: `uri`, request: `navigate`\n     * 9. channel: `navbar`, request: `stop`\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            _.bindAll(this, 'show', 'onFetch', 'requireView', 'redirect');\n\n            options.tab  = options.tab || 'modules';\n            this.options = options;\n            this.changes = {};\n            this.saves   = {};\n\n            // Fetch configs\n            Q.all([\n                Radio.request('configs', 'get:all', options),\n                Radio.request('configs', 'get:model', {\n                    name    : 'useDefaultConfigs',\n                    profile : options.profile\n                }),\n                Radio.request('configs', 'get:model', 'appProfiles')\n            ])\n            .spread(this.onFetch)\n            .then(this.requireView)\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n\n            // Events, replies\n            Radio.reply('AppSettings', 'has:changes', this.hasChanges, this);\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            Radio.stopReplying('AppSettings', 'has:changes');\n            Radio.request('global', 'region:empty', 'content');\n        },\n\n        /**\n         * Show the layout.\n         */\n        onFetch: function(configs, useDefault, profiles) {\n            this.configs    = configs;\n            this.profiles   = profiles;\n            this.useDefault = useDefault;\n\n            // Instantiate layout view and show it\n            this.layout = new View(this.options);\n            Radio.request('global', 'region:show', 'content', this.layout);\n        },\n\n        /**\n         * Load a content view.\n         */\n        requireView: function() {\n        },\n\n        /**\n         * Render the content view.\n         */\n        show: function(ContentView) {\n            this.view = new ContentView({\n                collection : this.configs,\n                profiles   : this.profiles,\n                useDefault : this.useDefault\n            });\n\n            this.layout.content.show(this.view);\n\n            // Collection events\n            this.listenTo(this.configs, 'new:value', this.onChange);\n\n            // Layout events\n            this.listenTo(this.layout, 'cancel', this.confirmRedirect);\n            this.listenTo(this.layout, 'save', this.save);\n        },\n\n        onChange: function(data) {\n            this.changes[data.name] = data;\n        },\n\n        /**\n         * Reload the page when config 'useDefaultConfigs' is changed\n         */\n        onChangeConfigs: function(changes) {\n            if (changes.useDefaultConfigs) {\n                window.location.reload();\n            }\n\n            this.redirect();\n        },\n\n        save: function() {\n\n            // Do nothing if there are not any changes\n            if (_.isEmpty(this.changes)) {\n                this.redirect();\n                return;\n            }\n\n            Radio.channel('configs')\n            .request('save:objects', this.changes, this.useDefault);\n\n            this.saves = _.union(this.saves, this.changes);\n            this.changes = {};\n        },\n\n        /**\n         * Before closing the page, show a confirm message\n         */\n        confirmRedirect: function() {\n            this.showConfirm(this.redirect);\n        },\n\n        /**\n         * If there are any changes, show a confirm message.\n         */\n        hasChanges: function() {\n            var defer = Q.defer();\n            this.showConfirm(defer.resolve, defer.reject);\n            return defer.promise;\n        },\n\n        showConfirm: function(onconfirm, onreject) {\n            if (_.isEmpty(this.changes)) {\n                return onconfirm();\n            }\n\n            Radio.request('Confirm', 'start', {\n                content   : $.t('You have unsaved changes'),\n                onconfirm : onconfirm,\n                onreject  : onreject\n            });\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/module/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/settings/module/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    var Module = App.module('AppSettings.Module', {startWithParent: false});\n\n    /**\n     * Initializer & finalizer\n     */\n    Module.on('before:start', function(options) {\n        Module.controller = new Controller(options);\n    });\n\n    Module.on('before:stop', function() {\n        Module.controller.destroy();\n        Module.controller = null;\n    });\n\n    return Module;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/module/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/settings/controller',\n], function(Q, _, Marionette, Radio, BasicController) {\n    'use strict';\n\n    /**\n     * Module configs.\n     */\n    var Controller = BasicController.extend({\n\n        /**\n         * Show layout view and load a module view\n         */\n        requireView: function() {\n            requirejs(['modules/' + this.options.module + '/views/settings'], this.show);\n        },\n\n        redirect: function() {\n            Radio.request('uri', 'navigate', '/settings/modules', {\n                trigger        : false,\n                includeProfile : true\n            });\n            window.location.reload();\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/settings/show/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    var Show = App.module('AppSettings.Show', {startWithParent: false});\n\n    /**\n     * Initializer & finalizer\n     */\n    Show.on('before:start', function(options) {\n        Show.controller = new Controller(options);\n    });\n\n    Show.on('before:stop', function() {\n        Show.controller.destroy();\n        Show.controller = null;\n    });\n\n    return Show;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/settings/controller',\n    'helpers/fileSaver',\n], function(Q, _, Marionette, Radio, BasicController, fileSaver) {\n    'use strict';\n\n    /**\n     * Settings show controller.\n     */\n    var Controller = BasicController.extend({\n\n        initialize: function(options) {\n\n            // Execute parent initializer first\n            _.bind(BasicController.prototype.initialize, this)(options);\n\n            // Activate tab in sidebar\n            Radio.request('AppSettings', 'activate:tab', this.options.tab);\n\n            // Events, replies\n            this.listenTo(Radio.channel('configs'), 'changed', this.onChangeConfigs);\n        },\n\n        /**\n         * Load the tab view.\n         */\n        requireView: function() {\n            requirejs(['apps/settings/show/views/' + this.options.tab], this.show);\n        },\n\n        /**\n         * Show settings.\n         */\n        show: function(TabView) {\n\n            // Execute parent function first\n            _.bind(BasicController.prototype.show, this)(TabView);\n\n            // View events\n            this.listenTo(this.view, 'remove:profile', this.confirmRmProfile);\n            this.listenTo(this.view, 'create:profile', this.createProfile);\n            this.listenTo(this.view, 'import', this.import);\n            this.listenTo(this.view, 'export', this.export);\n        },\n\n        /**\n         * Create a new profile.\n         */\n        createProfile: function(name) {\n            Radio.request('configs', 'create:profile', this.profiles, name);\n        },\n\n        /**\n         * Before removing a profile, show a confirm message.\n         */\n        confirmRmProfile: function(name) {\n            var self = this;\n\n            Radio.request('Confirm', 'start', {\n                content   : $.t('profile.confirm remove', {profile: name}),\n                onconfirm : function() {\n                    self.removeProfile(name);\n                }\n            });\n        },\n\n        /**\n         * Remove a profile\n         */\n        removeProfile: function(name) {\n            Radio.request('configs', 'remove:profile', this.profiles, name);\n        },\n\n        /**\n         * Import settings from a JSON file\n         */\n        import: function(data) {\n            var reader = new FileReader(),\n                self   = this;\n\n            reader.onload = function(evt) {\n                try {\n                    self.changes = JSON.parse(evt.target.result);\n                    self.save();\n                } catch (e) {\n                    Radio.request('Confirm', 'start', {\n                        title   : $.t('Wrong format'),\n                        content : $.t('File chould be in json format')\n                    });\n                }\n            };\n\n            reader.readAsText(data.files[0]);\n        },\n\n        /**\n         * Export settings to a JSON file.\n         */\n        export: function() {\n            var blob = new Blob(\n                [JSON.stringify(this.configs)],\n                {type: 'text/plain;charset=utf8'}\n            );\n            fileSaver(blob, 'laverna-settings.json');\n        },\n\n        /**\n         * Before closing the page, show a confirm message\n         */\n        confirmRedirect: function() {\n            this.showConfirm(this.redirect);\n        },\n\n        redirect: function() {\n            Radio.request('uri', 'navigate', '/notes', {\n                trigger        : false,\n                includeProfile : true\n            });\n            window.location.reload();\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/formBehavior.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'marionette',\n    'sjcl'\n], function(Marionette, sjcl) {\n    'use strict';\n\n    /**\n     * Default behaviour for settings views.\n     */\n    var FormBehavior = Marionette.Behavior.extend({\n\n        events: {\n            'input input, select, textarea' : 'triggerChange',\n            'change input, select, textarea': 'triggerChange',\n            'change .show-onselect'   : 'showOnSelect',\n            'click .showField'        : 'showOnCheck',\n        },\n\n        initialize: function() {\n            this.view.on('render', this.popover, this);\n        },\n\n        popover: function() {\n            var pop = this.view.$('.popover-dropbox').html();\n\n            this.view.$('.popover-key').popover({\n                trigger: 'click',\n                html   : true,\n                content: function() { return pop; }\n            });\n        },\n\n        triggerChange: function(e) {\n            var el   = $(e.target),\n                conf = { name: el.attr('name') };\n\n            if (el.attr('type') !== 'checkbox') {\n                conf.value = el.val();\n            }\n            else {\n                conf.value = (el.is(':checked')) ? 1 : 0;\n            }\n\n            if (el.hasClass('hex') && typeof conf.value === 'string') {\n                conf.value = sjcl.codec.hex.toBits(conf.value);\n            }\n\n            this.view.collection.trigger('new:value', conf);\n        },\n\n        /**\n         * Shows additional parameters when option is selected\n         */\n        showOnSelect: function(e) {\n            var $el = $(e.target),\n                option = $el.find('option[value=' + $el.attr('data-option') + ']');\n\n            if (option.is(':selected')) {\n                $( option.attr('data-show') ).removeClass('hidden');\n            }\n            else {\n                $( option.attr('data-show') ).addClass('hidden');\n            }\n        },\n\n        /**\n         * Shows fieldsets with aditional parameters when checkbox is checked\n         */\n        showOnCheck: function(e) {\n            var input = $(e.currentTarget),\n                field = $(input.attr('data-field'));\n\n            if ( input.is(':checked') ) {\n                field.removeClass('hidden');\n            }\n            else {\n                field.addClass('hidden');\n            }\n        }\n    });\n\n    return FormBehavior;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'marionette'\n], function(Marionette) {\n    'use strict';\n\n    /**\n     * Module settings.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/editor.html",
    "content": "<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"editMode\">{{ i18n('Default edit mode') }}</label>\n    <div class=\"col-sm-10\">\n        <select id=\"editMode\" class=\"form-control\" name=\"editMode\" checked=\"{{models.editMode}}\">\n            <option value=\"normal\">{{ i18n('Normal') }}</option>\n            <option value=\"fullscreen\" <%if(models.editMode === 'fullscreen'){%>selected<%}%>>{{ i18n('Fullscreen') }}</option>\n            <option value=\"preview\" <%if(models.editMode === 'preview'){%>selected<%}%>>{{ i18n('Fullscreen with preview') }}</option>\n        </select>\n    </div>\n</div>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"indentUnit\">{{ i18n('Spaces per indent') }}</label>\n    <div class=\"col-sm-10\">\n        <input type=\"number\" id=\"indentUnit\" name=\"indentUnit\" value=\"{{models.indentUnit}}\" class=\"form-control\" >\n        <div id=\"indentUnit-low-warning\" class=\"text-warning\" <%if(models.indentUnit >= 3){%>style=\"display: none;\"<%}%>>{{ i18n('Recommended 3 or more, for indenting numbered lists') }}</div>\n    </div>\n</div>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"textEditor\">{{ i18n('Text editor') }}</label>\n    <div class=\"col-sm-10\">\n\t\t<!-- TODO Change editMode in Zeile drunter-->\n\t\t<!-- TODO Add translation-->\n        <select id=\"textEditor\" class=\"form-control\" name=\"textEditor\" checked=\"{{models.textEditor}}\">\n            <option value=\"default\">{{ i18n('Default') }}</option>\n\t\t\t<option value=\"vim\" <%if(models.textEditor === 'vim'){%>selected<%}%>>{{ i18n('Vim') }}</option>\n\t\t\t<option value=\"emacs\" <%if(models.textEditor === 'emacs'){%>selected<%}%>>{{ i18n('Emacs') }}</option>\n\t\t\t<option value=\"sublime\" <%if(models.textEditor === 'sublime'){%>selected<%}%>>{{ i18n('Sublime') }}</option>\n        </select>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/encryption.html",
    "content": "<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"useEncryption\">{{ i18n('Use encryption') }}</label>\n    <div class=\"col-sm-10 checkbox\">\n         <label class=\"control-label\">\n             <input id=\"useEncryption\" type=\"checkbox\" name=\"encrypt\" <% if(Number(models.encrypt) === 1) { %> checked<% } %> >\n             {{ i18n('Yes') }}\n         </label>\n    </div>\n</div>\n\n<fieldset id=\"encryptOpt\" <% if(Number(models.encrypt) === 0) { %>disabled=\"disabled\"<% } %>>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"encryptPass\">{{ i18n('Encryption Password') }}</label>\n        <div class=\"col-sm-10\">\n            <input type=\"password\" id=\"encryptPass\" name=\"encryptPass\" value=\"\" placeholder=\"{{passwordText()}}\" class=\"form-control\" />\n        </div>\n    </div>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"encryptSalt\">{{ i18n('Salt') }}</label>\n        <div class=\"col-sm-10\">\n            <input class=\"form-control hex\" disabled type=\"text\" name=\"encryptSalt\" id=\"encryptSalt\" value=\"{{hex(models.encryptSalt)}}\" >\n\n            <a id=\"randomize\" href=\"#\">{{ i18n('Random') }}</a>\n        </div>\n    </div>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"encryptIter\">{{ i18n('Strengthen by a factor of') }}</label>\n        <div class=\"col-sm-10\">\n            <input type=\"number\" name=\"encryptIter\" id=\"encryptIter\" value=\"{{models.encryptIter}}\" class=\"form-control\" >\n        </div>\n    </div>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"encryptKeySize\">{{ i18n('Key size') }}</label>\n        <div class=\"col-sm-10\">\n            <select class=\"form-control\" name=\"encryptKeySize\" id=\"encryptKeySize\">\n                <option value=\"128\"<%if(models.encryptKeySize === '128'){ %> selected<% } %>>128</option>\n                <option value=\"192\"<%if(models.encryptKeySize === '192'){ %> selected<% } %>>192</option>\n                <option value=\"256\"<%if(models.encryptKeySize === '256'){ %> selected<% } %>>256</option>\n            </select>\n        </div>\n    </div>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"encryptTag\">{{ i18n('Authentication strength') }}</label>\n        <div class=\"col-sm-10\">\n            <select class=\"form-control\" name=\"encryptTag\" id=\"encryptTag\">\n                <option value=\"64\"<%if(models.encryptTag === '64'){ %> selected<% } %>>64</option>\n                <option value=\"96\"<%if(models.encryptTag === '96'){ %> selected<% } %>>96</option>\n                <option value=\"128\"<%if(models.encryptTag === '128'){ %> selected<% } %>>128</option>\n            </select>\n        </div>\n    </div>\n</fieldset>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/general.html",
    "content": "<% if (!isDefaultProfile()) { %>\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"useDefaultConfigs\">\n        {{ i18n('useDefaultConfigs') }}\n    </label>\n    <div class=\"col-sm-10 checkbox\">\n         <label class=\"control-label\">\n             <input id=\"useDefaultConfigs\" type=\"checkbox\" name=\"useDefaultConfigs\" <% if(Number(useDefault.value) === 1) { %> checked<% } %> >\n             {{ i18n('Yes') }}\n         </label>\n    </div>\n</div>\n<% } %>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"appLang\">\n        <i class=\"fa fa-cloud\"></i> {{ i18n('Language') }}\n    </label>\n    <div class=\"col-sm-10\">\n        <select class=\"form-control\" name=\"appLang\" id=\"appLang\">\n        <% _.forEach(locales, function(val, locale) { %>\n        <option value=\"{{ locale }}\"{{ isLocaleActive(locale) }}>\n            {{ val.nativeName }}\n        </option>\n        <% }); %>\n        </select>\n    </div>\n</div>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"pagination\">{{ i18n('Notes per page') }}</label>\n    <div class=\"col-sm-10\">\n        <input type=\"number\" id=\"pagination\" name=\"pagination\" value=\"{{models.pagination}}\" class=\"form-control\" >\n    </div>\n</div>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"sortnotes\">{{ i18n('Sort notes') }}</label>\n    <div class=\"col-sm-10\">\n        <select id=\"sortnotes\" class=\"form-control\" name=\"sortnotes\" checked=\"{{models.sortnotes}}\">\n            <option value=\"created\">{{ i18n('Created date') }}</option>\n            <option value=\"updated\" <%if(models.sortnotes === 'updated'){%>selected<%}%>>\n            {{ i18n('Updated date') }}\n            </option>\n            <option value=\"title\" <%if(models.sortnotes === 'title'){%>selected<%}%>>\n            {{ i18n('Title') }}\n            </option>\n        </select>\n    </div>\n</div>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"sortnotebooks\">{{ i18n('Sort notebooks') }}</label>\n    <div class=\"col-sm-10\">\n        <select id=\"sortnotebooks\" class=\"form-control\" name=\"sortnotebooks\" checked=\"{{models.sortnotebooks}}\">\n            <option value=\"name\">{{ i18n('Name') }}</option>\n            <option value=\"created\" <%if(models.sortnotebooks === 'created'){%>selected<%}%>>\n            {{ i18n('Created date') }}\n            </option>\n        </select>\n    </div>\n</div>\n\n<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"navbarNotebooksMax\">{{ i18n('Notebooks in drawer') }}</label>\n    <div class=\"col-sm-10\">\n        <input type=\"number\" id=\"navbarNotebooksMax\" name=\"navbarNotebooksMax\" value=\"{{models.navbarNotebooksMax}}\" class=\"form-control\" >\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/importExport.html",
    "content": "<div class=\"center-block\">\n    <header>\n        <h3>{{i18n('Transfer settings')}}</h3>\n    </header>\n\n    <input id=\"import-file\" class=\"hide\" type=\"file\">\n    <button data-file=\"#import-file\" id=\"do-import\" type=\"button\" class=\"btn btn-default btn-block btn--import\">\n        <i class=\"icon-cog\"></i> {{ i18n('Import settings') }}\n    </button>\n    <button id=\"do-export\" type=\"button\" class=\"btn btn-default btn-block\">\n        <i class=\"icon-cog\"></i> {{ i18n('Export settings') }}\n    </button>\n</div>\n\n<div class=\"center-block\">\n    <header>\n        <h3>{{i18n('Transfer everything')}}</h3>\n    </header>\n\n    <input id=\"import-data-file\" class=\"hide\" type=\"file\">\n    <button data-file=\"#import-data-file\" id=\"import-data\" type=\"button\" class=\"btn btn-default btn-block btn--import\">\n        <i class=\"icon-note\"></i> {{ i18n('Import data') }}\n    </button>\n    <button id=\"export-data\" type=\"button\" class=\"btn btn-default btn-block\">\n        <i class=\"icon-note\"></i> {{ i18n('Export data') }}\n    </button>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/keybindings.html",
    "content": "<fieldset>\n    <legend>{{ i18n('Navigation') }}</legend>\n    <% _.forEach(filter('navigate'), function (model) { %>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"{{ model.get('name') }}\">{{ i18n(model.get('name')) }}</label>\n        <div class=\"col-sm-10\">\n            <input id=\"{{ model.get('name') }}\" class=\"form-control\" type=\"text\" value=\"{{ model.get('value') }}\" name=\"{{ model.get('name') }}\"/>\n        </div>\n    </div>\n    <% }); %>\n</fieldset>\n<fieldset>\n    <legend>{{ i18n('Jump') }}</legend>\n    <% _.forEach(filter('jump'), function (model) { %>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"{{ model.get('name') }}\">{{ i18n(model.get('name')) }}</label>\n        <div class=\"col-sm-10\">\n            <input id=\"{{ model.get('name') }}\" class=\"form-control\" type=\"text\" value=\"{{ model.get('value') }}\" name=\"{{ model.get('name') }}\"/>\n        </div>\n    </div>\n    <% }); %>\n</fieldset>\n<fieldset>\n    <legend>{{ i18n('Actions') }}</legend>\n    <% _.forEach(filter('actions'), function (model) { %>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"{{ model.get('name') }}\">{{ i18n(model.get('name')) }}</label>\n        <div class=\"col-sm-10\">\n            <input id=\"{{ model.get('name') }}\" class=\"form-control\" type=\"text\" value=\"{{ model.get('value') }}\" name=\"{{ model.get('name') }}\"/>\n        </div>\n    </div>\n    <% }); %>\n</fieldset>\n<fieldset>\n    <legend>{{ i18n('App') }}</legend>\n    <% _.forEach(appShortcuts(), function (model) { %>\n    <div class=\"form-group\">\n        <label class=\"col-sm-2 control-label\" for=\"{{ model.get('name') }}\">{{ i18n(model.get('name')) }}</label>\n        <div class=\"col-sm-10\">\n            <input id=\"{{ model.get('name') }}\" class=\"form-control\" type=\"text\" value=\"{{ model.get('value') }}\" name=\"{{ model.get('name') }}\"/>\n        </div>\n    </div>\n    <% }); %>\n</fieldset>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/modules.html",
    "content": "<% _.each(modules, function(module) { %>\n<div class=\"media\">\n    <div class=\"media-left\">\n    </div>\n    <div class=\"media-body\">\n        <div class=\"col-xs-8\">\n        <h4 class=\"media-heading\">{{module.name}}</h4>\n        <p>{{module.description}}</p>\n        </div>\n\n        <div class=\"col-xs-4\">\n            <% if (isEnabled(module)) {%>\n\n            <% if (module.hasSettings) {%>\n            <a href=\"#/settings/module/{{module.id}}\" class=\"btn btn-default\">\n                {{i18n('Settings')}}\n            </a>\n            <% } %>\n\n            <button class=\"btn btn-success -disable\" data-id=\"{{module.id}}\">\n                {{i18n('Enabled')}}\n            </button>\n            <% } else { %>\n            <button class=\"btn btn-default -enable\" data-id=\"{{module.id}}\">\n                {{i18n('Disabled')}}\n            </button>\n            <% } %>\n        </div>\n    </div>\n</div>\n<% }) %>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/profiles.html",
    "content": "<table class=\"table table-hover\">\n    <thead>\n        <tr>\n            <th>{{ i18n('profile.profile name') }}</th>\n            <th width=\"5%\">{{ i18n('Action') }}</th>\n        </tr>\n    </thead>\n    <tbody>\n    <% _.forEach (appProfiles, function(profile) { %>\n        <tr>\n            <td>{{ profile }}</td>\n            <td>\n                <% if (profile !== 'notes-db') { %>\n                <button class=\"btn btn-link removeProfile\" data-profile=\"{{ profile }}\">\n                    <i class=\"icon-trashed\"></i>\n                </button>\n                <% } %>\n            </td>\n        </tr>\n    <% }); %>\n        <tr>\n            <td>\n                <input type=\"text\" id=\"profileName\" class=\"form-control\" placeholder=\"{{i18n('profile.type name')}}\"/>\n            </td>\n            <td></td>\n        </tr>\n    </tbody>\n</table>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/showTemplate.html",
    "content": "<nav class=\"layout--navbar header navbar navbar-static-top -settings -right -tab-{{tab}}\">\n<div class=\"container-fluid\">\n    <div class=\"navbar-left header--left navbar-btn\">\n        <button title=\"{{i18n('Show sidebar')}}\" id=\"show--sidebar\" class=\"btn btn-default visible-xs\">\n            <span class=\"icon-left-open\"></span>\n        </button>\n    </div>\n\n    <div class=\"navbar-right header--right navbar-btn\">\n        <button type=\"button\" class=\"btn btn-default settings--cancel\">\n            {{ i18n('Close') }}\n        </button>\n        <button type=\"button\" class=\"btn btn-success settings--save\">\n            <i class=\"icon-save\"></i>\n            <span class=\"save-btn-text\">{{ i18n('Save') }}</span>\n        </button>\n    </div>\n</div>\n</nav>\n\n<div class=\"layout--body -scroll -settings\">\n    <div class=\"container-fluid -settings\">\n        <form class=\"form form-horizontal\"></form>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/templates/sync.html",
    "content": "<div class=\"form-group\">\n    <label class=\"col-sm-2 control-label\" for=\"cloudStorage\">\n        <i class=\"fa fa-cloud\"></i> {{ i18n('Cloud storage') }}\n    </label>\n    <div class=\"col-sm-10\">\n        <select class=\"form-control show-onselect\" data-option=\"dropbox\" name=\"cloudStorage\" id=\"cloudStorage\">\n            <option value=\"0\">{{ i18n('No') }}</option>\n            <option data-show=\"#group-dropboxKey\" value=\"dropbox\"<% if(models.cloudStorage === 'dropbox'){ %> selected<% } %>>Dropbox</option>\n            <option value=\"remotestorage\"<%if(models.cloudStorage === 'remotestorage'){ %> selected<% } %>>RemoteStorage</option>\n        </select>\n    </div>\n</div>\n\n<div class=\"form-group <% if (models.cloudStorage !== 'dropbox') { %> hidden<% } %>\" id=\"group-dropboxKey\">\n    <label class=\"col-sm-2 control-label\" for=\"dropboxKey\">\n        <span class=\"pull-left\">{{ i18n('Dropbox API key') }}</span>\n        <button type=\"button\" class=\"btn btn-link popover-key\" role=\"button\" data-title=\"Dropbox API Key\" data-container=\"#group-dropboxKey\">?</button>\n    </label>\n    <div class=\"col-sm-10\">\n        <input id=\"dropboxKey\" type=\"text\" name=\"dropboxKey\" class=\"form-control\" value=\"{{ models.dropboxKey }}\" placeholder=\"<% if(dropboxKeyNeed) { %>{{i18n('Required')}}!<% } else { %>{{i18n('Optional')}}<% } %>\" />\n    </div>\n    <div class=\"popover-dropbox hidden\">\n        <p>{{i18n('dropbox.api info 1')}} <a target=\"_blank\" href=\"https://www.dropbox.com/developers/apps\">Dropbox</a></p>\n        <p>{{i18n('dropbox.api info 2')}}</p>\n        <ol>\n            <li>{{i18n('dropbox.api info li 1')}}</li>\n            <li>{{i18n('dropbox.api info li 2')}}</li>\n        </ol>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/editor.js",
    "content": "/**\n * Copyright (C) 2016 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'apps/settings/show/formBehavior',\n    'text!apps/settings/show/templates/editor.html'\n], function(_, Marionette, FormBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Note editor settings\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        behaviors: {\n            FormBehavior: {\n                behaviorClass: FormBehavior\n            }\n        },\n\n        ui: {\n            indentUnit: '#indentUnit',\n            indentUnitLowWarning: '#indentUnit-low-warning'\n        },\n\n        events: {\n            'change #indentUnit' : 'checkIndentUnit'\n        },\n\n        serializeData: function() {\n            return { models: this.collection.getConfigs() };\n        },\n\n        checkIndentUnit: function() {\n            if (this.ui.indentUnit.val() < 3) {\n                this.ui.indentUnitLowWarning.show();\n            } else {\n                this.ui.indentUnitLowWarning.hide();\n            }\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/encryption.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'sjcl',\n    'apps/settings/show/formBehavior',\n    'text!apps/settings/show/templates/encryption.html'\n], function(_, Marionette, Radio, sjcl, FormBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Encryption settings.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        behaviors: {\n            FormBehavior: {\n                behaviorClass: FormBehavior.extend({\n\n                    /**\n                     * Prevent from saving an empty password.\n                     */\n                    triggerChange: function(e) {\n                        var $e = $(e.currentTarget);\n\n                        if ($e.attr('name') !== 'encryptPass' || $e.val().length) {\n                            $e.parent().parent().removeClass('has-error');\n                            FormBehavior.prototype.triggerChange.apply(this, arguments);\n                            return;\n                        }\n\n                        $e.parent().parent().addClass('has-error');\n                    }\n                })\n            }\n        },\n\n        ui: {\n            settings  : '#encryptOpt',\n            saltInput : 'input[name=encryptSalt]',\n            password  : 'input[name=encryptPass]'\n        },\n\n        events: {\n            'click #useEncryption' : 'toggleSettings',\n            'click #randomize'     : 'randomize',\n            'blur @ui.password'    : 'randomizeOnPassword'\n        },\n\n        serializeData: function() {\n            return {models  : this.collection.getConfigs()};\n        },\n\n        templateHelpers: function() {\n            return {\n                hex: function(str) {\n                    if (typeof str === 'string') {\n                        return str;\n                    }\n\n                    return sjcl.codec.hex.fromBits(str);\n                },\n\n                passwordText: function() {\n                    if (this.models.encryptPass.length !== 0) {\n                        return $.t('encryption.change password');\n                    }\n                    return $.t('encryption.provide password');\n                },\n            };\n        },\n\n        initialize: function() {\n            sjcl.random.startCollectors();\n        },\n\n        onDestroy: function() {\n            sjcl.random.stopCollectors();\n        },\n\n        /**\n         * Toggle active status of encryption settings.\n         */\n        toggleSettings: function() {\n            var state = this.ui.settings.attr('disabled') !== 'disabled';\n            this.ui.settings.attr('disabled', state);\n        },\n\n        /**\n         * Automatically generate new encryption salt every time the password is\n         * changed.\n         */\n        randomizeOnPassword: function() {\n            if (!this.ui.password.val().trim().length) {\n                return;\n            }\n\n            this.randomize();\n        },\n\n        /**\n         * Generate random salt.\n         */\n        randomize: function() {\n            var random = Radio.request('encrypt', 'randomize', 5, 0);\n            this.ui.saltInput.val(random).trigger('change');\n            return false;\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/general.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'i18next',\n    'text!locales/locales.json',\n    'apps/settings/show/formBehavior',\n    'text!apps/settings/show/templates/general.html'\n], function(_, Marionette, Radio, i18n, locales, FormBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * General settings.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        behaviors: {\n            FormBehavior: {\n                behaviorClass: FormBehavior\n            }\n        },\n\n        serializeData: function() {\n            var appLang = this.collection.get('appLang');\n            return {\n                locales    : JSON.parse(locales),\n                models     : this.collection.getConfigs(),\n                appLang    : (appLang.get('value') || i18n.language) || 'en',\n                useDefault : this.options.useDefault.toJSON()\n            };\n        },\n\n        templateHelpers: function() {\n            return {\n                isDefaultProfile: function() {\n                    var profile = Radio.request('uri', 'profile');\n                    return _.indexOf([null, 'notes-db'], profile) > -1;\n                },\n\n                isLocaleActive: function(locale) {\n                    if (this.appLang === locale ||\n                        this.appLang.search(locale) >= 0) {\n                        return ' selected';\n                    }\n                }\n            };\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/importExport.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/settings/show/templates/importExport.html'\n], function (_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Import or export settings\n     */\n    var ImportExport = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        ui: {\n            importBtn  : '#do-import',\n            import     : '#import-file',\n\n            // Export  / import buttons\n            importData : '#import-data-file',\n            exportData : '#export-data',\n        },\n\n        events: {\n            'click .btn--import'    : 'triggerClick',\n            'change @ui.import'     : 'triggerImport',\n            'change @ui.importData' : 'triggerImportData',\n            'click @ui.exportData'  : 'triggerExportData'\n        },\n\n        triggers: {\n            'click #do-export'  : 'export'\n        },\n\n        triggerImport: function(e) {\n            if (!e.target.files.length) {\n                return;\n            }\n            this.trigger('import', e.target);\n        },\n\n        triggerImportData: function(e) {\n            if (!e.target.files.length) {\n                return;\n            }\n\n            Radio.request('importExport', 'import', e.target.files);\n        },\n\n        triggerExportData: function() {\n            Radio.request('importExport', 'export');\n        },\n\n        triggerClick: function(e) {\n            var file = $(e.currentTarget).attr('data-file');\n            $(file).click();\n        }\n    });\n\n    return ImportExport;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/keybindings.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'apps/settings/show/formBehavior',\n    'text!apps/settings/show/templates/keybindings.html'\n], function(_, Marionette, FormBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Keybinding settings\n     */\n    var Shortcuts = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        behaviors: {\n            FormBehavior: {\n                behaviorClass: FormBehavior\n            }\n        },\n\n        serializeData: function() {\n            return { collection: this.collection };\n        },\n\n        templateHelpers: function() {\n            return {\n                filter: function(str) {\n                    return this.collection.filterName(str);\n                },\n\n                appShortcuts: function() {\n                    return this.collection.appShortcuts();\n                }\n            };\n        }\n    });\n\n    return Shortcuts;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/modules.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/settings/show/templates/modules.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Sync settings.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        events: {\n            'click .-enable'  : 'toggle',\n            'click .-disable' : 'toggle',\n        },\n\n        serializeData: function() {\n            return {\n                active  : this.collection.get('modules').get('value'),\n                modules : this.options.modules\n            };\n        },\n\n        templateHelpers: function() {\n            return {\n                isEnabled: function(module) {\n                    return _.indexOf(this.active, module.id) > -1;\n                }\n            };\n        },\n\n        initialize: function() {\n            this.options.modules = Radio.request('global', 'modules');\n        },\n\n        /**\n         * Enable or disable a module.\n         */\n        toggle: function(e) {\n            var id      = $(e.currentTarget).attr('data-id'),\n                configs = this.collection.get('modules').get('value'),\n                self    = this;\n\n            e.preventDefault();\n\n            if (_.indexOf(configs, id) === -1) {\n                configs.push(id);\n            }\n            else {\n                configs = _.without(configs, id);\n            }\n\n            // Save the list of modules\n            Radio.request('configs', 'save', this.collection.get('modules'), {value: configs})\n            .then(function() {\n                self.render();\n            });\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/profiles.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/settings/show/templates/profiles.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Show, add, remove profiles\n     */\n    var Profiles = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        ui: {\n            profile : '#profileName'\n        },\n\n        events: {\n            'keypress @ui.profile' : 'createProfile',\n            'click .removeProfile' : 'removeProfile'\n        },\n\n        initialize: function() {\n            this.listenTo(this.options.profiles, 'change', this.render);\n        },\n\n        removeProfile: function(e) {\n            this.trigger('remove:profile', $(e.currentTarget).attr('data-profile'));\n            return false;\n        },\n\n        createProfile: function(e) {\n            if (e.which === 13) {\n                e.preventDefault();\n                this.trigger('create:profile', this.ui.profile.val().trim());\n                this.ui.profile.val('').blur();\n            }\n        },\n\n        serializeData: function() {\n            return {\n                appProfiles: this.options.profiles.getValueJSON()\n            };\n        }\n    });\n\n    return Profiles;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/showView.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'behaviors/content',\n    'text!apps/settings/show/templates/showTemplate.html'\n], function(_, $, Marionette, Behavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Settings layout view\n     */\n    var View = Marionette.LayoutView.extend({\n        template: _.template(Tmpl),\n\n        regions: {\n            content: 'form'\n        },\n\n        behaviors: {\n            ContentBehavior: {\n                behaviorClass: Behavior\n            }\n        },\n\n        events: {\n            'click .settings--save'   : 'save',\n            'click .settings--cancel' : 'cancel'\n        },\n\n        onRender: function() {\n            this.$cancel = $('.settings--cancel');\n            this.$cancel.on('click', _.bind(this.cancel, this));\n        },\n\n        onBeforeDestroy: function() {\n            this.$cancel.off('click');\n        },\n\n        serializeData: function() {\n            return this.options;\n        },\n\n        cancel: function(e) {\n            e.preventDefault();\n            console.warn('cancel');\n            this.trigger('cancel');\n        },\n\n        save: function(e) {\n            var view = this.content.currentView;\n            e.preventDefault();\n\n            /*\n             * If the password was autofilled by a user's browser, it usually will\n             * not trigger `change` event. This will fix it.\n             */\n            if (view.ui && view.ui.password) {\n                this.content.currentView.ui.password.trigger('change');\n            }\n\n            this.trigger('save');\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/show/views/sync.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'constants',\n    'apps/settings/show/formBehavior',\n    'text!apps/settings/show/templates/sync.html'\n], function(_, Marionette, constants, FormBehavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Sync settings.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        behaviors: {\n            FormBehavior: {\n                behaviorClass: FormBehavior\n            }\n        },\n\n        serializeData: function() {\n            return {\n                models         : this.collection.getConfigs(),\n                dropboxKeyNeed : constants.DROPBOXKEYNEED\n            };\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/sidebar/app.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'app',\n    'apps/settings/sidebar/controller'\n], function(_, Marionette, Radio, App, Controller) {\n    'use strict';\n\n    var Sidebar = App.module('AppSettings.Sidebar', {startWithParent: false});\n\n    /**\n     * Initializer & finalizer\n     */\n    Sidebar.on('before:start', function(options) {\n        Sidebar.controller = new Controller(options);\n    });\n\n    Sidebar.on('before:stop', function() {\n        Sidebar.controller.destroy();\n        Sidebar.controller = null;\n    });\n\n    return Sidebar;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/sidebar/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'apps/settings/sidebar/view',\n    'apps/settings/sidebar/views/navbar'\n], function(_, Marionette, Radio, View, Navbar) {\n    'use strict';\n\n    /**\n     * Sidebar controller for settings module\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            this.options = options;\n            this.show();\n        },\n\n        onDestroy: function() {\n            this.stopListening();\n            this.view.trigger('destroy');\n            Radio.request('global', 'region:empty', 'sidebarNavbar');\n        },\n\n        show: function() {\n            this.view = new View(this.options);\n            Radio.request('global', 'region:show', 'sidebar', this.view);\n\n            // Show a different Navbar\n            Radio.request('navbar', 'stop');\n            Radio.request('global', 'region:show', 'sidebarNavbar', new Navbar());\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/sidebar/template.html",
    "content": "<div class=\"layout--body --scroll\">\n    <div class=\"list-group list-notebooks list--settings\">\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/general\">\n            <i class=\"icon-cog\"></i>\n            {{i18n('General')}}\n        </a>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/editor\">\n            <i class=\"icon-pencil\"></i>\n            {{i18n('Editor')}}\n        </a>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/encryption\">\n            <i class=\"icon-lock\"></i>\n            {{i18n('Encryption')}}\n        </a>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/keybindings\">\n            <i class=\"icon-indent\"></i>\n            {{i18n('Keybindings')}}\n        </a>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/sync\">\n            <i class=\"icon-arrows\"></i>\n            {{i18n('Sync')}}\n        </a>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/profiles\">\n            <i class=\"icon-notebook\"></i>\n            {{i18n('Profiles')}}\n        </a>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/modules\">\n            <i class=\"icon-notebook\"></i>\n            {{i18n('Modules')}}\n        </a>\n\n        <li class=\"list-group-item list--settings -disabled\">{{i18n('Other')}}</li>\n        <a class=\"list-group-item list--settings\" href=\"#{{uri}}settings/importExport\">\n            <i class=\"icon-cog\"></i>\n            {{i18n('Transfer data')}}\n        </a>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/apps/settings/sidebar/templates/navbar.html",
    "content": "<nav class=\"header navbar navbar-default navbar-static-top header--settings -left\" id=\"sidebar--nav\">\n<div class=\"container-fluid\">\n    <ul class=\"nav navbar-nav navbar-left\">\n        <li>\n            <a href=\"#\" class=\"settings--cancel\">\n                <i class=\"icon-left-open-big\"></i>\n            </a>\n        </li>\n        <li>\n            <a href=\"#/settings/general\" class=\"btn-link header--title\">\n                {{i18n('Settings')}}\n            </a>\n        </li>\n    </ul>\n</div>\n</nav>\n"
  },
  {
    "path": "app/scripts/apps/settings/sidebar/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'behaviors/sidebar',\n    'text!apps/settings/sidebar/template.html'\n], function(_, Q, Marionette, Radio, Behavior, Tmpl) {\n    'use strict';\n\n    /**\n     * Sidebar view for settings\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        behaviors: {\n            SidebarBehavior: {\n                behaviorClass: Behavior\n            }\n        },\n\n        events: {\n            'click a': 'confirm'\n        },\n\n        serializeData: function() {\n            return {\n                uri: Radio.request('uri', 'link:profile', '')\n            };\n        },\n\n        initialize: function() {\n            Radio.channel('AppSettings')\n            .reply('activate:tab', this.activateTab, this);\n        },\n\n        onDestroy: function() {\n            Radio.channel('AppSettings')\n            .stopReplying('activate:tab');\n        },\n\n        onRender: function() {\n            this.activateTab(this.options.tab);\n        },\n\n        /**\n         * Before navigating to another page, ask for confirmation.\n         */\n        confirm: function(e) {\n            e.preventDefault();\n\n            Radio.request('AppSettings', 'has:changes')\n            .then(function() {\n                Radio.request('uri', 'navigate', $(e.currentTarget).attr('href'));\n            });\n        },\n\n        activateTab: function(tab) {\n            this.$('.active').removeClass('active');\n            this.$('[href*=' + tab + ']').addClass('active');\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/apps/settings/sidebar/views/navbar.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!apps/settings/sidebar/templates/navbar.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Settings navbar\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl)\n    });\n\n    return View;\n\n});\n"
  },
  {
    "path": "app/scripts/backbone.noworker.sync.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'backbone.sync',\n    'helpers/db'\n], function(Q, _, Sync, DB) {\n    'use strict';\n\n    var Adapter = _.extend({}, Sync, {\n        promises: [],\n\n        /**\n         * Create a new worker and start listening to its events.\n         *\n         * @return function\n         */\n        sync: function() {\n            var self = this,\n                sync;\n\n            sync = function(method, model, options) {\n                return self[method](model, options);\n            };\n\n            return sync;\n        },\n\n        /**\n         * Send a message to the Webworker.\n         *\n         * @type string msg\n         * @type object data\n         */\n        _emit: function(msg, data) {\n            return DB[msg](data);\n        }\n\n    });\n\n    return Adapter;\n});\n"
  },
  {
    "path": "app/scripts/backbone.sync.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore'\n], function(Q, _) {\n    'use strict';\n\n    var Adapter = {\n        promises: {},\n\n        /**\n         * Create a new worker and start listening to its events.\n         *\n         * @return function\n         */\n        sync: function() {\n            _.bindAll(this, 'listenToWorker', 'backboneSync');\n\n            this.worker = new Worker('scripts/workers/localForage.js');\n\n            // Promise which signifies whether the worker is ready\n            this.workerPromise = Q.defer();\n\n            // Start listening to WebWorker events\n            this.worker.onmessage = this.listenToWorker;\n\n            // A function for Backbone sync\n            return this.backboneSync;\n        },\n\n        /**\n         * Resolve promises after receiving WebWorker messages.\n         *\n         * @type object data\n         */\n        listenToWorker: function(data) {\n            var msg = data.data;\n\n            switch (msg.msg) {\n\n                // Database webworker is ready\n                case 'ready':\n                    this.workerPromise.resolve();\n                    break;\n\n                // Request was fullfilled\n                case 'done':\n                    this.promises[msg.promiseId].resolve(msg.data);\n                    delete this.promises[msg.promiseId];\n                    break;\n\n                // Request failed with errors\n                case 'fail':\n                    this.promises[msg.promiseId].reject(msg.data);\n                    delete this.promises[msg.promiseId];\n                    break;\n\n                default:\n            }\n        },\n\n        /**\n         * With this method Backbone.sync will be overriden.\n         *\n         * @return promise\n         */\n        backboneSync: function(method, model, options) {\n\n            // First, make sure WebWorker is ready\n            return this.workerPromise.promise\n            .then(_.bind(function() {\n\n                // Execute the method (read, create, update)\n                return this[method](model, options);\n            }, this));\n        },\n\n        /**\n         * Find a model in the database by ID or models in a collection.\n         *\n         * @type object model\n         * @type object options\n         */\n        read: function(model, options) {\n\n            // If it has an ID, it is a model\n            if (model.id) {\n                return this.find(model, options);\n            }\n\n            return this.findAll(model, options);\n        },\n\n        create: function() {\n            return this.save.apply(this, arguments);\n        },\n\n        update: function() {\n            return this.save.apply(this, arguments);\n        },\n\n        /**\n         * Generate four random hex digits.\n         */\n        /* jshint ignore:start */\n        s4: function() {\n            return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);\n        },\n\n        /**\n         * Generate a pseudo-GUID by concatenating random hexadecimal.\n         */\n        guid: function() {\n            return (this.s4() + this.s4() + \"-\" + this.s4() + \"-\" + this.s4() + \"-\" + this.s4() + \"-\" + this.s4() + this.s4() + this.s4());\n        },\n        /* jshint ignore:end */\n\n        /**\n         * Save a model.\n         *\n         * @type object Backbone model\n         * @type object options\n         */\n        save: function(model, options) {\n\n            // Generate an ID if it wasn't provided\n            model.id = model.id || this.guid();\n            model.set(model.idAttribute, model.id);\n\n            return this._emit('save', {\n                id      : model.id,\n                data    : model.toJSON(),\n                options : {\n                    profile     : options.profile || model.profileId,\n                    storeName   : model.storeName,\n                    encryptKeys : model.encryptKeys\n                }\n            });\n        },\n\n        /**\n         * Find a model by ID.\n         *\n         * @type object Backbone model\n         * @type object options\n         */\n        find: function(model, options) {\n            return this._emit('find', {\n                id      : model.id,\n                options : {\n                    profile   : options.profile || model.profileId,\n                    storeName : model.storeName\n                }\n            })\n            .then(function(res) {\n                model.set(res);\n                return model;\n            });\n        },\n\n        /**\n         * Find models in a collection.\n         *\n         * @type object Backbone collection\n         * @type object options\n         */\n        findAll: function(collection, options) {\n            return this._emit('findAll', {\n                options: {\n                    conditions : options.conditions,\n                    storeName  : collection.storeName,\n                    profile    : options.profile || collection.profileId\n                }\n            })\n            .then(function(res) {\n                if (res && res.length) {\n                    collection.add(res);\n                }\n                return collection;\n            });\n        },\n\n        /**\n         * Send a message to the Webworker.\n         *\n         * @type string msg\n         * @type object data\n         */\n        _emit: function(msg, data) {\n\n            // Generate a unique ID for the worker's promise\n            var promiseId            = this.guid();\n            this.promises[promiseId] = Q.defer();\n\n            this.worker.postMessage({\n                msg       : msg,\n                promiseId : promiseId,\n                data      : data\n            });\n\n            return this.promises[promiseId].promise;\n        }\n\n    };\n\n    return Adapter;\n});\n"
  },
  {
    "path": "app/scripts/behaviors/content.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio'\n], function(_, Marionette, Radio) {\n    'use strict';\n\n    /**\n     * Content region behaviour\n     */\n    var Content = Marionette.Behavior.extend({\n\n        events: {\n            'swiperight'          : 'showSidebar',\n            'click #show--sidebar': 'showSidebar',\n        },\n\n        initialize: function() {\n            var channel = Radio.channel('region');\n\n            this.showContent();\n            this.listenActive();\n\n            this.listenTo(channel, 'sidebar:shown', this.listenActive);\n            this.listenTo(channel, 'content:hidden', this.showSidebar);\n            this.listenTo(channel, 'content:shown', this.showContent);\n        },\n\n        onRender: function() {\n            this.view.$el.hammer();\n        },\n\n        /**\n         * When some active element is clicked, hide the sidebar.\n         */\n        listenActive: function() {\n            if (!this.$active || !this.$active.length) {\n                this.$active = $('.list--item.active, .list--settings.active');\n                this.$active.on('click.toggle', this.showContent);\n            }\n        },\n\n        onDestroy: function() {\n            if (this.$active) {\n                this.showSidebar();\n                this.$active.off('click.toggle');\n            }\n        },\n\n        /**\n         * Show only the content\n         */\n        showContent: function() {\n            Radio.request('global', 'region:hide', 'sidebar', 'hidden-xs');\n            Radio.request('global', 'region:visible', 'content', 'hidden-xs');\n        },\n\n        /**\n         * Show only the sidebar.\n         */\n        showSidebar: function() {\n            Radio.request('global', 'region:visible', 'sidebar', 'hidden-xs');\n            Radio.request('global', 'region:hide', 'content', 'hidden-xs');\n        }\n\n    });\n\n    return Content;\n});\n"
  },
  {
    "path": "app/scripts/behaviors/modal.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'marionette'\n], function(Marionette) {\n    'use strict';\n\n    /**\n     * Typical behaviors of modal views\n     */\n    var ModalBehavior = Marionette.Behavior.extend({\n        initialize: function() {\n            this.view.on('hidden.modal', this.redirect, this);\n        },\n\n        /**\n         * Triggers redirect event when it's closed\n         */\n        redirect: function() {\n            this.view.trigger('redirect');\n        }\n    });\n\n    return ModalBehavior;\n});\n"
  },
  {
    "path": "app/scripts/behaviors/modalForm.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'behaviors/modal'\n], function(_, Marionette, ModalBehavior) {\n    'use strict';\n\n    var ModalForm = Marionette.Behavior.extend({\n        behaviors: {\n            ModalBehavior: {\n                behaviorClass: ModalBehavior\n            }\n        },\n\n        defaults: {\n            uiFocus: 'name'\n        },\n\n        events: {},\n\n        triggers: {\n            'submit form'      : 'save',\n            'click .ok'        : 'save',\n            'click .cancelBtn' : 'close'\n        },\n\n        modelEvents: {\n            'invalid': 'showErrors'\n        },\n\n        initialize: function() {\n            this.events['keyup @ui.' + this.options.uiFocus] = 'closeOnEsc';\n            this.view.on('shown.modal', this.onFormShown, this);\n        },\n\n        onFormShown: function() {\n            this.view.ui[this.options.uiFocus].focus();\n        },\n\n        /**\n         * Triggers 'close' event when user hits ESC\n         */\n        closeOnEsc: function(e) {\n            if (e.which === 27) {\n                this.view.trigger('close');\n            }\n        },\n\n        /**\n         * Shows validation errors\n         */\n        showErrors: function(model, errors) {\n            _.forEach(errors, function(err) {\n                this.view.ui[err].parent().addClass('has-error');\n\n                if (this.view.ui[err].attr('type') === 'text') {\n                    this.view.ui[err].attr('placeholder', $.t(model.storeName + '.' + err));\n                }\n            }, this);\n        }\n    });\n\n    return ModalForm;\n});\n"
  },
  {
    "path": "app/scripts/behaviors/sidebar.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'backbone.radio'\n], function(_, $, Marionette, Radio) {\n    'use strict';\n\n    /**\n     * Sidebar region behaviour\n     */\n    var Sidebar = Marionette.Behavior.extend({\n\n        defaults: {\n            events: {\n                'swipeleft'  : 'onSwipeLeft',\n                'swiperight' : 'onSwipeRight',\n            }\n        },\n\n        onRender: function() {\n            var hammer = $('#sidebar--content').hammer();\n\n            _.each(this.options.events, function(func, ev) {\n                hammer.bind(ev, this.view[func] || this[func]);\n            }, this);\n        },\n\n        /**\n         * Show sidemenu.\n         */\n        onSwipeRight: function() {\n            Radio.trigger('sidemenu', 'show');\n        },\n\n        /**\n         * Switch to content region (hide sidebar).\n         */\n        onSwipeLeft: function() {\n            Radio.trigger('region', 'content:shown');\n        },\n\n    });\n\n    return Sidebar;\n\n});\n"
  },
  {
    "path": "app/scripts/behaviors/sidemenu.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'backbone.radio',\n    'mousetrap'\n], function(_, $, Marionette, Radio, Mousetrap) {\n    'use strict';\n\n    /**\n     * This behaviour can be used for views which need to show\n     * hamburger type of menu.\n     */\n    var Sidemenu = Marionette.Behavior.extend({\n\n        ui: {\n            sidemenu: '.sidemenu'\n        },\n\n        defaults: {\n            events: {\n                'swipeleft'  : 'hideMenu',\n            }\n        },\n\n        events: {\n            'click .sidemenu--open'  : 'showMenu',\n            'click .sidemenu--close' : 'hideMenu',\n            'click .sidemenu a'      : 'hideMenu'\n        },\n\n        initialize: function() {\n            this.listenTo(Radio.channel('sidemenu'), 'show', this.showMenu);\n        },\n\n        onRender: function() {\n\n            // To avoid bugginess, add hammer events to the backdrop el too\n            var hammer  = $('.layout--backdrop').hammer(),\n                hammer2 = this.$el.hammer();\n\n            _.each(this.options.events, function(func, ev) {\n                hammer.bind(ev,  _.bind(this.view[func] || this[func], this));\n                hammer2.bind(ev, _.bind(this.view[func] || this[func], this));\n            }, this);\n        },\n\n        onShow: function() {\n            _.bindAll(this, 'hideMenu');\n\n            this.$backdrop = $('#layout--backdrop');\n\n            // Hide when 'Esc' was pressed\n            Mousetrap.bind('esc', this.hideMenu);\n        },\n\n        hideMenu: function() {\n            this.ui.sidemenu.removeClass('-show');\n            this.$backdrop.removeClass('-show');\n        },\n\n        showMenu: function() {\n            var self = this;\n\n            // Show the menu and backdrop\n            this.ui.sidemenu.addClass('-show');\n            this.$backdrop.addClass('-show');\n\n            this.ui.sidemenu.scrollTop(0);\n\n            // Hide the menu when a user clicks on the backdrop area\n            this.$backdrop.on('click', function() {\n                self.$backdrop.off('click');\n                self.hideMenu();\n            });\n\n            return false;\n        }\n\n    });\n\n    return Sidemenu;\n});\n"
  },
  {
    "path": "app/scripts/classes/encryption.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'classes/sjcl.worker',\n    'sjcl'\n], function(Q, _, Marionette, Radio, Sjcl, sjcl) {\n    'use strict';\n\n    /**\n     * Encryption class.\n     *\n     * Replies to requests on channel `encrypt`:\n     * 1. `sha256`          - generates and returns sha256 hash of provided string.\n     * 2. `randomize`       - generates and returns random data.\n     * 3. `change:configs`   - changes encryption configs.\n     * 4. `delete:secureKey` - delete PBKDF2 from session storage.\n     *\n     * 3. `check:auth`      - checks whether a user is authorized.\n     * 4. `check:password`  - validate provided password.\n     * 5. `save:secureKey`  - compute PBKDF2 and save it to session storage.\n     *\n     * 6. `encrypt`         - encrypt a string\n     * 7. `decrypt`         - decrypt a string\n     * 8. `encrypt:model`   - encrypt a Backbone model\n     * 9. `decrypt:model`   - decrypt a Backbone model\n     * 10. `encrypt:models` - encrypt a Backbone collection\n     * 11. `decrypt:models` - decrypt a Backbone collection\n     */\n    var Encrypt = Marionette.Object.extend({\n\n        initialize: function() {\n\n            // Get configs\n            this.configs = Radio.request('configs', 'get:object');\n            this.keys    = {};\n\n            this.sjcl = new Sjcl(this.configs);\n\n            // Pass requests directly to Sjcl class\n            Radio.reply('encrypt', {\n                'sha256'           : this.sjcl.sha256,\n            }, this.sjcl);\n\n            // Replies\n            Radio.reply('encrypt', {\n                'randomize'        : this.randomize,\n                'change:configs'   : this.changeConfigs,\n\n                // Check auth/password\n                'check:auth'       : this.checkAuth,\n                'check:password'   : this.checkPassword,\n                'save:secureKey'   : this.saveSecureKey,\n                'delete:secureKey' : this.deleteSecureKey,\n\n                // Encrypt/decrypt some string\n                'encrypt'          : this.encrypt,\n                'decrypt'          : this.decrypt,\n\n                // Encrypt/decrypt a model\n                'encrypt:model'    : this.encryptModel,\n                'decrypt:model'    : this.decryptModel,\n\n                // Encrypt/decrypt a collection of models\n                'encrypt:models'   : this.encryptModels,\n                'decrypt:models'   : this.decryptModels\n            }, this);\n        },\n\n        /**\n         * Generate random words.\n         *\n         * @return string\n         */\n        randomize: function(number, paranoia, noHex) {\n            if (noHex) {\n                return sjcl.random.randomWords(number, paranoia);\n            }\n\n            return sjcl.codec.hex.fromBits(\n                sjcl.random.randomWords(number, paranoia)\n            );\n        },\n\n        /**\n         * Change encryption configs. It is useful when re-encrypting data.\n         */\n        changeConfigs: function(configs) {\n            configs      = configs || Radio.request('configs', 'get:object');\n            this.configs = _.extend(this.configs, configs);\n        },\n\n        /**\n         * Check whether a user is already authorized\n         *\n         * @return bool\n         */\n        checkAuth: function() {\n            /**\n             * If encryption backup is not empty, it means a user changed\n             * encryption settings.\n             */\n            if (!_.isEmpty(this.configs.encryptBackup)) {\n                Radio.trigger('encrypt', 'changed');\n                return {isChanged: true};\n            }\n\n            // Encryption is disabled\n            if (!Number(this.configs.encrypt) || this.configs.encryptPass === '') {\n                return true;\n            }\n\n            return !_.isEmpty(this.keys) || this._getSession() !== null;\n        },\n\n        /**\n         * Check the password with the password in the database which is saved\n         * in there in sha256 hash format. Note, just the password is not used\n         * for encrypting/decrypting data. We use instead PBKDF2.\n         *\n         * @return promise\n         */\n        checkPassword: function(password) {\n            var pwd = this.configs.encryptPass;\n\n            return new Q(this.sjcl.sha256(password))\n            .then(function(hash) {\n                return hash.toString() === pwd.toString();\n            });\n        },\n\n        /**\n         * Generate PBKDF2 and save it. It will be used to encrypt/decrypt data.\n         *\n         * @return promise\n         */\n        saveSecureKey: function(password) {\n            var self  = this;\n\n            return new Q(this.sjcl.deriveKey({\n                configs : this.configs,\n                password: password\n            }))\n            .then(function(keys) {\n                self.keys.key    = keys.key;\n                self.keys.hexKey = keys.hexKey;\n                self._saveSession();\n            });\n        },\n\n        /**\n         * Delete current PBKDF2.\n         */\n        deleteSecureKey: function() {\n            this.keys = {};\n\n            if (window.sessionStorage) {\n                window.sessionStorage.removeItem(this._getSessionKey());\n            }\n        },\n\n        /**\n         * Encrypt data.\n         *\n         * @return promise\n         */\n        encrypt: function(str) {\n            return new Q(this.sjcl.encrypt({\n                configs : this.configs,\n                string  : str,\n                keys    : this.keys,\n\n                // Random initialization vector every time\n                iv      : sjcl.random.randomWords(4, 0),\n            }));\n        },\n\n        /**\n         * Decrypt data.\n         *\n         * @return promise\n         */\n        decrypt: function(str) {\n            return new Q(this.sjcl.decrypt({\n                configs : this.configs,\n                string  : str,\n                keys    : this.keys,\n            }));\n        },\n\n        /**\n         * Encrypt a model.\n         *\n         * @return promise\n         */\n        encryptModel: function(model) {\n            var data = _.pick(model.attributes, model.encryptKeys);\n\n            return this.encrypt(data)\n            .then(function(encrypted) {\n                model.set('encryptedData', encrypted);\n                return model;\n            });\n        },\n\n        /**\n         * Decrypt a model.\n         *\n         * @return promise\n         */\n        decryptModel: function(model) {\n            if (model.attributes.encryptedData) {\n                return this._decryptModel(model);\n            }\n\n            return this._decryptModelKeys(model);\n        },\n\n        /**\n         * Encrypt a collection.\n         *\n         * @return promise\n         */\n        encryptModels: function(collection) {\n\n            // The collection is empty or PBKDF2 wasn't generated\n            if (!collection.length || !Number(this.configs.encrypt) ||\n                !this.keys.key) {\n                return new Q();\n            }\n\n            var promises = [],\n                self     = this;\n\n            Radio.trigger('encrypt', 'encrypting:models', collection);\n\n            collection.each(function(model) {\n                promises.push(function() {\n                    return new Q(self.encryptModel(model));\n                });\n            }, this);\n\n            return _.reduce(promises, Q.when, new Q())\n            .fail(function(e) {\n                console.error('EncryptModels Error:', e);\n            });\n        },\n\n        /**\n         * Decrypt a collection.\n         *\n         * @return promise\n         */\n        decryptModels: function(collection) {\n\n            // The collection is empty or encryption is disabled\n            if (!collection.length || !Number(this.configs.encrypt)) {\n                return new Q();\n            }\n\n            // PBKDF2 wasn't generated\n            if (!this.keys.key) {\n                Radio.trigger('encrypt', 'decrypt:error', 'PBKDF2 is empty');\n                return new Q();\n            }\n\n            var promises = [],\n                self = this;\n\n            Radio.trigger('encrypt', 'decrypting:models', collection);\n\n            collection.each(function(model) {\n                promises.push(function() {\n                    return new Q(self.decryptModel(model));\n                });\n            }, this);\n\n            return _.reduce(promises, Q.when, new Q())\n            .fail(function(e) {\n                console.error('DecryptModels Error:', e);\n            });\n        },\n\n        /**\n         * Decrypt a model by getting data from \"encryptedData\" attribute.\n         *\n         * @return promise\n         */\n        _decryptModel: function(model) {\n            return new Q(this.sjcl.decrypt({\n                configs : this.configs,\n                string  : model.get('encryptedData'),\n                keys    : this.keys,\n            }))\n            .then(function(data) {\n                _.each(JSON.parse(data), function(val, key) {\n                    model.set(key, val);\n                });\n\n                Radio.trigger('encrypt', 'decrypted:model', model);\n                return model;\n            });\n        },\n\n        /**\n         * Deprecated decryption.\n         *\n         * @return promise\n         */\n        _decryptModelKeys: function(model) {\n            var promises = [],\n                self     = this;\n\n            _.each(model.encryptKeys, function(key) {\n                promises.push(\n                    new Q(self.sjcl.decryptLegacy({\n                        configs : self.configs,\n                        string  : model.get(key),\n                        keys    : this.keys\n                    }))\n                    .then(function(data) {\n                        model.set(key, data);\n                    })\n                );\n            }, this);\n\n            return Q.all(promises)\n            .then(function() {\n                Radio.trigger('encrypt', 'decrypted:model', model);\n                return model;\n            });\n        },\n\n        /**\n         * Save PBKDF2 to sessionStorage. That way the user will not have to\n         * type their passwords every time.\n         */\n        _saveSession: function() {\n            if (!window.sessionStorage || !this.keys) {\n                return;\n            }\n\n            window.sessionStorage.setItem(\n                this._getSessionKey(),\n                JSON.stringify(this.keys)\n            );\n        },\n\n        /**\n         * Get PBKDF2 from sessionStorage.\n         *\n         * @return [object|null]\n         */\n        _getSession: function() {\n            if (!window.sessionStorage) {\n                return null;\n            }\n\n            var keys  = window.sessionStorage.getItem(this._getSessionKey());\n            try {\n                keys = JSON.parse(keys);\n                this.keys = keys || this.keys;\n            } catch (e) {\n                keys = null;\n            }\n\n            return keys;\n        },\n\n        /**\n         * Return session storage key which will be used to save PBKDF2.\n         *\n         * @return string\n         */\n        _getSessionKey: function() {\n            var profile = Radio.request('uri', 'profile') || 'default';\n            profile = (Number(this.configs.useDefaultConfigs) ? 'default' : profile);\n            return 'secureKey.' + profile;\n        }\n\n    });\n\n    // Initialize\n    Radio.request('init', 'add', 'app:before', function() {\n        new Encrypt();\n    });\n\n    return Encrypt;\n});\n"
  },
  {
    "path": "app/scripts/classes/sjcl.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'sjcl'\n], function(_, Q, sjcl) {\n    'use strict';\n\n    /**\n     * Sjcl adapter\n     */\n    function Sjcl() {\n    }\n\n    _.extend(Sjcl.prototype, {\n        keys: [],\n\n        /**\n         * Convert a string to a HEX.\n         *\n         * @type string str\n         * @return string\n         */\n        hex: function(str) {\n            return sjcl.codec.hex.fromBits(str);\n        },\n\n        /**\n         * Replace every letter in a HEX string with uppercased letters.\n         *\n         * @type string str\n         * @return string\n         */\n        toUpperCase: function(str) {\n            return str.toUpperCase().replace(/ /g,'').replace(/(.{8})/g, '$1 ').replace(/ $/, '');\n        },\n\n        /**\n         * Hash a string.\n         *\n         * @type string str\n         * @return promise\n         */\n        sha256: function(str) {\n\n            // Don't try to compute SHA256 if a string is empty\n            if (!str || !str.length) {\n                return new Q(str);\n            }\n\n            return new Q(sjcl.hash.sha256.hash(str));\n        },\n\n        /**\n         * Return encryption configs.\n         *\n         * @type object configs\n         * @return object\n         */\n        getConfigs: function(configs) {\n            return {\n                mode   : 'ccm',\n                iter   : Number(configs.encryptIter),\n                ts     : Number(configs.encryptTag),\n                ks     : Number(configs.encryptKeySize),\n                salt   : configs.encryptSalt,\n                v      : 1,\n                adata  : '',\n                cipher : 'aes',\n            };\n        },\n\n        /**\n         * Compute PBKDF2 which will be used to encrypt/decrypt data\n         *\n         * @type object data\n         * @return object\n         */\n        deriveKey: function(data) {\n\n            // If encryption is disabled, don't compute PBKDF2\n            if (!Number(data.configs.encrypt)) {\n                return {};\n            }\n\n            var pbkdf2 = {},\n                password;\n\n            pbkdf2.iter = Number(data.configs.encryptIter);\n            pbkdf2.salt = data.configs.encryptSalt;\n\n            pbkdf2   = sjcl.misc.cachedPbkdf2(data.password, pbkdf2);\n            password = pbkdf2.key.slice(0, Number(data.configs.encryptKeySize) / 32);\n\n            return (this.keys = {\n                key    : password,\n                hexKey : sjcl.codec.hex.fromBits(password)\n            });\n        },\n\n        /**\n         * Returns either locally cached keys or the keys from the provided object.\n         *\n         * @type object data\n         * @return object\n         */\n        getKeys: function(data) {\n            return data.keys || this.keys;\n        },\n\n        /**\n         * Encrypt data.\n         *\n         * @type object data\n         * @return string\n         */\n        encrypt: function(data) {\n\n            // Encryption is disabled\n            if (!data.string || !Number(data.configs.encrypt)) {\n                return data.string;\n            }\n\n            var p = this.getConfigs(data.configs);\n\n            // Random initialization vector every time\n            p.iv  = data.iv;\n\n            if (typeof data.string !== 'string') {\n                data.string = JSON.stringify(data.string);\n            }\n\n            data.string = sjcl.encrypt(this.getKeys(data).key, data.string, p);\n            return data.string;\n        },\n\n        /**\n         * Decrypt data.\n         *\n         * @type object data\n         * @return [string|object]\n         */\n        decrypt: function(data) {\n\n            // Encryption is disabled\n            if ((!data.string || !data.string.length) ||\n                !Number(data.configs.encrypt)) {\n                return data.string;\n            }\n\n            try {\n                data.string = sjcl.decrypt(this.getKeys(data).key, data.string);\n            } catch (e) {\n                return this.triggerDecryptError(e, data.string);\n            }\n\n            return data.string;\n        },\n\n        /**\n         * Deprecated decryption.\n         *\n         * @type object data\n         * @return string\n         */\n        decryptLegacy: function(data) {\n\n            // Encryption is disabled\n            if ((!data.string || !data.string.length) ||\n                !Number(data.configs.encrypt)) {\n                return data.string;\n            }\n\n            var keys = this.getKeys(data),\n                key  = keys.key.toString(),\n                str,\n                object;\n\n            try {\n                str    = _.unescape(data.string);\n                object = JSON.parse(data.string);\n            } catch (e) {\n                return data.string;\n            }\n\n            // We need more encryption data\n            if (_.size(object) < 9) {\n                key = this.toUpperCase(keys.hexKey);\n                str = _.extend(this.getConfigs(data.configs), object);\n                str = JSON.stringify(str);\n            }\n\n            try {\n                str = sjcl.decrypt(key, str);\n            } catch (e) {\n                return this.triggerDecryptError(e, str);\n            }\n\n            return str;\n        },\n\n        triggerDecryptError: function(e, str) {\n\n            // The text wasn't encrypted\n            if (e.message.search('json decode') > -1 &&\n               !/\"ct\":\"([^\"]*)\"./.test(str)) {\n                return str;\n            }\n\n            console.error('Decryption error', e, str);\n            throw new Error(e.message);\n        }\n\n    });\n\n    return Sjcl;\n});\n"
  },
  {
    "path": "app/scripts/classes/sjcl.worker.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'classes/sjcl',\n    'backbone.radio'\n], function(_, Q, Sjcl, Radio) {\n    'use strict';\n\n    // Use Sjcl without WebWorkers\n    if (!Radio.request('global', 'use:webworkers')) {\n        return Sjcl;\n    }\n\n    function SjclWorker() {\n        var self    = this;\n        this.worker = new Worker('scripts/workers/sjcl.js');\n\n        // Promise which signifies whether the worker is ready\n        this.workerPromise = Q.defer();\n\n        this.worker.onmessage = function(data) {\n            var msg = data.data;\n\n            switch (msg.msg) {\n\n                // Webworker is ready\n                case 'ready':\n                    self.workerPromise.resolve();\n                    break;\n\n                // Request was fullfilled\n                case 'done':\n                    self.promises[msg.promiseId].resolve(msg.data);\n                    delete self.promises[msg.promiseId];\n                    break;\n\n                // Request failed with errors\n                case 'fail':\n                    self.promises[msg.promiseId].reject(msg.data);\n                    delete self.promises[msg.promiseId];\n                    break;\n\n                default:\n            }\n        };\n    }\n\n    _.extend(SjclWorker.prototype, {\n        promises: [],\n\n        /**\n         * Send a message to the Webworker.\n         *\n         * @type string msg\n         * @type object data\n         */\n        _emit: function(msg, data) {\n            var self = this;\n\n            // Worker is ready\n            if (!this.workerPromise.promise.isPending()) {\n                return this._send(msg, data);\n            }\n\n            return this.workerPromise.promise\n            .then(function() {\n                return self._send(msg, data);\n            });\n        },\n\n        _send: function(msg, data) {\n\n            // Generate a unique ID for the worker's promise\n            var promiseId = ((1 + Math.random()) * 0x10000);\n            this.promises[promiseId] = Q.defer();\n\n            this.worker.postMessage({\n                msg       : msg,\n                promiseId : promiseId,\n                data      : data\n            });\n\n            return this.promises[promiseId].promise;\n        }\n    });\n\n    /**\n     * Automatically create \"proxy\" functions. Copy function names from\n     * classes/sjcl.js.\n     */\n    _.each(_.keys(Sjcl.prototype), function(key) {\n        if (_.isFunction(Sjcl.prototype[key])) {\n            SjclWorker.prototype[key] = function(data) {\n                return this._emit(key, data);\n            };\n        }\n    });\n\n    return SjclWorker;\n\n});\n"
  },
  {
    "path": "app/scripts/collections/configs.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'q',\n    'backbone',\n    'models/config',\n], function(_, Q, Backbone, Config) {\n    'use strict';\n\n    var Configs = Backbone.Collection.extend({\n        model : Config,\n\n        profileId : 'notes-db',\n        storeName : 'configs',\n\n        configNames: {\n            'appVersion'         : '0.5.0',\n            'firstStart'         : '1',\n            'appProfiles'        : JSON.stringify(['notes-db']),\n            'appLang'            : '',\n            'cloudStorage'       : '0',\n            'dropboxKey'         : '',\n            'dropboxAccessToken' : '',\n            'pagination'         : '10',\n            'sortnotes'          : 'created',\n            'sortnotebooks'      : 'name',\n            'navbarNotebooksMax' : '5',\n            'useDefaultConfigs'  : '1',\n\n            // Editor settings\n            'editMode'           : 'preview',\n            'indentUnit'         : '4',\n\n            // Encryption settings\n            'encrypt'            : '0',\n            'encryptPass'        : '',\n            'encryptSalt'        : '',\n            'encryptIter'        : '10000',\n            'encryptTag'         : '128',\n            'encryptKeySize'     : '256',\n            'encryptBackup'      : {},\n\n            // Keybindings\n            'navigateTop'        : 'k',\n            'navigateBottom'     : 'j',\n            'jumpInbox'          : 'g i',\n            'jumpNotebook'       : 'g n',\n            'jumpFavorite'       : 'g f',\n            'jumpRemoved'        : 'g t',\n            'jumpOpenTasks'      : 'g o',\n            'actionsEdit'        : 'e',\n            'actionsOpen'        : 'o',\n            'actionsRemove'      : 'shift+3',\n            'actionsRotateStar'  : 's',\n            'appCreateNote'      : 'c',\n            'appSearch'          : '/',\n            'appKeyboardHelp'    : '?',\n            'textEditor'         : 'default',\n\n            'modules'            : []\n        },\n\n        /**\n         * Check for new configs.\n         */\n        hasNewConfigs: function() {\n            return (\n                _.keys(this.configNames).length !== this.length\n            );\n        },\n\n        /**\n         * Switch to another profile\n         */\n        changeDB: function(id) {\n            this.profileId = id;\n            this.model.prototype.profileId = this.profileId;\n        },\n\n        /**\n         * Migrate configs from localStorage.\n         */\n        migrateFromLocal: function() {\n            var val;\n            _.each(this.configNames, function(value, name) {\n                val = localStorage.getItem('vimarkable.configs-' + name);\n                if (val) {\n                    this.configNames[name] = JSON.parse(val).value;\n                }\n            }, this);\n        },\n\n        /**\n         * Create default configs.\n         */\n        createDefault: function() {\n            var self     = this,\n                promises = [];\n\n            _.each(this.configNames, function(value, name) {\n                // Check whether a model already exists\n                if (typeof this.get(name) !== 'undefined') {\n                    return;\n                }\n\n                promises.push(\n                    new Q(new self.model().save({name: name, value: value}))\n                );\n            }, this);\n\n            return Q.all(promises);\n        },\n\n        /**\n         * Transform the collection to key = value structure.\n         */\n        getConfigs: function() {\n            var data = {};\n\n            _.forEach(this.models, function(model) {\n                data[model.get('name')] = model.get('value');\n            });\n\n            data.appProfiles = JSON.parse(data.appProfiles || this.configNames.appProfiles);\n\n            return data;\n        },\n\n        /**\n         * Return a model with a default value\n         */\n        getDefault: function(name) {\n            var config = this.configNames[name];\n            return new this.model({name: name, value: config});\n        },\n\n        resetFromJSON: function(jsonSettings) {\n            var newConfs = [];\n            _.forEach(jsonSettings, function(val, key) {\n                newConfs.push({name: key, value: val});\n            });\n            this.reset(newConfs);\n        },\n\n        shortcuts: function() {\n            var pattern = /(actions|navigate|jump|appCreateNote|appSearch|appKeyboardHelp)/;\n            return this.filter(function(m) {\n                pattern.lastIndex = 0;\n                return pattern.test(m.get('name'));\n            });\n        },\n\n        /**\n         * Filter\n         */\n        appShortcuts: function() {\n            var names = ['appCreateNote', 'appSearch', 'appKeyboardHelp'];\n            return this.filter(function(m) {\n                return _.contains(names, m.get('name'));\n            });\n        },\n\n        filterName: function(str) {\n            return this.filter(function(m) {\n                return m.get('name').search(str) >= 0;\n            });\n        }\n\n    });\n\n    return Configs;\n\n});\n"
  },
  {
    "path": "app/scripts/collections/files.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone',\n    'models/file'\n], function(_, Backbone, File) {\n    'use strict';\n\n    /**\n     * Files collection\n     */\n    var Files = Backbone.Collection.extend({\n        model: File,\n\n        profileId : 'notes-db',\n        storeName : 'files',\n\n    });\n\n    return Files;\n});\n"
  },
  {
    "path": "app/scripts/collections/modules/configs.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'sjcl',\n    'collections/modules/module',\n    'collections/configs'\n], function(_, Q, Marionette, Radio, sjcl, ModuleObject, Configs) {\n    'use strict';\n\n    /**\n     * Collection module for Configs.\n     *\n     * Apart from the replies and events in collections/modules/module.js,\n     * it also has additional replies and events.\n     *\n     * Triggers events on channel `configs`:\n     * 1. event: `collection:empty` - if the collection is empty.\n     * 2. event: `removed:profile`  - when some profile is removed.\n     * 3. event: `changed`          - after configs are changed\n     *\n     * Replies on channel `configs` to:\n     * 1. request: `get:config`     - returns a config.\n     * 2. request: `get:object`     - returns configs in key=value format.\n     * 3. request: `get:profiles`   - returns list of profiles\n     * 4. request: `reset:encrypt`  - resets encryption configs backup.\n     * 5. request: `save:objects`   - save several configs at once\n     * 6. request: `create:profile` - create a new profile\n     * 7. request: `remove:profile` - remove a profile\n     * 8. request: `save:object`\n     */\n    var Collection = ModuleObject.extend({\n        Collection: Configs,\n\n        reply: function() {\n            return {\n                'save:object'    : this.saveObject,\n                'save:objects'   : this.saveObjects,\n                'create:profile' : this.createProfile,\n                'remove:profile' : this.removeProfile,\n                'get:config'     : this.getConfig,\n                'get:object'     : this.getObject,\n                'get:profiles'   : this.getProfiles,\n                'reset:encrypt'  : this.resetEncrypt\n            };\n        },\n\n        encryptionKeys: [\n            'encrypt'    , 'encryptPass', 'encryptSalt'  ,\n            'encryptIter', 'encryptTag' , 'encryptKeySize'\n        ],\n\n        /**\n         * Reset encryptBackup\n         */\n        resetEncrypt: function() {\n            var model = this.collection.get('encryptBackup');\n            return this.saveModel(model, {value: {}});\n        },\n\n        /**\n         * Create a new profile\n         *\n         * @type object Backbone.model - appProfiles model\n         */\n        createProfile: function(model, name) {\n            return model.createProfile(name);\n        },\n\n        /**\n         * Remove a profile\n         */\n        removeProfile: function(model, name) {\n            return new Q(model.removeProfile(name, model))\n            .then(function() {\n                Radio.trigger('configs', 'removed:profile', name);\n            });\n        },\n\n        /**\n         * Save a config.\n         * @type object Backbone model\n         * @type object new value\n         */\n        saveModel: function(model, data) {\n            var saveFunc = _.bind(ModuleObject.prototype.saveModel, this);\n\n            if (!model.isPassword(data)) {\n                return saveFunc(model, data);\n            }\n\n            // Always save passwords as sha256\n            return new Q(Radio.request('encrypt', 'sha256', data.value))\n            .then(function(result) {\n                data.value = result;\n                return saveFunc(model, data);\n            });\n        },\n\n        /**\n         * Update several configs at once\n         * @type array array of configs\n         * @type object Backbone model\n         */\n        saveObjects: function(objects, useDefault) {\n            var promises = [],\n                self  = this;\n\n            // Backup current encryption configs\n            if (objects.useDefaultConfigs) {\n                promises.push(\n                    this._backupEncrypt(useDefault.profileId)\n                );\n            }\n\n            // Convert configs to a key = value object.\n            objects = (_.isArray(objects) ? _.indexBy(objects, 'name') : objects);\n\n            // Backup encryption configs\n            this._backupEncryption(objects);\n\n            // return;\n            _.forEach(objects, function(object) {\n                promises.push(\n                    new Q(self.saveObject(object, useDefault, {profile: useDefault.profileId}))\n                );\n            }, this);\n\n            return Q.all(promises)\n            .then(function() {\n                Radio.trigger('configs', 'changed', objects);\n            });\n        },\n\n        /**\n         * Saves an object to the database.\n         * @type object\n         * @type object Backbone model\n         */\n        saveObject: function(object, useDefault, options) {\n            var self = this;\n\n            return this.getModel(_.extend({}, options || {}, {name: object.name}))\n            .then(function(model) {\n                if (!model) {\n                    return;\n                }\n\n                if (object.name === 'useDefaultConfigs') {\n                    model = useDefault;\n                }\n\n                return self.saveModel(model, object);\n            });\n        },\n\n        /**\n         * Return the value of a specific config\n         */\n        getConfig: function(name, defaultValue) {\n            var config = this.getObject()[name];\n            return !_.isUndefined(config) ? config : defaultValue;\n        },\n\n        /**\n         * Return configs as key=value\n         */\n        getObject: function() {\n            return this.collection.getConfigs();\n        },\n\n        /**\n         * Find a model by ID.\n         * @type object options\n         */\n        getModel: function(options) {\n            var getFunc = _.bind(ModuleObject.prototype.getModel, this),\n                self    = this;\n\n            options     = (typeof options === 'string' ? {name: options} : options);\n\n            return getFunc(options)\n            .then(function(model) {\n                if (model) {\n                    return model;\n                }\n\n                // If a model doesn't exist, return default values\n                var collection  = new (self.changeDatabase(options))();\n                return collection.getDefault(options.name);\n            });\n        },\n\n        /**\n         * Return all configs.\n         * @type object options\n         */\n        getAll: function(options) {\n            if (this.collection && this.collection.length) {\n                return new Q(this.collection);\n            }\n\n            var self = this,\n                profile = options.profile || this.defaultDB,\n                getFunc = _.bind(ModuleObject.prototype.getAll, this);\n\n            /**\n             * Before fetching configs collection, find out whether\n             * we should use configs from the default profile.\n             */\n            return this.useDefaultConfigs(options.profile)\n            .then(function(profile) {\n                options.profile = profile;\n                return getFunc(options);\n            })\n            .then(function() {\n                return self._checkBackup(profile);\n            })\n            .then(function() {\n                return self._createDefault(options);\n            })\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        /**\n         * Return null if configs from the default profile should be used.\n         *\n         * @type string profile\n         */\n        useDefaultConfigs: function(profile) {\n            return this.getModel({name: 'useDefaultConfigs', profile: profile})\n            .then(function(model) {\n                return (!model || Number(model.get('value')) ? null : profile);\n            });\n        },\n\n        /**\n         * Returns profiles which use configs from default profile or\n         * if current profile doesn't use configs from default profile,\n         * returns only current profile.\n         */\n        getProfiles: function() {\n            var current = this.collection.profileId,\n                backup  = this.collection.get('encryptBackup');\n\n            // If it is not the default profile, return only current profile\n            if (current !== this.defaultDB || backup.profileId !== this.defaultDB) {\n                return new Q([backup.profileId]);\n            }\n\n            /*\n             * If it is the default profile, return all profiles which\n             * use configs from default profile.\n             */\n            return this.getModel({name: 'appProfiles'})\n            .then(_.bind(this._getDefaultProfiles, this));\n        },\n\n        /**\n         * Return profiles which use configs from default profile.\n         * @type object Backbone model\n         */\n        _getDefaultProfiles: function(model) {\n            var profiles = model.getValueJSON(),\n                self     = this,\n                promises = [];\n\n            // Fetch `useDefaultConfigs` model of every profile\n            _.each(profiles, function(profile) {\n                promises.push(\n                    self.getModel({\n                        name    : 'useDefaultConfigs',\n                        profile : profile\n                    })\n                );\n            });\n\n            return Q.all(promises)\n            .then(function(profiles) {\n                profiles = _.filter(profiles, function(profile) {\n                    return (\n                        Number(profile.get('value')) === 1 ||\n                        profile.profileId === self.defaultDB\n                    );\n                });\n\n                return _.pluck(profiles, 'profileId');\n            });\n        },\n\n        /**\n         * Check encryption backup\n         */\n        _checkBackup: function(profile) {\n            var self = this;\n\n            return this.getModel({name: 'encryptBackup'})\n            .then(function(backup) {\n                /**\n                 * If it is the default profile or default backup is not empty,\n                 * do nothing.\n                 */\n                if (profile === self.defaultDB ||\n                   (!backup || !_.isEmpty(backup.get('value')))) {\n                    return;\n                }\n\n                // Fetch current profile's encryption backup configs\n                return self.getModel({\n                    name    : 'encryptBackup',\n                    profile : profile\n                })\n                .then(function(model) {\n                    // If profile's backup is not empty, change backup model\n                    if (!_.isEmpty(model.get('value'))) {\n                        backup.set(model.toJSON());\n                        backup.changeDB(profile);\n                    }\n                    return model;\n                });\n            });\n        },\n\n        /**\n         * If collection is empty, create configs with default values.\n         * @type object options\n         */\n        _createDefault: function(options) {\n            if (!this.collection.hasNewConfigs()) {\n                return new Q(this.collection);\n            }\n\n            var self = this;\n\n            // Trigger an event if the collection is empty\n            if (this.collection.length === 0) {\n                this.vent.trigger('collection:empty');\n            }\n\n            // If the collection is empty, create default set of configs.\n            return new Q(this.collection.migrateFromLocal())\n            .then(_.bind(this.collection.createDefault, this.collection))\n            .then(function() {\n                var func = _.bind(ModuleObject.prototype.getAll, self);\n                self.collection.trigger('reset:all');\n                return func(options);\n            })\n            .thenResolve(self.collection);\n        },\n\n        /**\n         * Check whether there are any changes in encryption configs.\n         */\n        _getEncryption: function(collection) {\n\n            // Don't create a backup if encryption is not used in both new and old configs\n            if ((!collection.encrypt || !Number(collection.encrypt.value)) &&\n                !Number(this.getConfig('encrypt'))) {\n\n                return [];\n            }\n\n            // Disable encryption if password is empty in both configs\n            if ((!collection.encryptPass || !collection.encryptPass.value.length) &&\n                !this.getConfig('encryptPass').length) {\n\n                collection.encrypt = {value : '0', name: 'encrypt'};\n                return [];\n            }\n\n            return _.filter(collection, function(value) {\n\n                // Compare values\n                if (typeof value === 'object') {\n                    return (\n                        _.indexOf(this.encryptionKeys, value.name) > -1 &&\n                        this.getConfig(value.name) !== value.value &&\n                        this._checkPassChanged(value)\n                    );\n                }\n\n                return (_.indexOf(this.encryptionKeys, value) > -1);\n            }, this);\n        },\n\n        _checkPassChanged: function(object) {\n            if (object.name !== 'encryptPass') {\n                return true;\n            }\n\n            var pass = this.getConfig('encryptPass');\n            pass     = pass ? pass.toString() : pass;\n\n            // Password salt was saved\n            if (pass === object.value) {\n                return false;\n            }\n\n            // Additional check to make sure it's not the same password\n            var salt = sjcl.hash.sha256.hash(object.value);\n            return (salt.toString() !== pass);\n        },\n\n        /**\n         * Backup current encryption configs to current profile.\n         */\n        _backupEncrypt: function(profile) {\n            var encrypt = _.pluck(this.collection.filter(function(model) {\n                    return (_.indexOf(this.encryptionKeys, model.get('name')) > -1);\n                }, this), 'id'),\n                model   = this.collection.get('encryptBackup');\n\n            model.changeDB(profile);\n            return new Q(this.saveModel(model, {\n                value: _.pick(this.collection.getConfigs(), encrypt)\n            }));\n        },\n\n        /**\n         * Backup encryption configs if there are any changes in them.\n         */\n        _backupEncryption: function(objects) {\n            var changed = _.pluck(this._getEncryption(objects), 'name');\n\n            /*\n             * Don't create encryption backup if:\n             * Encryption configs have not changed\n             * or\n             * there is already a backup.\n             */\n            if (_.isEmpty(changed) ||\n                _.keys(this.getConfig('encryptBackup')).length) {\n                return;\n            }\n\n            // Backup configs that are changed\n            var configs = this.getObject();\n            changed     = _.pick(configs, changed);\n\n            // Password hasn't changed\n            if (objects.encryptPass &&\n                configs.encryptPass.toString() === objects.encryptPass.value.toString()) {\n                delete changed.encryptPass;\n            }\n\n            if (!_.keys(changed).length) {\n                return;\n            }\n\n            /**\n             * Extend old backup from new.\n             * That way we ensure that only the oldest configs will be saved.\n             */\n            objects.encryptBackup = {\n                name  : 'encryptBackup',\n                value : _.extend({}, changed, configs.encryptBackup)\n            };\n\n            return objects;\n        },\n\n    });\n\n    /**\n     * Initialize it automaticaly because everything depends on configs\n     * collection and it should be available as soon as possible.\n     */\n    return new Collection();\n});\n"
  },
  {
    "path": "app/scripts/collections/modules/files.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/modules/module',\n    'collections/files',\n    'models/file',\n    'toBlob',\n    'blobjs'\n], function(Q, _, Radio, ModuleObject, Files, File, toBlob) {\n    'use strict';\n\n    /**\n     * Collection module for Files.\n     *\n     * Apart from the replies in collections/modules/module.js,\n     * it also has additional replies:\n     *\n     * 1. `get:files` - fetches all files with specified IDs.\n     *\n     * Triggers events on channel `files`:\n     * 1. `saved:all` - after changes to collection are saved.\n     */\n    var Collection = ModuleObject.extend({\n        Collection: Files,\n\n        reply: function() {\n            return {\n                'get:files'  : this.getFiles,\n                'save:all'   : this.saveAll\n            };\n        },\n\n        /**\n         * Fetch files with specific ids\n         * @type array ids of files\n         * @type object options\n         */\n        getFiles: function(ids, options) {\n            var promises = [];\n\n            _.each(ids, function(id) {\n                promises.push(\n                    this.getModel(_.extend({id: id}, options))\n                );\n            }, this);\n\n            return Q.all(promises)\n            .then(function() {\n                return arguments[0];\n            });\n        },\n\n        /**\n         * Save a file.\n         * @type object Backbone model\n         * @type object new values\n         */\n        saveModel: function(model, data) {\n            var saveFunc = _.bind(ModuleObject.prototype.saveModel, this);\n            data.src = toBlob(data.src);\n            model.setEscape(data);\n\n            return saveFunc(model, model.attributes);\n        },\n\n        /**\n         * Save several files.\n         * @type array\n         * @type object\n         */\n        saveAll: function(data, options) {\n            var promises = [],\n                self     = this,\n                files    = [],\n                model;\n\n            _.each(data, function(imgData) {\n                promises.push(function() {\n                    model = new (self.changeDatabase(options)).prototype.model();\n                    return self.saveModel(model, imgData)\n                    .then(function(file) {\n                        files.push(file);\n                    });\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                Radio.trigger('files', 'saved:all');\n                return files;\n            });\n        }\n\n    });\n\n    // Initialize it automaticaly\n    Radio.request('init', 'add', 'app:before', function() {\n        new Collection();\n    });\n\n    return Collection;\n});\n"
  },
  {
    "path": "app/scripts/collections/modules/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio'\n], function(_, Q, Marionette, Radio) {\n    'use strict';\n\n    /**\n     * Collection object from which other collection objects extend.\n     *\n     * For default it\n     *\n     * replies to the following requests:\n     * 1. save            - save model changes\n     * 2. save:collection - save all collection changes\n     * 3. save:all:raw    - saves several objects\n     * 4. fetch           - fetches models from the database\n     * 5. get:model       - returns a specific model\n     * 6. get:all         - returns a collection\n     * 7. remove          - removes a model\n     *\n     * and triggers the following events:\n     * 1. model:update    - after a model is updated or created\n     * 2. destroy:model   - after a model is removed\n     */\n    var Module = Marionette.Object.extend({\n        /**\n         * @type object Backbone collection\n         */\n        Collection: null,\n\n        /**\n         * @type string default profile\n         */\n        defaultDB: 'notes-db',\n\n        /**\n         * Requests to which every collection module\n         * replies for default.\n         * @return object\n         */\n        reply: function() {\n            return {\n                'save'            : this.saveModel,\n                'save:collection' : this.saveCollection,\n                'save:raw'        : this.saveRaw,\n                'save:all:raw'    : this.saveAllRaw,\n                'fetch'           : this.fetch,\n                'get:model'       : this.getModel,\n                'get:all'         : this.getAll,\n                'remove'          : this.remove,\n            };\n        },\n\n        initialize: function() {\n            // Default replies\n            var defReply = _.bind(Module.prototype.reply, this);\n            this.vent = Radio.channel(this.Collection.prototype.storeName);\n\n            _.bindAll(this, 'encryptModel', 'decryptModel', 'decryptModels');\n\n            // Register replies\n            this.vent.reply(_.extend(defReply(), this.reply()), this);\n\n            // Listen to events\n            this.listenTo(this.vent, 'destroy:collection', this.onReset, this);\n        },\n\n        /**\n         * Switch to another database (e.g. profile)\n         * @type object\n         */\n        changeDatabase: function(options) {\n            var profile  = options && options.profile ? options.profile : this.defaultDB,\n                model,\n                collection;\n\n            model  = this.Collection.prototype.model.extend({\n                profileId : profile\n            });\n\n            collection = this.Collection.extend({\n                profileId : profile,\n                model     : model\n            });\n\n            return collection;\n        },\n\n        /**\n         * Stop listening to current collection's events.\n         */\n        onReset: function() {\n            if (!this.collection) {\n                return;\n            }\n\n            this.stopListening(this.collection);\n            if (this.collection.removeEvents) {\n                this.collection.removeEvents();\n            }\n            this.collection.reset([]);\n            this.collection = null;\n        },\n\n        /**\n         * Save changes to a model.\n         * @type object Backbone model\n         * @type object new values\n         */\n        save: function(model, data) {\n            var self   = this,\n                setF   = model.setEscape ? 'setEscape' : 'set',\n                errors = model.validate(data);\n\n            if (errors) {\n                model.trigger('invalid', model, errors);\n                return Q.reject('Validation error:' + model.storeName, errors);\n            }\n\n            // Set new values\n            model[setF](data);\n\n            return new Q(self.encryptModel(model))\n            .then(function(model) {\n                return new Q(model.save(model.attributes, {validate: false}))\n                .thenResolve(model);\n            });\n        },\n\n        /**\n         * @type object Backbone model\n         * @type object new values\n         */\n        saveModel: function(model, data) {\n            var self = this;\n\n            data.updated = Date.now();\n            if (!model.attributes.created) {\n                data.created = Date.now();\n            }\n\n            return this.save(model, data)\n            .then(function(model) {\n                self.vent.trigger('sync:model', model);\n                return self.decryptModel(model);\n            })\n            .then(function(model) {\n                self.vent.trigger('update:model', model);\n                return model;\n            });\n        },\n\n        /**\n         * Save all changes in the collection.\n         * @type object Backbone collection\n         */\n        saveCollection: function(collection) {\n            var promises = [],\n                self     = this;\n            collection   = collection || this.collection;\n\n            collection.each(function(model) {\n                model.attributes.updated = Date.now();\n\n                promises.push(\n                    Q.invoke(model, 'save', model.attributes)\n                );\n            });\n\n            return Q.all(promises)\n            .then(function() {\n                self.vent.trigger('saved:collection');\n                return collection;\n            });\n        },\n\n        /**\n         * Saves raw object to the database.\n         * @type object JSON object\n         * @type object options\n         */\n        saveRaw: function(data, options) {\n            var self   = this,\n                model  = new (this.changeDatabase(options)).prototype.model(data),\n                errors;\n\n            return this.decryptModel(model)\n            .then(function() {\n                errors = model.validate(model.attributes);\n\n                // Don't save data which can't be validated\n                if (errors) {\n                    console.error('Validation failed:' + model.storeName, errors);\n                    return;\n                }\n\n                return self.save(model, data)\n                .then(self.decryptModel)\n                .then(function(model) {\n                    self.vent.trigger('update:model', model);\n                    self.vent.trigger('synced:' + model.id, model);\n                    return model;\n                });\n            });\n        },\n\n        /**\n         * Saves all changes.\n         * @type array\n         */\n        saveAllRaw: function(arData, options) {\n            var promises = [],\n                self     = this;\n\n            _.each(arData, function(data) {\n                promises.push(function() {\n                    return self.saveRaw(data, options);\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q());\n        },\n\n        /**\n         * Remove a model.\n         * @type object Backbone model or ID\n         * @type object options\n         */\n        remove: function(model, options) {\n            var self = this;\n\n            // Change model's attributes to default values (empty values)\n            model = typeof model === 'string' ? model : model.id;\n            model = new (this.changeDatabase(options)).prototype.model({id: model});\n\n            model.set({'trash': 2, updated: Date.now()});\n\n            return this.save(model, model.attributes)\n            .then(function() {\n                self.vent.trigger('destroy:model', model);\n            });\n        },\n\n        /**\n         * Find a model by id.\n         * @type object options\n         */\n        getModel: function(options) {\n            var Model  = (this.changeDatabase(options)).prototype.model,\n                idAttr = Model.prototype.idAttribute,\n                data   = {},\n                model;\n\n            data[idAttr] = options[idAttr];\n            model        = new Model(data);\n\n            // If id was not provided, return a model with default values\n            if (!options[idAttr] || options[idAttr] === '0') {\n                model.set(idAttr, undefined);\n                return new Q(model);\n            }\n\n            // In case if the collection isn't empty, get the model from there.\n            if (this.collection &&\n                this.collection.profileId === model.profileId &&\n                this.collection.get(options[idAttr])) {\n                return new Q(this.collection.get(options[idAttr]));\n            }\n\n            var self = this;\n\n            return new Q(model.fetch())\n            .then(function() {\n                return self.decryptModel(model)\n                .thenResolve(model);\n            })\n            .fail(function(e) {\n                if (typeof e === 'string' && e.search('not found') > -1) {\n                    return null;\n                }\n                throw new Error(e);\n            });\n        },\n\n        /**\n         * Fetch data and create a new collection.\n         * @type object options\n         */\n        getAll: function(options) {\n            var self = this;\n            this.vent.trigger('destroy:collection');\n\n            // Add filter conditions\n            if (options.filter) {\n                var cond = this.Collection.prototype.conditions[options.filter];\n                cond = (typeof cond === 'function' ? cond(options) : cond);\n                options.conditions = cond;\n            }\n\n            // this.onReset();\n\n            return this.fetch(options || {})\n            .then(function(collection) {\n                self.collection = collection;\n                self.collection.conditionFilter  = options.filter;\n                self.collection.conditionCurrent = options.conditions;\n\n                // Register events\n                if (self.collection.registerEvents) {\n                    self.collection.registerEvents();\n                }\n\n                // Events\n                self.listenTo(self.collection, 'reset:all', self.onReset);\n\n                return self.collection;\n            });\n        },\n\n        /**\n         * Fetch data.\n         * @type object options\n         */\n        fetch: function(options) {\n            var collection = new (this.changeDatabase(options))(),\n                self       = this;\n\n            return new Q(collection.fetch(options))\n            .then(function() {\n\n                // Return in decrypted format\n                if (!options.encrypt) {\n                    return self.decryptModels(collection.fullCollection || collection)\n                    .then(function() {\n                        collection.trigger('decrypted');\n                        return;\n                    })\n                    .thenResolve(collection);\n                }\n\n                return collection;\n            });\n        },\n\n        /**\n         * @return boolean\n         */\n        _isEncryptEnabled: function(model) {\n            // Don't use encryption on configs\n            if (this.Collection.prototype.storeName === 'configs') {\n                return false;\n            }\n\n            var configs = Radio.request('configs', 'get:object'),\n                backup  = {encrypt: configs.encryptBackup.encrypt || 0};\n            model       = model || this.Collection.prototype.model.prototype;\n\n            return (\n                !_.isUndefined(model.encryptKeys) &&\n                (Number(configs.encrypt) || Number(backup.encrypt)) === 1\n            );\n        },\n\n        /**\n         * @type object Backbone model\n         */\n        encryptModel: function(model) {\n            if (!this._isEncryptEnabled(model)) {\n                return new Q(model);\n            }\n\n            return Radio.request('encrypt', 'encrypt:model', model);\n        },\n\n        /**\n         * @type object Backbone model\n         */\n        decryptModel: function(model) {\n            if (!this._isEncryptEnabled(model)) {\n                return new Q(model);\n            }\n\n            return new Q(\n                Radio.request('encrypt', 'decrypt:model', model)\n            );\n        },\n\n        /**\n         * Decrypt every model in the collection\n         * @type object Backbone collection\n         */\n        decryptModels: function(collection) {\n            collection = collection || this.collection;\n            if (!this._isEncryptEnabled(collection.model.prototype)) {\n                return new Q(collection);\n            }\n\n            collection = collection.fullCollection || collection;\n            return Radio.request('encrypt', 'decrypt:models', collection);\n        }\n    });\n\n    return Module;\n});\n"
  },
  {
    "path": "app/scripts/collections/modules/notebooks.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'collections/modules/module',\n    'collections/notebooks'\n], function(_, Q, Marionette, Radio, ModuleObject, Notebooks) {\n    'use strict';\n\n    /**\n     * Collection module for Notebooks.\n     * A convenience object that handles operations to Notebooks collection.\n     *\n     * It listens to all events and replies registered in collections/modules/module.js\n     */\n    var Collection = ModuleObject.extend({\n        Collection: Notebooks,\n\n        /**\n         * Remove an existing notebook.\n         * @type object Backbone model\n         * @type object options\n         * @type boolean true if all attached notes should be removed\n         */\n        remove: function(model, options, remove) {\n            var self = this;\n\n            if (typeof model === 'string') {\n                model = this.getModel(_.extend({id: model}, options));\n            }\n\n            /**\n             * Move child models to a higher level.\n             * Then, remove notes attached to the notebook or change their notebookId.\n             * And finally, remove the notebook.\n             */\n            return new Q(model)\n            .then(function(model) {\n                return self.updateChildren(model).thenResolve(model);\n            })\n            .then(function(model) {\n                return Radio.request('notes', 'change:notebookId', model, remove)\n                .thenResolve(model);\n            })\n            .then(function(model) {\n                var removeFunc = _.bind(ModuleObject.prototype.remove, self);\n                return removeFunc(model, options);\n            });\n        },\n\n        /**\n         * Move child models to a higher level.\n         * @type object Backbone model\n         */\n        updateChildren: function(model) {\n            var self = this;\n\n            return this.getChildren(model.id, {profile: model.profileId})\n            .then(function(collection) {\n                var promises = [];\n\n                // Change parentId of each children\n                collection.each(function(child) {\n                    promises.push(new Q(\n                        self.saveModel(child, {parentId: model.get('parentId')})\n                    ));\n                });\n\n                return Q.all(promises);\n            });\n        },\n\n        /**\n         * Returns models with the specified parent ID.\n         * @type string\n         */\n        getChildren: function(parentId, options) {\n            // Just filter an existing collection\n            if (this.collection) {\n                var collection = this.collection.clone();\n                collection.reset(collection.getChildren(parentId));\n                return Q.resolve(collection);\n            }\n\n            return this.fetch(_.extend(\n                {conditions: {parentId: parentId}},\n                options || {}\n            ));\n        },\n\n        /**\n         * Get all notebooks.\n         * @type object options\n         */\n        getAll: function(options) {\n            var self      = this,\n                sortField = Radio.request('configs', 'get:config', 'sortnotebooks');\n\n            options.profile = options.profile || this.defaultDB;\n            options.filter  = options.filter || 'active';\n\n            // Do not fetch twice\n            if (this.collection && this.collection.profileId === options.profile) {\n                this.collection.models = this.collection.getTree();\n                return new Q(\n                    this.collection\n                );\n            }\n\n            var getFunc = _.bind(ModuleObject.prototype.getAll, this);\n            this.Collection.prototype.sortField = sortField;\n\n            return getFunc(options)\n            .then(function() {\n                self.collection.models = self.collection.getTree();\n                return self.collection;\n            });\n        },\n\n    });\n\n    // Initialize it automaticaly\n    Radio.request('init', 'add', 'app:before', function() {\n        new Collection();\n    });\n\n    return Collection;\n});\n"
  },
  {
    "path": "app/scripts/collections/modules/notes.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'collections/modules/module',\n    'collections/notes'\n], function(_, Q, Marionette, Radio, ModuleObject, Notes) {\n    'use strict';\n\n    /**\n     * Collection module for Notes.\n     *\n     * Apart from the replies in collections/modules/module.js,\n     * it also has additional replies:\n     * 1. `get:model:full`    - returns a note with its notebook.\n     * 2. `restore`           - restores a note from trash\n     * 3. `change:notebookId` - when a notebook is removed, either move all attached\n     *    notes to trash or change notebook ID.\n     */\n    var Collection = ModuleObject.extend({\n        Collection: Notes,\n\n        reply: function() {\n            return {\n                'get:model:full'  : this.getModelFull,\n                'restore'         : this.restore,\n                'change:notebookId' : this.onNotebookRemove\n            };\n        },\n\n        /**\n         * Save a note.\n         * @type object Backbone model\n         * @type object new values\n         */\n        saveModel: function(model, data, saveTags) {\n\t\t\tif(saveTags === undefined || saveTags === null){\n\t\t\t\tsaveTags = true;\n\t\t\t}\n            var saveFunc = _.bind(ModuleObject.prototype.saveModel, this);\n\n\t\t\tif(saveTags){\n            \t// Before saving the model, add tags\n\t\t\t\treturn new Q(Radio.request('tags', 'add', data.tags || [], {\n\t\t\t\t\tprofile: model.profileId\n\t\t\t\t}))\n\t\t\t\t.then(function() {\n\t\t\t\t\treturn saveFunc(model, data);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Save model without tags\n\t\t\treturn saveFunc(model,data);\n\n        },\n\n        /**\n         * Remove a note.\n         * @type object Backbone model\n         * @type object options\n         */\n        remove: function(model, options) {\n            var self = this;\n            model = (typeof model === 'string' ? this.getModel(model, options) : model);\n\n            return new Q(model)\n            .then(function(model) {\n                // If the model is already in trash, destroy it\n                if (Number(model.get('trash')) === 1) {\n                    var removeFunc = _.bind(ModuleObject.prototype.remove, self);\n                    return removeFunc(model, options);\n                }\n\n                // Otherwise, just change 'trash' status\n                return self.save(model, {trash: 1, updated: Date.now()})\n                .then(function(model) {\n                    self.vent.trigger('destroy:model', model);\n                    return model;\n                });\n            });\n        },\n\n        /**\n         * Restore a model from trash.\n         * @type object Backbone model\n         * @type object options\n         */\n        restore: function(model, options) {\n            var self = this;\n            model = (typeof model === 'string' ? this.getModel(model, options) : model);\n\n            return new Q(model)\n            .then(function(model) {\n                return self.save(model, {trash: 0, updated: Date.now()})\n                .then(function(model) {\n                    self.vent.trigger('restore:model', model);\n                    return model;\n                });\n            });\n        },\n\n        /**\n         * When a notebook is removed, either move all attached\n         * notes to trash or change notebook ID.\n         * @type object Backbone model\n         * @type boolean true if all attached notes should be removed\n         */\n        onNotebookRemove: function(notebook, remove) {\n            var self = this,\n                data = {notebookId: 0};\n\n            if (remove) {\n                data.trash = 1;\n            }\n\n            return this.fetch({\n                conditions: {\n                    notebookId: notebook.id\n                },\n                profile : notebook.profileId\n            })\n            .then(function(notes) {\n                if (notes.length === 0) {\n                    return;\n                }\n\n                var coll     = notes.fullCollection || notes,\n                    promises = [];\n\n                coll.each(function(note) {\n                    promises.push(self.saveModel(note, data));\n                });\n\n                return Q.all(promises);\n            });\n        },\n\n        /**\n         * Get all notes.\n         * @type object options\n         */\n        getAll: function(options) {\n            var getAll    = _.bind(ModuleObject.prototype.getAll, this),\n                self      = this,\n                sortField = Radio.request('configs', 'get:config', 'sortnotes');\n\n            options.filter = options.filter || 'active';\n\n            this.Collection.prototype.sortField = sortField;\n\n            return getAll(options)\n            .then(function(collection) {\n                self._filterOnFetch(collection, options);\n                return collection;\n            });\n        },\n\n        /**\n         * Use Backbone's filters when IndexedDB is not available\n         */\n        _filterOnFetch: function(collection, options) {\n            collection.filterList(options.filter, options);\n        },\n\n        /**\n         * Return a note with its notebook.\n         * @type object options\n         */\n        getModelFull: function(options) {\n            return this.getModel(options)\n            .then(function(note) {\n                return Q.all([\n                    Radio.request('notebooks', 'get:model', {\n                        profile : options.profile,\n                        id      : note.get('notebookId')\n                    }),\n                    Radio.request('files', 'get:files', note.get('files'), {\n                        profile: options.profile\n                    })\n                ])\n                .spread(function(notebook, files) {\n                    note.notebook = notebook;\n                    note.files    = files;\n                    return [note, notebook];\n                });\n            });\n        },\n    });\n\n    // Initialize it automaticaly\n    Radio.request('init', 'add', 'app:before', function() {\n        new Collection();\n    });\n\n    return Collection;\n});\n"
  },
  {
    "path": "app/scripts/collections/modules/tags.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'collections/modules/module',\n    'collections/tags'\n], function(_, Q, Marionette, Radio, ModuleObject, Tags) {\n    'use strict';\n\n    /**\n     * Collection module for Tags.\n     *\n     * Apart from the replies in collections/modules/module.js,\n     * it also has an additional reply `add` which inserts several\n     * tags into database.\n     */\n    var Collection = ModuleObject.extend({\n        Collection: Tags,\n\n        reply: function() {\n            return {\n                'add': this.addTags,\n            };\n        },\n\n        /**\n         * Add a bunch of tags.\n         * @type array array of tags\n         * @type object options\n         */\n        addTags: function(tags, options) {\n            var self     = this,\n                promises = [];\n\n            if (!tags.length) {\n                return new Q();\n            }\n\n            options = options || {};\n            _.each(tags, function(tag) {\n                promises.push(function() {\n                    return self.addTag(tag, options);\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        /**\n         * Add a tag.\n         * @type string name of a tag\n         * @type object options\n         */\n        addTag: function(tag, options) {\n            var self = this;\n\n            return new Q(Radio.request('encrypt', 'sha256', tag))\n            .then(function(id) {\n                options.id = id;\n                return self.getModel({id: id.join(''), profile: options.profile});\n            })\n            .then(function(model) {\n                if (!model || !model.get('name').length) {\n                    model = new (self.changeDatabase(options)).prototype.model();\n                    return self.saveModel(model, {name: tag});\n                }\n                return model;\n            });\n        },\n\n        /**\n         * Save or create a tag.\n         * @type object Backbone model\n         * @type object new values\n         */\n        saveModel: function(model, data) {\n            var self   = this,\n                errors = model.validate(data);\n\n            if (errors) {\n                model.trigger('invalid', model, errors);\n                return Q.reject('Validation error: tags', errors);\n            }\n\n            // First, make sure that a model won't duplicate itself.\n            return new Q(Radio.request('encrypt', 'sha256', data.name))\n            .then(function(id) {\n                id = id.join('');\n\n                if (!model.id) {\n                    return id;\n                }\n\n                return self.remove(model, {profile: model.profileId})\n                .thenResolve(id);\n            })\n            .then(function(id) {\n                var saveFunc = _.bind(ModuleObject.prototype.saveModel, self);\n                model.set(data);\n                model.set('id', id);\n\n                return saveFunc(model, model.attributes);\n            });\n        },\n\n    });\n\n    // Initialize the collection automaticaly\n    Radio.request('init', 'add', 'app:before', function() {\n        new Collection();\n    });\n\n    return Collection;\n});\n"
  },
  {
    "path": "app/scripts/collections/notebooks.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone',\n    'collections/pageable',\n    'backbone.radio',\n    'models/notebook',\n], function(_, Backbone, PageableCollection, Radio, Notebook) {\n    'use strict';\n\n    /**\n     * Notebooks collection\n     */\n    var Notebooks = PageableCollection.extend({\n        model: Notebook,\n\n        profileId : 'notes-db',\n        storeName : 'notebooks',\n\n        state: {\n            pageSize     : 0,\n            firstPage    : 1,\n            totalPages   : 1,\n            currentPage  : 0\n        },\n\n        conditions: {\n            active: {trash: 0}\n        },\n\n        sortField: 'name',\n\n        comparator: function(model) {\n            if (this.sortField === 'name') {\n                return model.get(this.sortField);\n            } else {\n                return -model.get(this.sortField);\n            }\n        },\n\n        sortItOut: function() {\n            this.models = this.getTree();\n        },\n\n        sortFullCollection: function() {\n            this.sortItOut();\n            this.reset(this.models);\n        },\n\n        _onAddItem: function(model) {\n            /**\n             * Remove a model from the collection if it doesn't meet\n             * the current filter condition.\n             */\n            if (!model.matches(this.conditionCurrent || {trash: 0})) {\n                return this._navigateOnRemove(model);\n            }\n\n            var colModel = this.get(model.id);\n            if (colModel) {\n                colModel.set(model.attributes);\n            }\n            else {\n                this.add(model);\n            }\n\n            this.sortFullCollection();\n            Radio.trigger('notebooks', 'model:navigate', model);\n        },\n\n        /**\n         * Return only notebooks that are not related to a specified notebook.\n         */\n        rejectTree: function(id) {\n            var ids = [id];\n            return this.filter(function(model) {\n                if (_.indexOf(ids, model.id) > -1 ||\n                    _.indexOf(ids, model.get('parentId')) > -1) {\n\n                    ids.push(model.id);\n                    return false;\n                }\n\n                return true;\n            });\n        },\n\n        /**\n         * Build a tree structure\n         */\n        getTree: function(parents, tree) {\n            var self = this,\n                children;\n\n            parents = (parents || this.getRoots());\n            tree = (tree || []);\n\n            _.forEach(parents, function(model) {\n                tree.push(model);\n                children = self.getChildren(model.get('id'));\n\n                // Every child model can have its own children\n                if (children.length > 0) {\n                    children = self.getTree(children, tree);\n                }\n            });\n\n            return tree;\n        },\n\n        /**\n         * Finds notebooks children\n         */\n        getChildren: function(parentId) {\n            return this.filter(function(model) {\n                return model.get('parentId') === parentId;\n            });\n        },\n\n        /**\n         * Only root notebooks\n         */\n        getRoots:  function() {\n            return this.filter(function(notebook) {\n                return notebook.get('parentId') === '0';\n            });\n        },\n\n    });\n\n    return Notebooks;\n});\n"
  },
  {
    "path": "app/scripts/collections/notes.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone',\n    'backbone.radio',\n    'collections/pageable',\n    'models/note',\n    'fuse',\n], function(_, Backbone, Radio, PageableCollection, Note, Fuse) {\n    'use strict';\n\n    var Notes = PageableCollection.extend({\n        model: Note,\n\n        profileId : 'notes-db',\n        storeName : 'notes',\n\n        state: {\n            pageSize     : 10,\n            firstPage    : 0,\n            currentPage  : 0,\n            totalRecords : 0\n        },\n\n        conditions: {\n            active   : {trash      : 0},\n            favorite : {isFavorite : 1, trash : 0},\n            trashed  : {trash      : 1},\n            notebook : function(args) {\n                return {notebookId: args.query, trash: 0};\n            }\n        },\n\n        sortField: 'created',\n\n        initialize: function() {\n            this.state.comparator = {};\n            this.state.comparator[this.sortField] = this.sortField === 'title' ? 'asc' : 'desc';\n            this.state.comparator.isFavorite = 'desc';\n        },\n\n        filterList: function(filter, options) {\n            if (!filter || !this[filter + 'Filter']) {\n                return;\n            }\n\n            var res = this[filter + 'Filter'](options.query);\n            return this.reset(res);\n        },\n\n        /**\n         * Show notes with unfinished tasks\n         */\n        taskFilter: function() {\n            return this.filter(function(note) {\n                return note.get('taskCompleted') < note.get('taskAll');\n            });\n        },\n\n        /**\n         * Show only tag's notes\n         * Returns notes to which a specified tag was attached.\n         */\n        tagFilter: function(tagName) {\n            return this.filter(function(note) {\n                if (note.get('tags').length > 0) {\n                    return (\n                        (_.indexOf(note.get('tags'), tagName) !== -1) &&\n                        note.get('trash') === 0\n                    );\n                }\n            });\n        },\n\n        /**\n         * Search\n         */\n        searchFilter: function(letters) {\n            if (!letters || letters === '') {\n                return this;\n            }\n\n            var pattern = new RegExp(letters, 'gim');\n\n            return this.filter(function(model) {\n                pattern.lastIndex = 0;\n                return pattern.test(model.get('title')) || pattern.test(model.get('content'));\n            });\n        },\n\n        fuzzySearch: function(text) {\n            var fuse = new Fuse(this.fullCollection.models, {\n                keys  : ['title'],\n                getFn : function(obj, path) {\n                    return obj.get(path);\n                }\n            });\n            return fuse.search(text);\n        },\n\n    });\n\n    return Notes;\n});\n"
  },
  {
    "path": "app/scripts/collections/pageable.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone',\n    'backbone.radio'\n], function(_, Backbone, Radio) {\n    'use strict';\n\n    /**\n     * Pagination support for Backbone collections.\n     * Some code was borrowed from the plugin Backbone.paginator.\n     *\n     * Triggers:\n     * ---------\n     * Events to channel `notes`:\n     * 1. `model:navigate` - when the next or previous model was requested\n     *     or a model was removed.\n     *\n     * Events to itself (e.g. collection):\n     * 1. `page:next` - when the next model was requested but a user\n     *     has reached the last model on the page.\n     * 2. `page:previous` - when the previous model was requested but a user\n     *     has reached the first model on the page.\n     */\n    var PageableCollection = Backbone.Collection.extend({\n\n        // Default pagination settings\n        state: {\n            pageSize     : 4,\n            firstPage    : 0,\n            currentPage  : 0,\n            totalRecords : 0,\n            comparator   : {'isFavorite' : 'desc', 'created' : 'desc'}\n        },\n\n        /**\n         * Overrite `fetch` method.\n         */\n        fetch: function(options) {\n            options = options || {};\n            options.options = options.options || {};\n\n            if (!_.isUndefined(options.pageSize)) {\n                this.state.pageSize = Number(options.pageSize);\n            }\n\n            // Do not use pagination\n            if (this.state.pageSize === 0) {\n                return Backbone.Collection.prototype.fetch.call(this, options);\n            }\n\n            var success = options.success,\n                self    = this;\n\n            options.success = function(resp) {\n\n                // Keep full collection in memory\n                self.fullCollection = self.clone();\n\n                // Sort the collection\n                self.fullCollection.sortItOut();\n\n                // Pagination\n                self._updateTotalPages();\n                self.getPage(options.page || self.state.firstPage);\n\n                if (success) {\n                    success(self, resp);\n                }\n            };\n\n            return Backbone.Collection.prototype.fetch.call(this, options)\n            .then(function(resp) {\n                options.success(resp);\n                return resp;\n            });\n        },\n\n        /**\n         * Handles events.\n         * It needs to be called after a collection was instantiated.\n         */\n        registerEvents: function() {\n            this.vent = Radio.channel(this.storeName);\n\n            // Sort the collection again when favorite status is changed\n            this.listenTo(this, 'change:isFavorite', this.sortItOut);\n            this.listenTo(this, 'reset', this.sortItOut);\n\n            // Listen to events\n            this.listenTo(this.vent, 'update:model' , this._onAddItem, this);\n            this.listenTo(this.vent, 'destroy:model', this._navigateOnRemove, this);\n            this.listenTo(this.vent, 'restore:model', this._onRestore, this);\n\n            return this;\n        },\n\n        /**\n         * It makes some \"garbage collection\"\n         * by destroying full collection and event listeners.\n         * If a collection is no longer in use, this method should be called.\n         */\n        removeEvents: function() {\n            // Destroy a full collection\n            if (this.fullCollection) {\n                this.fullCollection.reset();\n                this.fullCollection = null;\n            }\n\n            // Remove all the event listeners\n            this.stopListening();\n            this.stopListening(this.vent);\n\n            return this;\n        },\n\n        getNextPage: function() {\n            var models = this.getPage(this.state.currentPage + 1);\n            this.reset(models);\n        },\n\n        getPreviousPage: function() {\n            var models = this.getPage(this.state.currentPage - 1);\n            this.reset(models);\n        },\n\n        /**\n         * Sets state.currentPage to the given number.\n         * Then, it overwrites models of the current collection.\n         */\n        getPage: function(number) {\n            // Calculate page number\n            var pageStart = this.getOffset(number);\n\n            // Save where we currently are\n            this.state.currentPage = number;\n\n            // Slice an array of models\n            this.models = this.fullCollection.models.slice(pageStart, pageStart + this.state.pageSize);\n\n            return this.models;\n        },\n\n        getOffset: function(number) {\n            return (\n                (this.state.firstPage === 0 ? number : number - 1) *\n                this.state.pageSize\n            );\n        },\n\n        hasPreviousPage: function() {\n            return this.state.currentPage !== this.state.firstPage;\n        },\n\n        hasNextPage: function() {\n            return this.state.currentPage !== this.state.totalPages - 1;\n        },\n\n        /**\n         * It is used to sort models in full collection.\n         */\n        sortFullCollection: function() {\n            if (!this.fullCollection) {\n                return;\n            }\n\n            // Sort the full collection again\n            this.fullCollection.sortItOut();\n\n            // Update pagination state\n            this._updateTotalPages();\n            this.getPage(this.state.currentPage);\n\n            // Reset the collection so the view could re-render itself\n            this.reset(this.models);\n        },\n\n        /**\n         * Useful when sorting models in a collection by multiple keys.\n         */\n        sortItOut: function() {\n            var comparator = this.comparator,\n                self = this;\n\n            _.each(this.state.comparator, function(value, key) {\n                self.comparator = function(model) {\n                    return (value === 'desc' ? (-model.get(key)) : model.get(key));\n                };\n                self.sort();\n            });\n\n            this.comparator = comparator;\n            return this.models;\n        },\n\n        getNextItem: function(id) {\n            // The collection is empty\n            if (this.length === 0) {\n                return false;\n            }\n\n            var model  = this.get(id),\n                index  = model ? this.indexOf(model) + 1 : 0;\n\n            // It is the last model on this page\n            if (index >= this.models.length) {\n                return this.trigger(\n                    this.hasNextPage() ? 'page:next' : 'page:end'\n                );\n            }\n\n            Radio.trigger(this.storeName, 'model:navigate', this.at(index));\n        },\n\n        getPreviousItem: function(id) {\n            // The collection is empty\n            if (this.length === 0) {\n                return false;\n            }\n\n            var model = this.get(id),\n                index = model ? this.indexOf(model) - 1 : this.models.length - 1;\n\n            // It is the first model on this page\n            if (index < 0) {\n                return this.trigger(\n                    this.hasPreviousPage() ? 'page:previous' : 'page:start'\n                );\n            }\n\n            Radio.trigger(this.storeName, 'model:navigate', this.at(index));\n        },\n\n        /**\n         * When some model was removed, trigger `model:navigate` event\n         * passing a model which has the same index as the removed model.\n         * @type object Backbone model\n         */\n        _navigateOnRemove: function(model) {\n            model     = this.get(model.id);\n            if (!model) {\n                return false;\n            }\n\n            var coll  = this.fullCollection || this,\n                index = this.indexOf(model);\n\n            coll.remove(model);\n            this.sortFullCollection();\n\n            if (!this.at(index)) {\n                index--;\n            }\n\n            if (!this.at(index)) {\n                return this.hasPreviousPage() ? this.trigger('page:previous') : null;\n            }\n\n            Radio.trigger(this.storeName, 'model:navigate', this.at(index));\n        },\n\n        /**\n         * When a model was restored from trash.\n         */\n        _onRestore: function(model) {\n            if (this.conditionFilter !== 'trashed') {\n                return this._onAddItem(model);\n            }\n\n            if (this.length > 1) {\n                return this._navigateOnRemove(model);\n            }\n        },\n\n        /**\n         * Update pagination when a model is added\n         */\n        _onAddItem: function(model) {\n\n            // Don't add models from other profiles\n            if (this.profileId !== model.profileId) {\n                return;\n            }\n\n            /**\n             * Remove a model from the collection if it doesn't meet\n             * the current filter condition.\n             */\n            if (!model.matches(this.conditionCurrent || {trash: 0})) {\n                return this._navigateOnRemove(model);\n            }\n\n            // If the model already exists, update it\n            var coll     = this.fullCollection || this,\n                colModel = coll.get(model.id);\n\n            if (colModel) {\n                return colModel.set(model.toJSON());\n            }\n\n            // Or add it to fullCollection and sort the collection again\n            coll.add(model, {at: 0});\n            this.sortFullCollection();\n        },\n\n        /**\n         * Update pagination when a model is removed\n         */\n        _onRemoveItem: function(model) {\n            this.fullCollection.remove(model);\n            this.sortFullCollection();\n        },\n\n        /**\n         * Updates the number of available pages\n         */\n        _updateTotalPages: function() {\n            this.state.totalPages = Math.ceil(\n                this.fullCollection.length / this.state.pageSize\n            );\n        }\n\n    });\n\n    return PageableCollection;\n});\n"
  },
  {
    "path": "app/scripts/collections/tags.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone',\n    'collections/pageable',\n    'backbone.radio',\n    'models/tag'\n], function(_, Backbone, PageableCollection, Radio, Tag) {\n    'use strict';\n\n    /**\n     * Tags collection\n     */\n    var Tags = PageableCollection.extend({\n        model: Tag,\n\n        profileId : 'notes-db',\n        storeName : 'tags',\n\n        state: {\n            pageSize     : 20,\n            firstPage    : 0,\n            currentPage  : 0,\n            totalRecords : 0,\n            comparator   : {'updated': 'desc'}\n        },\n\n        conditions: {\n            active: {trash: 0}\n        },\n\n        _onAddItem: function(model) {\n            PageableCollection.prototype._onAddItem.apply(this, arguments);\n            Radio.trigger('tags', 'model:navigate', model);\n        },\n\n        sortFullCollection: function() {\n            if (!this.fullCollection) {\n                return;\n            }\n\n            // Sort the full collection again\n            this.fullCollection.sortItOut();\n\n            // Update pagination state\n            this._updateTotalPages();\n\n            var models = this.fullCollection.models.slice(\n                0, this.state.pageSize * (this.state.currentPage + 1)\n            );\n\n            // Reset the collection so the view could re-render itself\n            this.reset(models);\n            return true;\n        },\n\n        /**\n         * Sets state.currentPage to the given number.\n         * Then, it overwrites models of the current collection.\n         */\n        getPage: function(number) {\n            // Calculate page number\n            var pageStart = this.getOffset(number),\n                models;\n\n            // Save where we currently are\n            this.state.currentPage = number;\n\n            // Slice an array of models\n            models = this.fullCollection.models.slice(pageStart, pageStart + this.state.pageSize);\n\n            if (number === 0) {\n                this.reset(models);\n            }\n            else {\n                this.add(models);\n            }\n\n            return this.models;\n        },\n\n        /**\n         * This collection is never going to have a previous page\n         * because it uses inifinite pagination.\n         */\n        hasPreviousPage: function() {\n            return false;\n        }\n\n    });\n\n    return Tags;\n});\n"
  },
  {
    "path": "app/scripts/constants.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine(['underscore'], function (_) {\n    'use strict';\n\n    var constants = {};\n\n    constants.VERSION = '0.7.51';\n    constants.URL = location.origin + location.pathname.replace('index.html', '');\n\n    // List of hosts and urls where default dropbox API will work\n    constants.DEFAULTHOSTS = ['laverna.cc', 'laverna.github.io', 'localhost', 'localhost:9000', 'localhost:9100'];\n\n    constants.DROPBOX_KEY = '10iirspliqts95d';\n    constants.DROPBOX_SECRET = null;\n\n    // Default Dropbox API key will not work\n    if ( !_.contains(constants.DEFAULTHOSTS, location.host) ) {\n        constants.DROPBOXKEYNEED = true;\n    }\n\n    return constants;\n});\n"
  },
  {
    "path": "app/scripts/helpers/db.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'localforage'\n], function(_, Q, localForage) {\n    'use strict';\n\n    /**\n     * LocalForage adapter.\n     */\n    var db = {\n        dbs: {},\n\n        /**\n         * Create a new localforage instance if it doesn't exist for\n         * current profile or store.\n         *\n         * @type object options\n         */\n        getDb: function(options) {\n            var dbId = options.profile + '/' + options.storeName;\n\n            this.dbs[dbId] = this.dbs[dbId] || localForage.createInstance({\n                name      : options.profile || 'notes-db',\n                storeName : options.storeName\n            });\n\n            return this.dbs[dbId];\n        },\n\n        /**\n         * Find an item by ID.\n         *\n         * @type object data\n         */\n        find: function(data) {\n            var defer = Q.defer();\n\n            this.getDb(data.options).getItem(data.id, function(err, data) {\n                if (err) {\n                    return defer.reject(err);\n                }\n\n                if (!data) {\n                    defer.reject('not found');\n                }\n\n                return defer.resolve(data);\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Find all items.\n         *\n         * @type object data\n         */\n        findAll: function(data) {\n            var defer = Q.defer(),\n                self  = this;\n\n            // Find all keys of objects\n            this.getDb(data.options).keys(function(err, keys) {\n                if (!keys || !keys.length) {\n                    return defer.resolve([]);\n                }\n\n                // Return all found objects\n                return self.findByKeys(keys, data)\n                .then(function(res) {\n                    defer.resolve(res);\n                })\n                .fail(function(e) {\n                    defer.reject(e);\n                });\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Find all models with specified keys.\n         *\n         * @type array keys\n         * @type object data\n         */\n        findByKeys: function(keys, data) {\n            var promises = [],\n                self     = this,\n                models   = [];\n\n            _.each(keys, function(key) {\n                promises.push(\n                    self.find({id: key, options: data.options})\n                    .then(function(item) {\n\n                        // If conditions are provided, filter items with them\n                        if (item &&\n                            (!data.options.conditions || _.isMatch(item, data.options.conditions))) {\n                            models.push(item);\n                            return item;\n                        }\n\n                        return;\n                    })\n                );\n            });\n\n            return Q.all(promises)\n            .then(function() {\n                return models;\n            });\n        },\n\n        /**\n         * Save an item.\n         *\n         * @type object data\n         */\n        save: function(data) {\n            var defer = Q.defer(),\n                sData = data.data;\n\n            if (sData.encryptedData) {\n                sData = _.omit(sData, data.options.encryptKeys);\n            }\n\n            this.getDb(data.options).setItem(data.id, sData, function(err, val) {\n                if (err) {\n                    return defer.reject(err);\n                }\n\n                return defer.resolve(val);\n            });\n\n            return defer.promise;\n        },\n\n    };\n\n    return db;\n});\n"
  },
  {
    "path": "app/scripts/helpers/fileSaver.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\ndefine([\n    'q',\n    'fileSaver'\n], function(Q, fileSaver) {\n    'use strict';\n\n    return function(content, fileName) {\n\n        // If it is not Cordova app, use HTML5's saveAs function\n        if (!window.cordova) {\n            return new Q(fileSaver(content, fileName));\n        }\n\n        var defer = Q.defer();\n\n        // Use file plugin API\n        window.resolveLocalFileSystemURL(window.cordova.file.externalDataDirectory, function(dir) {\n            dir.getFile(fileName, {create: true}, function(file) {\n                file.createWriter(function(writer) {\n                    writer.write(content);\n                    defer.resolve();\n                });\n            });\n        });\n\n        return defer.promise;\n    };\n\n});\n"
  },
  {
    "path": "app/scripts/helpers/i18next.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'q',\n    'backbone.radio',\n    'i18next',\n    'i18nextXHRBackend',\n    'text!locales/locales.json'\n], function(_, $, Q, Radio, i18n, XHR, locales) {\n    'use strict';\n\n    var __ = {\n\n        /**\n         * Initialize i18next\n         */\n        init: function() {\n            var defer = Q.defer();\n            $.t       = i18n.t.bind(i18n);\n\n            i18n\n            .use(XHR)\n            .init(\n                {\n                    lng          : __.getLang(),\n                    fallbackLng  : ['en'],\n                    ns           : [''],\n                    defaultNS    : '',\n                    backend      : {\n                        loadPath : 'locales/{{lng}}/translation.json'\n                    },\n                },\n                function() {\n                    defer.resolve();\n                }\n            );\n\n            return defer.promise;\n        },\n\n        /**\n         * Get language either from configs or\n         * autodetect it from browser settings.\n         */\n        getLang: function() {\n            var lng = Radio.request('configs', 'get:config', 'appLang');\n\n            if (lng || typeof window.navigator === 'undefined') {\n                return lng;\n            }\n\n            // Language keys in navigator\n            lng     = ['languages', 'language', 'userLanguage', 'browserLanguage'];\n\n            // Available locales\n            locales = _.keys(JSON.parse(locales));\n\n            return _.chain(window.navigator)\n            .pick(lng)\n            .values()\n            .flatten()\n            .compact()\n            .map(function(key) {\n                return key.replace('-', '_').toLowerCase();\n            })\n            .find(function(key) {\n                return _.contains(locales, key);\n            })\n            .value();\n        },\n\n    };\n\n    /**\n     * Init i18next on `app:before` initialize request.\n     */\n    Radio.request('init', 'add', 'app:before', __.init);\n\n    return __;\n\n});\n"
  },
  {
    "path": "app/scripts/helpers/keybindings.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'mousetrap',\n    'mousetrap.pause'\n], function(_, Marionette, Radio, Mousetrap) {\n    'use strict';\n\n    /**\n     * Keybindings helper.\n     *\n     * Replies to requests on `global` channel:\n     * 1. `mousetrap:toggle`  - pause or unpause Mousetrap.\n     * 2. `mousetrap:restart` - rebind the keys.\n     * 3. `mousetrap:reset`   - reset the keys.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n            // Fetch configs and bind the keys\n            this.configs = Radio.request('configs', 'get:object');\n            this.bind();\n\n            Radio.reply('global', {\n                'mousetrap:toggle'  : this.toggle,\n                'mousetrap:restart' : this.restart,\n                'mousetrap:reset'   : Mousetrap.reset\n            }, this);\n        },\n\n        /**\n         * Reset Mousetrap keys and bind them again.\n         */\n        restart: function() {\n            Mousetrap.reset();\n            this.bind();\n        },\n\n        /**\n         * Pause or unpause Mousetrap\n         */\n        toggle: function() {\n            Mousetrap[(this.paused ? 'unpause' : 'pause')]();\n            this.paused = (this.paused ? false : true);\n        },\n\n        navigate: function(uri) {\n            Radio.request('uri', 'navigate', uri, {\n                includeProfile : true,\n                trigger        : true\n            });\n        },\n\n        /**\n         * Register keybindings.\n         */\n        bind: function() {\n            var self = this;\n\n            // Help\n            Mousetrap.bind(this.configs.appKeyboardHelp, function(e) {\n                e.preventDefault();\n                Radio.request('Help', 'show:keybindings');\n            });\n\n            // Focus on search form\n            Mousetrap.bind(this.configs.appSearch, function(e) {\n                e.preventDefault();\n                Radio.trigger('global', 'show:search');\n            });\n\n            // Add or edit notes or notebooks\n            Mousetrap.bind(this.configs.appCreateNote, function() {\n                Radio.trigger('global', 'form:show');\n            });\n\n            // Redirect to notes list\n            Mousetrap.bind(this.configs.jumpInbox, function() {\n                self.navigate('/notes');\n            });\n\n            // Redirect to favorite notes\n            Mousetrap.bind(this.configs.jumpFavorite, function() {\n                self.navigate('/notes/f/favorite');\n            });\n\n            // Redirect to removed list of notes\n            Mousetrap.bind(this.configs.jumpRemoved, function() {\n                self.navigate('/notes/f/trashed');\n            });\n\n            // Redirect to list of notes with open tasks\n            Mousetrap.bind(this.configs.jumpOpenTasks, function() {\n                self.navigate('/notes/f/task');\n            });\n\n            // Redirect to notebooks list\n            Mousetrap.bind(this.configs.jumpNotebook, function() {\n                self.navigate('/notebooks');\n            });\n        }\n\n    });\n\n    /**\n     * Initializer\n     */\n    Radio.request('init', 'add', 'app:before', function() {\n        new Controller();\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/helpers/migrate.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, Modernizr */\ndefine([\n    'q',\n    'underscore',\n    'localforage',\n    'sjcl'\n], function(Q, _, localForage, sjcl) {\n    'use strict';\n\n    /**\n     * Migrate data from IndexedDB to localForage.\n     */\n    function Migrate() {\n    }\n\n    _.extend(Migrate.prototype, {\n\n        /**\n         * Initialize migration proccess.\n         *\n         * @return promise\n         */\n        init: function() {\n            if (!Modernizr.indexeddb) {\n                return new Q();\n            }\n\n            var defer = Q.defer(),\n                self = this;\n\n            self.start()\n            .then(function() {\n                setTimeout(function() {\n                    return defer.resolve();\n                }, 100);\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Start migration.\n         *\n         * @return promise\n         */\n        start: function() {\n            var self = this;\n\n            return this.openDb('notes-db')\n            .then(function(db) {\n                if (_.isNull(db)) {\n                    console.log('no migration is needed');\n                    return;\n                }\n\n                self.db = db;\n                return self.migrate(db);\n            })\n            .fail(function(e) {\n                console.error('Migration:initialize', e);\n            });\n        },\n\n        /**\n         * Open an indexedDB database.\n         *\n         * @type string name\n         * @return promise\n         */\n        openDb: function(name) {\n            var req   = window.indexedDB.open(name),\n                defer = Q.defer();\n\n            req.onerror = function(e) {\n                console.error('Migration:openDb', e);\n                defer.reject(e);\n            };\n\n            // If DB is empty, resolve with null\n            req.onupgradeneeded = function() {\n                if (req.result) {\n                    req.result.close();\n                }\n\n                defer.resolve(null);\n            };\n\n            req.onsuccess = function(e) {\n                defer.resolve(e.target.result);\n            };\n\n            return defer.promise;\n        },\n\n        /**\n         * Migrate data from all stores to localForage.\n         *\n         * @type object db\n         * @return promise\n         */\n        migrate: function(db) {\n            var stores   = ['notes', 'notebooks', 'tags', 'files'],\n                self     = this,\n                promises = [];\n\n            _.each(stores, function(store) {\n                promises.push(function() {\n                    var tr;\n                    try {\n                        tr = db.transaction([store]);\n                    } catch (e) {\n                        return;\n                    }\n\n                    return self.getData(tr, store)\n                    .then(function(data) {\n                        return self.migrateStore(store, data);\n                    });\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                db.close();\n                return;\n            })\n            .fail(function(e) {\n                console.error('Migration:migrate', e);\n            });\n        },\n\n        /**\n         * Call it after fetching data. It saves data to localForage.\n         *\n         * @type string store\n         * @type object data\n         * @return promise\n         */\n        migrateStore: function(store, data) {\n            var self     = this,\n                promises = [],\n                db       = localForage.createInstance({\n                    name      : 'notes-db',\n                    storeName : store\n                });\n\n            _.each(data, function(item) {\n                promises.push(function() {\n                    return self.removeItem(store, db, item.id)\n                    .then(function() {\n                        return self.saveForageItem(store, db, item);\n                    });\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                return;\n            })\n            .fail(function(e) {\n                console.error('Migration:migrateStore', e);\n            });\n        },\n\n        /**\n         * Save an item to localForage.\n         *\n         * @type string storeName\n         * @type object db\n         * @type string id\n         * @type object data\n         * @return promise\n         */\n        saveForageItem: function(storeName, db, data) {\n            var defer  = Q.defer();\n\n            // Convert data\n            if (!_.isUndefined(data.notebookId)) {\n                data.notebookId = data.notebookId.toString();\n            }\n            if (!_.isUndefined(data.parentId)) {\n                data.parentId = data.parentId.toString();\n            }\n            if (storeName === 'files') {\n                data.fileType = data.type;\n                data.type     = 'files';\n            }\n            if (storeName === 'tags') {\n                data.id = sjcl.hash.sha256.hash(data.name.toString()).join('');\n            }\n\n            data = _.extend(\n                {\n                    type    : storeName,\n                    created : Date.now(),\n                    updated : Date.now(),\n                    trash   : 0\n                },\n                data,\n                {\n                    id: data.id.toString()\n                }\n            );\n\n            db.setItem(data.id, data, function(err, val) {\n                if (err) {\n                    return defer.reject(err);\n                }\n\n                return defer.resolve(val);\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Remove an item from IndexedDB.\n         *\n         * @type string storeName\n         * @type number id\n         */\n        removeItem: function(storeName, db, id) {\n            if (storeName === 'tags') {\n                return this.putToTrash.apply(this, arguments);\n            }\n\n            if (storeName !== 'notebooks' || typeof id === 'string') {\n                return new Q();\n            }\n\n            var defer = Q.defer(),\n                req   = this.db.transaction([storeName], 'readwrite').objectStore(storeName).delete(id);\n\n            req.onsuccess = function() {\n                defer.resolve();\n            };\n\n            req.onerror = function(e) {\n                defer.reject(e);\n            };\n\n            return defer.promise;\n        },\n\n        putToTrash: function(storeName, db, id) {\n            var defer = Q.defer(),\n                data  = {id: id, type: 'tags', name: id, trash: 2, created: 0, updated: 0};\n\n            db.setItem(data.id, data, function(err, val) {\n                if (err) {\n                    return defer.reject(err);\n                }\n\n                return defer.resolve(val);\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Fetch data from indexedDB.\n         *\n         * @type object transaction\n         * @type string storeName\n         * @return promise\n         */\n        getData: function(transaction, storeName) {\n            var defer = Q.defer(),\n                req   = transaction.objectStore(storeName).openCursor(),\n                data  = {};\n\n            req.onerror = function(e) {\n                console.error('Migration:getData', e);\n                defer.reject(e);\n            };\n\n            req.onsuccess = function(e) {\n                var cursor = e.target.result;\n\n                if (cursor) {\n                    data[cursor.key] = cursor.value;\n                    cursor.continue();\n                }\n                else {\n                    return defer.resolve(data);\n                }\n            };\n\n            return defer.promise;\n        },\n\n    });\n\n    return Migrate;\n\n});\n"
  },
  {
    "path": "app/scripts/helpers/radio.shim.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n(function(root, factory) {\n    'use strict';\n    /* global define */\n    if (typeof define === 'function' && define.amd) {\n        define(['marionette', 'backbone.radio', 'underscore'], function(Marionette, Radio, _) {\n            return factory(Marionette, Radio, _);\n        });\n    }\n    else if (typeof exports !== 'undefined') {\n        var Marionette = require('marionette');\n        var Radio = require('backbone.radio');\n        var _ = require('underscore');\n        module.exports = factory(Marionette, Radio, _);\n    }\n    else {\n        factory(root.Backbone.Marionette, root.Backbone.Radio, root._);\n    }\n}(this, function(Marionette, Radio, _) {\n    'use strict';\n\n    Marionette.Application.prototype._initChannel = function() {\n        this.channelName = _.result(this, 'channelName') || 'global';\n        this.channel = _.result(this, 'channel') || Radio.channel(this.channelName);\n        this.reqres = this.channel;\n    };\n}));\n"
  },
  {
    "path": "app/scripts/helpers/storage.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs, Modernizr */\ndefine([\n    'underscore',\n    'q',\n    'backbone',\n    'backbone.radio'\n], function(_, Q, Backbone, Radio) {\n    'use strict';\n\n    /**\n     * Used for checking indexedDB support in a browser.\n     */\n    var Storage = {\n\n        /**\n         * If indexeddb isn't available use sync adapter without workers.\n         * @return promise\n         */\n        check: function() {\n\n            // Browser doesn't support indexeddb at all\n            if (!Modernizr.indexeddb || !Radio.request('global', 'use:webworkers')) {\n                return this.switchDb('backbone.noworker.sync');\n            }\n\n            var self = this;\n\n            return this.testDb()\n            .then(function() {\n                return self.switchDb('backbone.sync');\n            })\n            .fail(function() {\n                return self.switchDb('backbone.noworker.sync');\n            });\n        },\n\n        /**\n         * Test if indexeddb can be used by opening a database.\n         * @return promise\n         */\n        testDb: function() {\n            var defer   = Q.defer(),\n                request = window.indexedDB.open('isPrivateMode');\n\n            request.onerror = function() {\n                defer.reject();\n            };\n\n            request.onsuccess = function() {\n                defer.resolve();\n            };\n\n            return defer.promise;\n        },\n\n        /**\n         * Override Backbone.sync with our own adapter.\n         * @return promise\n         */\n        switchDb: function(syncFile) {\n            var defer = Q.defer();\n\n            requirejs([syncFile], function(Adapter) {\n\n                // Override Backbone's sync adapter\n                Backbone.ajaxSync = Backbone.sync;\n                Backbone.sync     = Adapter.sync();\n\n                defer.resolve();\n            });\n\n            return defer.promise;\n        },\n\n    };\n\n    return Storage;\n});\n"
  },
  {
    "path": "app/scripts/helpers/title.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'q',\n    'helpers/underscore-util',\n    'marionette',\n    'backbone.radio'\n], function($, Q, _, Marionette, Radio) {\n    'use strict';\n\n    /**\n     * Title helper. It is used to build title from provided arguments and\n     * to change document title.\n     *\n     * Replies to:\n     * 1. channel: `global`, request: `get:title`\n     *\n     * Replies to:\n     * 1. channel: `global`, request: `set:title`\n     */\n    var Controller = Marionette.Object.extend({\n\n        title: {\n            page : '',\n            main : '',\n            db   : '',\n            app  : 'Laverna'\n        },\n\n        initialize: function() {\n            _.bindAll(this, '_makeTitle');\n\n            this.title.db = Radio.request('uri', 'profile');\n            this.title.db = this.title.db === 'notes-db' ? '' : this.title.db;\n\n            this.vent = Radio.channel('global');\n            this.vent.reply('get:title', this.getTitle, this);\n            this.vent.reply('set:title', this.setTitle, this);\n        },\n\n        onDestroy: function() {\n            this.vent\n            .stopReplying('get:title set:title');\n        },\n\n        /**\n         * Updates document title.\n         */\n        setTitle: function(title, type) {\n            /*\n             * If main title needs to be changed, it probably means\n             * that a user is not browsing a note. And that means we\n             * need to reset page title.\n             */\n            if (type === 'main' && this.title.main !== '') {\n                this.title.page = '';\n            }\n\n            type = type || 'page';\n            this.title[type] = title;\n\n            // Prepare an array of titles and remove empty ones\n            title = _.compact(_.values(this.title));\n            document.title = _.cleanXSS(title.join(' - '));\n        },\n\n        getTitle: function(args) {\n            // Filter has additional logic\n            if (args.query && this['_' + args.filter + 'Title']) {\n                return this['_' + args.filter + 'Title'](args)\n                .then(this._makeTitle);\n            }\n            else {\n                return new Q(this._makeTitle(args));\n            }\n        },\n\n        _makeTitle: function(args) {\n            // Translate the title to other languages\n            var title = args.title || (args.filter && args.filter !== 'active' ? args.filter : 'All notes');\n            title = $.t(title.substr(0, 1).toUpperCase() + title.substr(1));\n\n            if (!args.title && args.query && args.filter !== 'search') {\n                title = args.query;\n            }\n\n            // Change document.title and return the title\n            this.vent.request('set:title', title, 'main');\n            return title;\n        },\n\n        /**\n         * Use notebook name as a title instead of ID.\n         */\n        _notebookTitle: function(args) {\n            args.id = args.query;\n\n            return Radio.request('notebooks', 'get:model', args)\n            .then(function(model) {\n                args.title = model.get('name');\n                return args;\n            });\n        }\n\n    });\n\n    Radio.request('init', 'add', 'app:before', function() {\n        new Controller();\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/helpers/underscore-util.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'xss'\n], function(_, cleanXSS) {\n    'use strict';\n\n    /**\n     * Add some helper functions to Underscore.\n     */\n    _.mixin({\n\n        /**\n         * Sanitize HTML to prevent XSS.\n         *\n         * @type string str\n         * @type boolean unescape\n         * @type boolean stripTags\n         */\n        cleanXSS: function(str, unescape, stripTags) {\n\n            /* Unescape the string 2 times because\n             * data from Dropbox is always escaped + we escape them too.\n             */\n            if (unescape === true) {\n                str = _.runTimes(_.unescape, 2, str);\n            }\n\n            // Remove all HTML tags\n            if (stripTags === true) {\n                str = _.stripTags(str);\n            }\n\n            return cleanXSS(str);\n        },\n\n        /**\n         * Invokes the given function n times and returns the last result.\n         * Example:\n         * _.runTimes(_.unescape, 2, 'String');\n         *\n         * @type function func\n         * @type number n\n         */\n        runTimes: function(func, n) {\n            var args = Array.prototype.slice.call(arguments, 2),\n                res;\n\n            res = _.times(n, function() {\n                return func.apply(null, args);\n            });\n\n            return res[res.length - 1];\n        },\n\n        /**\n         * Remove all HTML from string.\n         *\n         * @type string str\n         */\n        stripTags: function(str) {\n            return str.replace(/<\\/?[^>]+>/g, '');\n        },\n\n    });\n\n    return _;\n});\n"
  },
  {
    "path": "app/scripts/helpers/uri.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone',\n    'backbone.radio',\n    'marionette',\n], function(_, Backbone, Radio, Marionette) {\n    'use strict';\n\n    /**\n     * Uri helper. It is a convenient module that we use to navigate\n     * or do some URI related stuff.\n     * It listens to requests on `uri` channel.\n     *\n     * Responds to:\n     * -----------\n     *\n     * Requests:\n     * 1. request: `profile`\n     *    returns current profile name.\n     * 2. request: `link:profile`\n     *    returns a link to a profile.\n     * 3. request: `link`\n     *    generates and returns a link to notes list or to a note.\n     * 4. request: `link:file`\n     *    generate file URL.\n     * 5. request: `navigate`\n     *    navigate to provided URL.\n     * 6. request: `back`\n     *    it navigates to the previous page.\n     */\n    var Uri = Marionette.Object.extend({\n\n        initialize: function() {\n            this.vent    = Radio.channel('uri');\n            this.profile = this.getProfile();\n\n            _.bindAll(this, 'checkProfile');\n            $(window).on('hashchange', this.checkProfile);\n\n            // Replies\n            this.vent.reply({\n                'navigate'      : this.navigate,\n                'back'          : this.navigateBack,\n                'profile'       : this.getProfile,\n                'link:profile'  : this.getProfileLink,\n                'link:file'     : this.getFileLink,\n                'link'          : this.getLink,\n                'get:current'   : this.getRoute,\n            }, this);\n        },\n\n        checkProfile: function() {\n            if (this.getProfile() !== this.profile) {\n                window.location.reload();\n            }\n        },\n\n        /**\n         * Navigate to url\n         */\n        navigate: function(uri, options) {\n            options = options || {};\n            if (typeof options.trigger === 'undefined') {\n                options.trigger = true;\n            }\n\n            // Build URL to notes list or a note\n            if (_.isObject(uri)) {\n                uri = (uri.model || uri.options) ? uri : {options: uri};\n                uri = this.getLink(uri.options, uri.model);\n            }\n\n            // Include profile link\n            if (options.includeProfile) {\n                uri = this.getProfileLink(uri);\n                options.includeProfile = null;\n            }\n\n            Backbone.history.navigate(uri, options);\n        },\n\n        navigateBack: function(url) {\n            var history = window.history;\n            if (history.length === 0) {\n                return this.navigate(url || '/notes', arguments[1]);\n            }\n            history.back();\n        },\n\n        /**\n         * Generate file URL.\n         */\n        getFileLink: function(model, blob) {\n            // Just generate pseudo URL\n            if (!blob) {\n                return '#file:' + model.id;\n            }\n\n            var url = window.URL || window.webkitURL,\n                src = (model.src || model.get('src'));\n\n            return url.createObjectURL(src);\n        },\n\n        /**\n         * Generates a link to a profile\n         */\n        getProfileLink: function(uri, profile) {\n            profile = profile || this.getProfile();\n            uri     = (uri[0] !== '/' ? '/' + uri : uri);\n\n            return !profile ? uri : '/p/' + profile + uri.replace(/\\/?p\\/[^/]*\\//, '/');\n        },\n\n        /**\n         * Returns current profile's name\n         */\n        getProfile: function() {\n            var profile = document.location.hash.match(/\\/?p\\/([^/]*)\\//);\n            return (!profile ? profile : profile[profile.index]);\n        },\n\n        /**\n         * Returns current route\n         */\n        getRoute: function() {\n            return Backbone.history.fragment;\n        },\n\n        /**\n         * Generates a link from provided options\n         */\n        getLink: function(options, model) {\n            options = _.extend({}, options || {});\n            var url = '/notes',\n                filters = {\n                    filter  : '/f/',\n                    query   : '/q/',\n                    page    : '/p'\n                };\n\n            options.page = isNaN(options.page) ? 0 : options.page;\n\n            _.each(filters, function(value, filter) {\n                if (_.has(options, filter) && options[filter]) {\n                    url += value + options[filter];\n                }\n            });\n\n            url += model ? '/show/' + model.id : '';\n            return this.getProfileLink(url, options.profile);\n        }\n\n    });\n\n    /**\n     * Add a new initializer\n     */\n    Radio.request('init', 'add', 'app:before', function() {\n        new Uri();\n    });\n\n    return Uri;\n});\n"
  },
  {
    "path": "app/scripts/init.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'jquery',\n    'q',\n    'fastclick',\n    'hammerjs',\n    'helpers/radio.shim',\n    'backbone.radio',\n    'app',\n    'initializers',\n    'bootstrap',\n    'jHammer'\n], function($, Q, FastClick, Hammer, shim, Radio, App) {\n    'use strict';\n\n    var hash = document.location.hash;\n    Radio.reply('global', 'hash:original', function() {\n        return hash;\n    });\n\n    console.time('App');\n\n    // Remove 300ms delay\n    FastClick.attach(document.body);\n\n    // Enable text selection\n    delete Hammer.defaults.cssProps.userSelect;\n\n    // Load all modules then start an application\n    requirejs([\n        // Helpers\n        'helpers/storage',\n        'helpers/uri',\n        'helpers/title',\n        'helpers/i18next',\n        'helpers/keybindings',\n\n        // Classes\n        'moduleLoader',\n        'classes/encryption',\n\n        // Collection modules\n        'collections/modules/notes',\n        'collections/modules/notebooks',\n        'collections/modules/tags',\n        'collections/modules/files',\n        'collections/modules/configs',\n\n        // Apps\n        'apps/confirm/appConfirm',\n        'apps/encryption/appEncrypt',\n        'apps/navbar/appNavbar',\n        'apps/notes/appNote',\n        'apps/notebooks/appNotebooks',\n        'apps/settings/appSettings',\n        'apps/help/appHelp',\n\n        // Modules\n        'modules/markdown/module',\n        'modules/codemirror/module',\n        'modules/linkDialog/module',\n        'modules/fileDialog/module',\n        'modules/importExport/module'\n    ], function(storage) {\n        // Get profile name from location hash\n        var profile = document.location.hash.match(/\\/?p\\/([^/]*)\\//);\n        profile     = (!profile ? profile : profile[profile.index]);\n\n        console.warn('prof', profile);\n\n        return storage.check()\n        .then(function() {\n            return Radio.request('configs', 'get:all', {profile: profile});\n        })\n        // Load optional modules\n        .then(function() {\n            return Radio.request('init', 'start', 'load:modules')();\n        })\n        .then(Radio.request('init', 'start', 'app:before app auth module'))\n        .then(function() {\n            console.log('modules are loaded');\n            App.start();\n        })\n        .fail(function(e) {\n            console.error('Error', e);\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "app/scripts/initializers.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'backbone.radio',\n    'marionette'\n], function(_, Q, Radio, Marionette) {\n    'use strict';\n\n    /**\n     * This class is used to add and execute asynchronous initializers.\n     * It is very convenient when writing modules or writing sub apps.\n     * Also it ensures that every module or sub app is ready\n     * before the app starts.\n     *\n     * App initializers will be executed first. Then, module initializers.\n     *\n     * Adding new initializers:\n     * Radio.request('init', 'add', '[app|app:before|module]', function() {});\n     *\n     * Executing initializers\n     * Radio.request('init', 'start', '[app|app:before|module]', args);\n     */\n    var Initializers = Marionette.Object.extend({\n\n        initialize: function() {\n            this._inits = {};\n\n            Radio.channel('init')\n            .reply('add', this.addInit, this)\n            .reply('start', this.executeInits, this);\n        },\n\n        addInit: function(name, initializer) {\n            this._inits[name] = this._inits[name] || [];\n            this._inits[name].push(initializer);\n        },\n\n        /**\n         * Executes all the initializers\n         */\n        executeInits: function(types, args) {\n            var self  = this;\n\n            types = types.split(' ');\n            args  = Array.prototype.slice.call(arguments, 1);\n\n            return function() {\n                var promises = [];\n\n                // Execute every init one after another\n                _.each(types, function(type) {\n                    promises.push(function() {\n                        return self._executeInit(type, args);\n                    });\n                });\n\n                return _.reduce(promises, Q.when, new Q());\n            };\n        },\n\n        /**\n         * Executes an initializer\n         */\n        _executeInit: function(type, args) {\n            var self     = this,\n                promises = [];\n\n            // Execute all the functions asynchronously\n            _.each(self._inits[type], function(fnc) {\n                promises.push(function() {\n                    return new Q(fnc.apply(null, args));\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q());\n        }\n\n    });\n\n    return new Initializers();\n});\n"
  },
  {
    "path": "app/scripts/main.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global requirejs, requireNode */\nrequirejs.config({\n\n    // Find all nested dependencies\n    findNestedDependencies: true,\n    waitSeconds: 10,\n\n    nodeRequire: (typeof requireNode === 'undefined' ? null : requireNode),\n\n    packages: [\n        // Codemirror editor\n        {\n            name     : 'codemirror',\n            location : '../bower_components/codemirror',\n            main     : 'lib/codemirror'\n        },\n        // Prismjs\n        {\n            name     : 'prism',\n            location : '../bower_components/prism',\n            main     : 'bundle'\n        },\n        // Xregexp\n        {\n            name     : 'xregexp',\n            location : '../bower_components/xregexp/src',\n            main     : 'xregexp'\n        }\n    ],\n    paths: {\n        sjcl                  : '../bower_components/sjcl/sjcl',\n        text                  : '../bower_components/requirejs-text/text',\n        jquery                : '../bower_components/jquery/dist/jquery',\n        q                     : '../bower_components/q/q',\n        bootstrap             : '../bower_components/bootstrap/dist/js/bootstrap.min',\n        i18next               : '../bower_components/i18next/i18next',\n        i18nextXHRBackend     : '../bower_components/i18next-xhr-backend/i18nextXHRBackend',\n\n        // Backbone\n        underscore            : '../bower_components/underscore/underscore',\n        backbone              : '../bower_components/backbone/backbone',\n        marionette            : '../bower_components/backbone.marionette/lib/core/backbone.marionette',\n        'backbone.radio'      : '../bower_components/backbone.radio/build/backbone.radio.min',\n        'backbone.babysitter' : '../bower_components/backbone.babysitter/lib/backbone.babysitter',\n        fuse                  : '../bower_components/fuse/src/fuse',\n\n        // Mousetrap\n        'mousetrap'           : '../bower_components/mousetrap/mousetrap',\n        'mousetrap.pause'     : '../bower_components/mousetrap/plugins/pause/mousetrap-pause',\n        'mousetrap.global'    : '../bower_components/mousetrap/plugins/global-bind/mousetrap-global-bind',\n\n        // Storage adapters\n        localforage           : '../bower_components/localforage/dist/localforage',\n        remotestorage         : '../bower_components/remotestorage.js/release/stable/remotestorage',\n        bluebird              : '../bower_components/bluebird/js/browser/bluebird.min',\n        tv4                   : '../bower_components/tv4/tv4',\n        dropbox               : 'helpers/Dropbox-sdk.min',\n\n        // Markdown\n        'markdown-it'         : '../bower_components/markdown-it/dist/markdown-it.min',\n        'markdown-it-san'     : '../bower_components/markdown-it-sanitizer/dist/markdown-it-sanitizer.min',\n        'markdown-it-hash'    : '../bower_components/markdown-it-hashtag/dist/markdown-it-hashtag.min',\n        'markdown-it-math'    : '../bower_components/markdown-it-math/dist/markdown-it-math.min',\n        'markdown-it-imsize'  : '../bower_components/markdown-it-imsize/dist/markdown-it-imsize.min',\n        'to-markdown'         : '../bower_components/to-markdown/src/to-markdown',\n\n        // Others\n        xss                   : '../bower_components/xss/dist/xss',\n        mathjax               : '../bower_components/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML',\n        prettify              : '../bower_components/google-code-prettify/src/prettify',\n        dropzone              : '../bower_components/dropzone/dist/dropzone-amd-module',\n        toBlob                : '../bower_components/blueimp-canvas-to-blob/js/canvas-to-blob',\n        blobjs                : '../bower_components/Blob/Blob',\n        fileSaver             : '../bower_components/FileSaver/FileSaver',\n        enquire               : '../bower_components/enquire/dist/enquire.min',\n        hammerjs              : '../bower_components/hammerjs/hammer',\n        jHammer               : '../bower_components/jquery-hammerjs/jquery.hammer',\n        fastclick             : '../bower_components/fastclick/lib/fastclick',\n        devicejs              : '../bower_components/device.js/lib/device.min',\n        jszip                 : '../bower_components/jszip/dist/jszip',\n\n        // Aliases\n        'modalRegion'         : 'views/modal',\n        'brandRegion'         : 'views/brand',\n        'apps'                : 'apps',\n        'locales'             : '../locales'\n    },\n    map: {\n        '*': {\n            'backbone.wreqr' : 'backbone.radio'\n        }\n    },\n    shim: {\n        // Backbone\n        underscore: {\n            exports: '_'\n        },\n        fileSaver: {\n            exports: 'saveAs',\n        },\n        backbone: {\n            deps: ['underscore', 'jquery'],\n            exports: 'Backbone'\n        },\n\n        // Storage adapters\n        dropbox: {\n            exports: 'Dropbox'\n        },\n        'remotestorage': {\n            exports: 'RemoteStorage',\n            deps: [\n                'tv4',\n                'bluebird',\n            ]\n        },\n        tv4: {\n            exports: 'tv4'\n        },\n\n        // Markdown\n        'to-markdown': {\n            exports: 'toMarkdown'\n        },\n\n        // Xregexp\n        'xregexp/xregexp': {\n            exports: 'XRegExp'\n        },\n        'xregexp/addons/unicode/unicode-base': {\n            deps: ['xregexp/xregexp'],\n            exports: 'XRegExp'\n        },\n        'xregexp/addons/unicode/unicode-categories': {\n            deps: [\n                'xregexp/addons/unicode/unicode-base'\n            ],\n            exports: 'XRegExp'\n        },\n\n        // Others\n        sjcl: {\n            exports: 'sjcl'\n        },\n        'prism/bundle': {\n            exports: 'Prism'\n        },\n        xss: {\n            exports: 'filterXSS'\n        },\n        bootstrap: {\n            deps: ['jquery']\n        },\n        'mathjax': {\n            exports: 'MathJax'\n        },\n        devicejs: {\n            exports: 'device'\n        },\n        prettify: {\n            exports: 'PR'\n        }\n    }\n});\n\n// Starting point\nrequirejs(['init']);\n"
  },
  {
    "path": "app/scripts/migrate.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global requirejs */\nrequirejs.config({\n    paths: {\n        q           : '../bower_components/q/q',\n        underscore  : '../bower_components/underscore/underscore',\n        localforage : '../bower_components/localforage/dist/localforage',\n        sjcl        : '../bower_components/sjcl/sjcl',\n    }\n});\n\nrequirejs(['helpers/migrate'], function(Migrate) {\n    'use strict';\n\n    new Migrate().init()\n    .then(function() {\n        document.location.href = document.location.href.toString().replace('migrate.html', '');\n    })\n    .fail(function(e) {\n        console.error('Migrate Error:', e);\n    });\n});\n"
  },
  {
    "path": "app/scripts/models/config.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone'\n], function(_, Backbone) {\n    'use strict';\n\n    var Config = Backbone.Model.extend({\n        idAttribute: 'name',\n\n        profileId : 'notes-db',\n        storeName : 'configs',\n\n        defaults: {\n            'name'  : '',\n            'value' : ''\n        },\n\n        validate: function(attrs) {\n            var errors = [];\n\n            if (attrs.name === '') {\n                errors.push('name');\n            }\n\n            if (errors.length > 0) {\n                return errors;\n            }\n        },\n\n        /**\n         * Switch to another profile\n         */\n        changeDB: function(id) {\n            this.profileId = id;\n        },\n\n        /**\n         * Parse the value of a model\n         */\n        getValueJSON: function() {\n            return JSON.parse(this.get('value'));\n        },\n\n        createProfile: function(name) {\n            if (!name) {\n                return;\n            }\n\n            var value = JSON.parse(this.get('value'));\n\n            if (_.contains(value, name) === false) {\n                value.push(name);\n                return this.save({value: JSON.stringify(value)});\n            }\n        },\n\n        removeProfile: function(name) {\n            if (!name) {\n                return;\n            }\n\n            var value = JSON.parse(this.get('value'));\n\n            if (_.contains(value, name) === true) {\n                value = _.without(value, name);\n                window.indexedDB.deleteDatabase(name);\n                return this.save({value: JSON.stringify(value)});\n            }\n        },\n\n        /**\n         * @return bool\n         */\n        isPassword: function(data) {\n            return (\n                (\n                    this.get('name') === 'encryptPass' || data.name === 'encryptPass'\n                ) &&\n                (\n                    typeof data.value !== 'object' &&\n                    data.value !== this.get('value').toString()\n                )\n            );\n        }\n\n    });\n\n    return Config;\n\n});\n"
  },
  {
    "path": "app/scripts/models/file.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone'\n], function(_, Backbone) {\n    'use strict';\n\n    /**\n     * Files model\n     */\n    var File = Backbone.Model.extend({\n        idAttribute: 'id',\n\n        profileId : 'notes-db',\n        storeName : 'files',\n\n        defaults: {\n            type         : 'files',\n            id           : undefined,\n            name         : '',\n            src          : '',\n            fileType     : '',\n            trash        : 0,\n            created      : 0,\n            updated      : 0\n        },\n\n        validate: function(attrs) {\n            var errors = [];\n\n            if (attrs.src === '') {\n                errors.push('src');\n            }\n            if (attrs.fileType === '') {\n                errors.push('fileType');\n            }\n\n            if (errors.length > 0) {\n                return errors;\n            }\n        },\n\n        setEscape: function(data) {\n            if (data.name) {\n                data.name = _.cleanXSS(data.name, true);\n            }\n\n            this.set(data);\n            return this;\n        }\n\n    });\n\n    return File;\n});\n"
  },
  {
    "path": "app/scripts/models/note.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone'\n], function(_, Backbone) {\n    'use strict';\n\n    /**\n     * Notes model\n     */\n    var Model = Backbone.Model.extend({\n\n        idAttribute: 'id',\n\n        profileId : 'notes-db',\n        storeName : 'notes',\n\n        defaults: {\n            'type'          : 'notes',\n            'id'            :  undefined,\n            'title'         :  '',\n            'content'       :  '',\n            'taskAll'       :  0,\n            'taskCompleted' :  0,\n            'created'       :  0,\n            'updated'       :  0,\n            'notebookId'    :  '0',\n            'tags'          :  [],\n            'isFavorite'    :  0,\n            'trash'         :  0,\n            'files'         :  []\n        },\n\n        encryptKeys: [\n            'title',\n            'content',\n            'tags',\n            'tasks'\n        ],\n\n        validate: function(attrs) {\n            // It's not neccessary to validate when a model is about to be removed\n            if (attrs.trash && Number(attrs.trash) === 2) {\n                return;\n            }\n\n            var errors = [];\n            if (!_.isUndefined(attrs.title) && !attrs.title.trim().length) {\n                errors.push('title');\n            }\n\n            if (errors.length > 0) {\n                return errors;\n            }\n        },\n\n        toggleFavorite: function() {\n            return {isFavorite: (this.get('isFavorite') === 1) ? 0 : 1};\n        },\n\n        /**\n         * Purify user inputs\n         */\n        setEscape: function(data) {\n\n            if (data.title) {\n                data.title = _.cleanXSS(data.title, true);\n            }\n            if (data.content) {\n                data.content = _.cleanXSS(data.content, true);\n            }\n\n            this.set(data);\n            return this;\n        }\n\n    });\n\n    return Model;\n});\n"
  },
  {
    "path": "app/scripts/models/notebook.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone'\n], function(_, Backbone) {\n    'use strict';\n\n    var Model = Backbone.Model.extend({\n        idAttribute: 'id',\n\n        profileId : 'notes-db',\n        storeName : 'notebooks',\n\n        defaults: {\n            'type'         : 'notebooks',\n            'id'           : undefined,\n            'parentId'     : '0',\n            'name'         : '',\n            'count'        : 0,\n            'trash'        : 0,\n            'created'      : 0,\n            'updated'      : 0\n        },\n\n        encryptKeys: ['name'],\n\n        validate: function(attrs) {\n            // It's not neccessary to validate when a model is about to be removed\n            if (attrs.trash && Number(attrs.trash) === 2) {\n                return;\n            }\n\n            var errors = [];\n            if (attrs.name === '') {\n                errors.push('name');\n            }\n            if (attrs.parentId === attrs.id) {\n                errors.push('parentId');\n            }\n            if (errors.length > 0) {\n                return errors;\n            }\n        },\n\n        initialize: function() {\n            if (typeof this.id === 'number') {\n                this.set('id', this.id.toString());\n                this.set('parentId', this.get('parentId').toString());\n            }\n        },\n\n        setEscape: function(data) {\n            if (data.name) {\n                data.name = _.cleanXSS(data.name, true);\n            }\n\n            this.set(data);\n            return this;\n        },\n\n    });\n\n    return Model;\n});\n"
  },
  {
    "path": "app/scripts/models/tag.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'jquery',\n    'underscore',\n    'backbone'\n], function($, _, Backbone) {\n    'use strict';\n\n    /**\n     * Tags model\n     */\n    var Tag = Backbone.Model.extend({\n        idAttribute: 'id',\n\n        profileId : 'notes-db',\n        storeName : 'tags',\n\n        defaults: {\n            'type'         : 'tags',\n            'id'           : undefined,\n            'name'         : '',\n            'count'        : '',\n            'trash'        : 0,\n            'created'      : 0,\n            'updated'      : 0\n        },\n\n        encryptKeys: ['name'],\n\n        setEscape: function(data) {\n            if (data.name) {\n                data.name = _.cleanXSS(data.name, true);\n            }\n\n            this.set(data);\n            return this;\n        },\n\n        /**\n         * Validates a tag.\n         * @type array\n         */\n        validate: function(attrs) {\n            // It's not neccessary to validate when a model is about to be removed\n            if (attrs.trash && Number(attrs.trash) === 2) {\n                return;\n            }\n\n            var errors = [];\n            if (!_.isUndefined(attrs.name) && !attrs.name.trim().length) {\n                errors.push('name');\n            }\n\n            if (errors.length > 0) {\n                return errors;\n            }\n        },\n\n    });\n\n    return Tag;\n});\n"
  },
  {
    "path": "app/scripts/moduleLoader.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requirejs */\ndefine([\n    'underscore',\n    'q',\n    'backbone.radio',\n    'text!modules/modules.json'\n], function(_, Q, Radio, modules) {\n    'use strict';\n\n    /**\n     * Module loader.\n     */\n    var ModuleLoader = {\n\n        init: function() {\n            var platform = Radio.request('global', 'platform');\n\n            // List of available modules\n            modules = _.filter(JSON.parse(modules), function(m) {\n                return _.indexOf(m.platforms, platform) > -1;\n            });\n            Radio.reply('global', 'modules', modules);\n\n            return this.load();\n        },\n\n        /**\n         * Load modules.\n         */\n        load: function() {\n            var defer = Q.defer();\n\n            requirejs(this.get(), function() {\n                defer.resolve();\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Return a list of modules which need to be loaded.\n         *\n         * @return array\n         */\n        get: function() {\n            var modules2Load = Radio.request('configs', 'get:config', 'modules');\n\n            modules2Load = _.map(modules2Load, function(name) {\n                if (!name || !_.findWhere(modules, {id: name})) {\n                    return '';\n                }\n\n                return 'modules/' + name + '/module';\n            });\n\n            switch (Radio.request('configs', 'get:config', 'cloudStorage')) {\n                case 'remotestorage':\n                    modules2Load.push('modules/remotestorage/module');\n                    break;\n\n                case 'dropbox':\n                    modules2Load.push('modules/dropbox/module');\n                    break;\n\n                default:\n                    break;\n            }\n\n            return _.compact(modules2Load);\n        },\n    };\n\n    // Register an initializer\n    Radio.request('init', 'add', 'load:modules', function() {\n        return ModuleLoader.init();\n    });\n\n    return ModuleLoader;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/codemirror/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n\t'jquery',\n    'marionette',\n    'backbone.radio',\n    'codemirror/lib/codemirror',\n    'modules/codemirror/views/editor',\n    'codemirror/mode/gfm/gfm',\n    'codemirror/mode/markdown/markdown',\n    'codemirror/addon/edit/continuelist',\n    'codemirror/addon/mode/overlay',\n    'codemirror/keymap/vim',\n    'codemirror/keymap/emacs',\n    'codemirror/keymap/sublime'\n], function(_, $, Marionette, Radio, CodeMirror, View) {\n    'use strict';\n\n    /**\n     * Codemirror module.\n     * Regex and WYSIWG button functions are based on simplemde-markdown-editor:\n     * https://github.com/NextStepWebs/simplemde-markdown-editor\n     */\n    var Controller = Marionette.Object.extend({\n\n        marks: {\n            strong: {\n                tag   : ['**', '__'],\n                start : /(\\*\\*|__)(?![\\s\\S]*(\\*\\*|__))/,\n                end   : /(\\*\\*|__)/,\n            },\n            em: {\n                tag   : ['*', '_'],\n                start : /(\\*|_)(?![\\s\\S]*(\\*|_))/,\n                end   : /(\\*|_)/,\n            },\n            strikethrough: {\n                tag   : ['~~'],\n                start : /(\\*\\*|~~)(?![\\s\\S]*(\\*\\*|~~))/,\n                end   : /(\\*\\*|~~)/,\n            },\n            'code': {\n                tag    : '```\\r\\n',\n                tagEnd : '\\r\\n```',\n            },\n            'unordered-list': {\n                replace : /^(\\s*)(\\*|\\-|\\+)\\s+/,\n                tag     : '* ',\n            },\n            'ordered-list': {\n                replace : /^(\\s*)\\d+\\.\\s+/,\n                tag     : '1. ',\n            },\n        },\n\n        initialize: function() {\n            _.bindAll(this, 'onChange', 'onScroll', 'onCursor', 'boldAction', 'italicAction', 'linkAction', 'headingAction', 'attachmentAction', 'codeAction', 'hrAction', 'listAction', 'numberedListAction');\n\n            // Get configs\n            this.configs = Radio.request('configs', 'get:object');\n\n            // Initialize the view\n            this.view = new View({\n                model   : Radio.request('notesForm', 'model'),\n                configs : this.configs\n            });\n\n            this.view.once('dom:refresh', this.initEditor, this);\n\n            // Events\n            this.listenTo(this.view, 'editor:action', this.onViewAction);\n            this.listenTo(Radio.channel('notesForm'), 'set:mode', this.changeMode);\n            this.listenTo(Radio.channel('editor'), 'focus', this.focus);\n\n            // Show the view and render Pagedown editor\n            Radio.request('notesForm', 'show:editor', this.view);\n\n            Radio.reply('editor', {\n                'get:data'      : this.getData,\n                'generate:link' : this.generateLink,\n                'generate:image': this.generateImage\n            }, this);\n\n\t\t\t// Init footer to show current line numbers\n\t\t\t// but first of hide it, because when you open/add a note\n\t\t\t// the title is focused, not the editor\n\t\t\tthis.footer = $('#editor--footer');\n\t\t\tthis.footer.hide();\n\n        },\n\n        onDestroy: function() {\n            Radio.stopReplying('editor', 'get:data');\n        },\n\n        initEditor: function() {\n            this.editor = CodeMirror.fromTextArea(document.getElementById('editor--input'), {\n                mode          : {\n                    name        : 'gfm',\n                    gitHubSpice : false\n                },\n\t\t\t\tkeyMap: this.configs.textEditor || 'default',\n                lineNumbers   : false,\n                matchBrackets : true,\n                lineWrapping  : true,\n                indentUnit    : parseInt(this.configs.indentUnit, 10),\n                extraKeys     : {\n                    'Cmd-B'  : this.boldAction,\n                    'Ctrl-B' : this.boldAction,\n\n                    'Cmd-I'  : this.italicAction,\n                    'Ctrl-I' : this.italicAction,\n\n                    'Cmd-H'  : this.headingAction,\n                    'Ctrl-H' : this.headingAction,\n\n                    'Cmd-L'  : this.linkAction,\n                    'Ctrl-L' : this.linkAction,\n\n                    'Cmd-K'  : this.codeAction,\n                    'Ctrl-K' : this.codeAction,\n\n                    'Cmd-O'  : this.numberedListAction,\n                    'Ctrl-O' : this.numberedListAction,\n\n                    'Cmd-U'  : this.listAction,\n                    'Ctrl-U' : this.listAction,\n\n                    // Ctrl+G - attach file\n                    'Cmd-G'  : this.attachmentAction,\n                    'Ctrl-G' : this.attachmentAction,\n                    \n                    // Shift+Ctrl+- - divider\n                    'Shift-Cmd--'   : this.hrAction,\n                    'Shift-Ctrl--'  : this.hrAction,\n\n\t\t\t\t\t// Ctrl+. - indent line\n\t\t\t\t\t'Ctrl-.' \t\t: 'indentMore',\n\t\t\t\t\t'Shift-Ctrl-.' \t: 'indentLess',\n\t\t\t\t\t'Cmd-.' \t\t: 'indentMore',\n\t\t\t\t\t'Shift-Cmd-.'\t: 'indentLess',\n\n                    'Enter' : 'newlineAndIndentContinueMarkdownList',\n\n                }\n            });\n\n            window.dispatchEvent(new Event('resize'));\n            this.editor.on('change', this.onChange);\n            this.editor.on('scroll', this.onScroll);\n            this.editor.on('cursorActivity', this.onCursor);\n\n            // Show the preview\n            this.updatePreview();\n        },\n\n        changeMode: function(mode) {\n            window.dispatchEvent(new Event('resize'));\n            this.view.trigger('change:mode', mode);\n        },\n\n        /**\n         * Update the preview.\n         */\n        updatePreview: function() {\n            var self = this,\n                data = _.pick(this.view.model, 'attributes', 'files');\n\n            return Radio.request('markdown', 'render', _.extend({}, data, {\n                attributes: {\n                    content: this.editor.getValue()\n                }\n            }))\n            .then(function(content) {\n                self.view.trigger('editor:change', content);\n            });\n        },\n\n        /**\n         * Text in the editor changed.\n         */\n        onChange: function() {\n\n            // Update the preview\n            this.updatePreview();\n\n            // Trigger autosave\n            this.autoSave();\n        },\n\n        /**\n         * Editor's cursor position changed.\n         */\n        onCursor: function() {\n            var state  = this.getState();\n            this.$btns = this.$btns || $('.editor--btns .btn');\n\n            // Make a specific button active depending on the type of the element under cursor\n            this.$btns.removeClass('btn-primary');\n            for (var i = 0; i < state.length; i++) {\n                this['$btn' + state[i]] = this['$btn' + state[i]] || $('.editor--btns [data-state=\"' + state[i] + '\"]');\n                this['$btn' + state[i]].addClass('btn-primary');\n            }\n\n\t\t\t// Update lines in footer\n\t\t\tthis.footer.show();\n\t\t\tvar currentLine = this.editor.getCursor('start').line + 1;\n\t\t\tvar numberOfLines = this.editor.lineCount();\n\t\t\tthis.footer.html($.t('Line of',\n\t\t\t\t{currentLine: currentLine, numberOfLines: numberOfLines}));\n        },\n\n        /**\n         * Trigger 'save:auto' event.\n         */\n        autoSave: _.debounce(function() {\n            Radio.trigger('notesForm', 'save:auto');\n        }, 1000),\n\n        /**\n         * Synchronize the editor's scroll position with the preview's.\n         */\n        onScroll: _.debounce(function(e) {\n\n            // Don't do any computations\n            if (!e.doc.scrollTop) {\n                this.view.ui.previewScroll.scrollTop(0);\n                return;\n            }\n\n            var info       = this.editor.getScrollInfo(),\n                lineNumber = this.editor.lineAtHeight(info.top, 'local'),\n                range      = this.editor.getRange({line: 0, ch: null}, {line: lineNumber, ch: null}),\n                self       = this,\n                fragment,\n                temp,\n                lines,\n                els;\n\n            Radio.request('markdown', 'render', range)\n            .then(function(html) {\n\n                // Create a fragment and attach rendered HTML\n                fragment       = document.createDocumentFragment();\n                temp           = document.createElement('div');\n                temp.innerHTML = html;\n                fragment.appendChild(temp);\n\n                // Get all elements in both the fragment and the preview\n                lines = temp.children;\n                els   = self.view.ui.preview[0].children;\n\n                // Get from the preview the last visible element of the editor\n                var newPos = els[lines.length].offsetTop;\n\n                /**\n                 * If the scroll position is on the same element,\n                 * change it according to the difference of scroll positions in the editor.\n                 */\n                if (self.scrollTop && self.scrollPos === newPos) {\n                    self.view.ui.previewScroll.scrollTop(self.view.ui.previewScroll.scrollTop() + (e.doc.scrollTop - self.scrollTop));\n                    self.scrollTop = e.doc.scrollTop;\n                    return;\n                }\n\n                // Scroll to the last visible element's position\n                self.view.ui.previewScroll.animate({\n                    scrollTop: newPos\n                }, 70, 'swing');\n\n                self.scrollPos = newPos;\n                self.scrollTop = e.doc.scrollTop;\n            });\n        }, 10),\n\n        /**\n         * If the view triggered some action event, call a suitable function.\n         * For instance, when action='bold', call boldAction method.\n         */\n        onViewAction: function(action) {\n            action = action + 'Action';\n\n            if (this[action]) {\n                this[action]();\n            }\n        },\n\n        /**\n         * Return data from the editor.\n         */\n        getData: function() {\n            var content = this.editor.getValue();\n\n            return Radio.request('markdown', 'parse', content)\n            .then(function(env) {\n                return _.extend(\n                    _.pick(env, 'tags', 'tasks', 'taskCompleted', 'taskAll', 'files'),\n                    {content: content}\n                );\n            });\n        },\n\n        /**\n         * Return state of the element under the cursor.\n         */\n        getState: function(pos) {\n            pos      = pos || this.editor.getCursor('start');\n            var stat = this.editor.getTokenAt(pos);\n\n            if (!stat.type) {\n                return [];\n            }\n\n            stat.type = stat.type.split(' ');\n\n            if (_.indexOf(stat.type, 'variable-2') !== -1) {\n                if (/^\\s*\\d+\\.\\s/.test(this.editor.getLine(pos.line))) {\n                    stat.type.push('ordered-list');\n                }\n                else {\n                    stat.type.push('unordered-list');\n                }\n            }\n\n\n            return stat.type;\n        },\n\n        /**\n         * Toggle Markdown block.\n         */\n        toggleBlock: function(type) {\n            var stat  = this.getState(),\n                start = this.editor.getCursor('start'),\n                end   = this.editor.getCursor('end'),\n\t\t        text,\n\t\t        startText,\n\t\t        endText;\n\n            // Text is already [strong|italic|etc]\n            if (_.indexOf(stat, type) !== -1) {\n                text      = this.editor.getLine(start.line);\n                startText = text.slice(0, start.ch);\n                endText   = text.slice(start.ch);\n\n                // Remove Markdown tags from the text\n                startText = startText.replace(this.marks[type].start, '');\n                endText   = endText.replace(this.marks[type].end, '');\n\n                this.replaceRange(startText + endText, start.line);\n\n                start.ch -= this.marks[type].tag[0].length;\n                end.ch   -= this.marks[type].tag[0].length;\n            }\n            else {\n                text = this.editor.getSelection();\n\n\t\t\t    for (var i = 0; i < this.marks[type].tag.length - 1; i++) {\n                    text = text.split(this.marks[type].tag[i]).join('');\n\t\t\t    }\n\n\t\t        this.editor.replaceSelection(this.marks[type].tag[0] + text + this.marks[type].tag[0]);\n\n                start.ch += this.marks[type].tag[0].length;\n                end.ch    = start.ch + text.length;\n            }\n\n            this.editor.setSelection(start, end);\n            this.editor.focus();\n        },\n\n        /**\n         * Make selected text strong.\n         */\n        boldAction: function() {\n            this.toggleBlock('strong');\n        },\n\n        /**\n         * Make selected text italicized.\n         */\n        italicAction: function() {\n            this.toggleBlock('em');\n        },\n\n        /**\n         * Create headings.\n         */\n        headingAction: function() {\n            var start = this.editor.getCursor('start'),\n                end   = this.editor.getCursor('end');\n\n            for (var i = start.line; i <= end.line; i++) {\n                this.toggleHeading(i);\n            }\n        },\n\n        /**\n         * Show a dialog to attach images or files.\n         */\n        attachmentAction: function() {\n            var self   = this,\n                dialog = Radio.request('editor', 'show:attachment', this.view.model);\n\n            if (!dialog) {\n                return;\n            }\n\n            dialog.then(function(text) {\n                if (!text || !text.length) {\n                    return;\n                }\n\n                self.editor.replaceSelection(text, true);\n                self.editor.focus();\n            });\n        },\n\n        /**\n         * Show a link dialog.\n         */\n        linkAction: function() {\n            var self   = this,\n                dialog = Radio.request('editor', 'show:link');\n\n            if (!dialog) {\n                return;\n            }\n\n            dialog.then(function(link) {\n                if (!link || !link.length) {\n                    return;\n                }\n\n                var cursor = self.editor.getCursor('start'),\n                    text   = self.editor.getSelection() || 'Link';\n\n                self.editor.replaceSelection('[' + text + '](' + link + ')');\n                self.editor.setSelection(\n                    {line: cursor.line, ch: cursor.ch + 1},\n                    {line: cursor.line, ch: cursor.ch + text.length + 1}\n                );\n                self.editor.focus();\n            });\n        },\n\n        /**\n         * Create a divider.\n         */\n        hrAction: function() {\n            var start = this.editor.getCursor('start');\n            this.editor.replaceSelection('\\r\\r-----\\r\\r');\n\n            start.line += 4;\n            start.ch    = 0;\n            this.editor.setSelection( start, start );\n            this.editor.focus();\n        },\n\n        /**\n         * Create a code block.\n         */\n        codeAction: function() {\n            var state = this.getState(),\n                start = this.editor.getCursor('start'),\n                end   = this.editor.getCursor('end'),\n                text;\n\n            if (_.indexOf(state, 'code') !== -1) {\n                return;\n            }\n            else {\n                text = this.editor.getSelection();\n                this.editor.replaceSelection(this.marks.code.tag + text + this.marks.code.tagEnd);\n            }\n            this.editor.setSelection({line: start.line + 1, ch: start.ch}, {line: end.line + 1, ch: end.ch});\n            this.editor.focus();\n        },\n\n        replaceRange: function(text, line) {\n            this.editor.replaceRange(text, {\n                line : line,\n                ch   : 0\n            }, {\n                line : line,\n                ch   : 99999999999999\n            });\n            this.editor.focus();\n        },\n\n        /**\n         * Convert a line to a headline.\n         */\n        toggleHeading: function(i) {\n            var text       = this.editor.getLine(i),\n\t\t\t    headingLvl = text.search(/[^#]/);\n\n            // Create a default headline\n            if (headingLvl === -1) {\n                text = '# Heading';\n\n                this.replaceRange(text, i);\n                return this.editor.setSelection(\n                    {line: i, ch: 2},\n                    {line: i, ch: 9}\n                );\n            }\n\n            // Increase headline level up to 6th\n            if (headingLvl < 6) {\n                text = headingLvl > 0 ? text.substr(headingLvl + 1) : text;\n                text = new Array(headingLvl + 2).join('#') + ' ' + text;\n            }\n            else {\n                text = text.substr(headingLvl + 1);\n            }\n\n            this.replaceRange(text, i);\n        },\n\n        /**\n         * Convert selected text to unordered list.\n         */\n        listAction: function() {\n            this.toggleLists('unordered-list');\n        },\n\n        /**\n         * Convert selected text to ordered list.\n         */\n        numberedListAction: function() {\n            this.toggleLists('ordered-list', 1);\n        },\n\n        /**\n         * Convert several selected lines to ordered or unordered lists.\n         */\n        toggleLists: function(type, order) {\n            var state = this.getState(),\n                start = this.editor.getCursor('start'),\n                end   = this.editor.getCursor('end');\n\n            // Convert each line to list\n            _.each(new Array(end.line - start.line + 1), function(val, i) {\n                this.toggleList(type, start.line + i, state, order);\n                if (order) {\n                    order++;\n                }\n            }, this);\n        },\n\n        /**\n         * Convert selected text to an ordered or unordered list.\n         */\n        toggleList: function(name, line, state, order) {\n            var text = this.editor.getLine(line);\n\n            // If it is a list, convert it to normal text\n            if (_.indexOf(state, name) !== -1) {\n                text = text.replace(this.marks[name].replace, '$1');\n            }\n            else if (order) {\n                text = order + '. ' + text;\n            }\n            else {\n                text = this.marks[name].tag + text;\n            }\n\n            this.replaceRange(text, line);\n        },\n\n        /**\n         * Redo the last action in Codemirror.\n         */\n        redoAction: function() {\n            this.editor.redo();\n        },\n\n        /**\n         * Undo the last action in Codemirror.\n         */\n        undoAction: function() {\n            this.editor.undo();\n        },\n\n        /**\n         * Focus on the editor.\n         */\n        focus: function() {\n            this.editor.focus();\n        },\n\n        generateLink: function(data) {\n            return '[' + data.text + ']' + '(' + data.url + ')';\n        },\n\n        generateImage: function(data) {\n            return '!' + this.generateLink(data);\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/modules/codemirror/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'modules',\n    'modules/codemirror/controller'\n], function(_, Radio, Marionette, Modules, Controller) {\n    'use strict';\n\n    /**\n     * Codemirror module.\n     */\n    var Module = Modules.module('Codemirror', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    Module.on('start', function() {\n        console.info('Codemirror module has started');\n        Module.controller = new Controller();\n    });\n\n    Module.on('stop', function() {\n        console.info('Codemirror module has stoped');\n\n        Module.controller.destroy();\n        Module.controller = null;\n    });\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n        console.info('Codemirror module has been initialized');\n\n        Radio.channel('notesForm')\n        .on('view:ready', Module.start, Module)\n        .on('view:destroy', Module.stop, Module);\n\n    });\n\n    return Module;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/codemirror/templates/editor.html",
    "content": "<div class=\"editor--bar dropdown text-center\">\n    <div class=\"btn-group editor--btns\">\n        <span data-action=\"bold\" data-state=\"strong\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-bold\"></i>\n        </span>\n        <span data-action=\"italic\" data-state=\"em\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-italic\"></i>\n        </span>\n        <span data-action=\"heading\" data-state=\"header\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-font\"></i>\n        </span>\n    </div>\n\n    <div class=\"btn-group editor--btns\">\n        <span data-action=\"link\" data-state=\"link\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-globe\"></i>\n        </span>\n        <span data-action=\"attachment\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-picture\"></i>\n        </span>\n        <span data-action=\"code\" data-state=\"comment\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-code\"></i>\n        </span>\n    </div>\n\n    <div class=\"btn-group editor--btns\">\n        <span data-action=\"numberedList\" data-state=\"ordered-list\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-list-numbered\"></i>\n        </span>\n        <span data-action=\"list\" data-state=\"unordered-list\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-list-bullet\"></i>\n        </span>\n        <span data-action=\"hr\" data-state=\"hr\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-minus\"></i>\n        </span>\n    </div>\n\n    <div class=\"btn-group editor--btns\">\n        <span data-action=\"undo\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-reply\"></i>\n        </span>\n        <span data-action=\"redo\" class=\"btn btn--wysiwyg\">\n            <i class=\"icon-share\"></i>\n        </span>\n    </div>\n</div>\n\n<div class=\"btn-group btn-group-justified hidden -show\">\n    <div class=\"btn-group\">\n        <button data-col=\"left\" class=\"btn btn-default active editor--col--btn\">\n            {{i18n('Editor')}}\n        </button>\n    </div>\n    <div class=\"btn-group\">\n        <button data-col=\"right\" class=\"btn btn-default editor--col--btn\">\n            {{i18n('Preview')}}\n        </button>\n    </div>\n</div>\n\n<div class=\"editor--row\">\n    <div class=\"editor--layout -left -show\">\n        <textarea class=\"editor--input\" id=\"editor--input\" name=\"content\">{=cleanXSS(content)}</textarea>\n    </div>\n\n    <div class=\"editor--layout -right\">\n        <div class=\"editor--preview\">\n            <div id=\"wmd-preview\" class=\"editor--preview--block\"></div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/modules/codemirror/views/editor.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!modules/codemirror/templates/editor.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Codemirror view.\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n        className: 'layout--body container-fluid',\n\n        ui: {\n            preview       : '#wmd-preview',\n            previewScroll : '.editor--preview',\n            bar           : '.editor--bar'\n        },\n\n        events: {\n            'click .editor--btns .btn' : 'triggerAction',\n            'click .editor--col--btn'  : 'showColumn'\n        },\n\n        initialize: function() {\n            this.options.mode = Radio.request('configs', 'get:config', 'editMode');\n\n            this.listenTo(this, 'editor:change', this.onEditorChange);\n            this.listenTo(this, 'change:mode', this.onChangeMode);\n\n            this.$layoutBody = $('.layout--body.-scroll.-form');\n            this.$layoutBody.on('scroll', _.bind(this.onScroll, this));\n        },\n\n        onDestroy: function() {\n            this.$layoutBody.off('scroll');\n            Radio.trigger('editor', 'view:destroy');\n        },\n\n        onChangeMode: function(mode) {\n            this.options.mode = mode;\n\n            if (mode !== 'normal') {\n\n                // Make the editor visible by scrolling back\n                this.$layoutBody.scrollTop(0);\n\n                // Change WYSIWYG bar width\n                this.ui.bar.css('width', 'initial');\n                return this.ui.bar.removeClass('-fixed');\n            }\n        },\n\n        onScroll: function() {\n\n            // If editor mode is not 'normal' mode, don't do anything\n            if (this.options.mode !== 'normal') {\n                return;\n            }\n\n            // Fix WYSIWYG bar on top\n            if (this.$layoutBody.scrollTop() > this.ui.bar.offset().top) {\n                this.ui.bar.css('width', this.$layoutBody.width());\n                return this.ui.bar.addClass('-fixed');\n            }\n\n            this.ui.bar.css('width', 'initial');\n            return this.ui.bar.removeClass('-fixed');\n        },\n\n        onEditorChange: function(content) {\n            this.ui.preview.html(content);\n\n            if (!this.isFirst) {\n                this.isFirst = true;\n                return Radio.trigger('editor', 'view:render', this);\n            }\n            Radio.trigger('editor', 'preview:refresh');\n        },\n\n        triggerAction: function(e) {\n            e.preventDefault();\n            var action = $(e.currentTarget).attr('data-action');\n\n            if (action) {\n                this.trigger('editor:action', action);\n            }\n        },\n\n        /**\n         * Shows either the preview or the editor.\n         */\n        showColumn: function(e) {\n            var $btn    = $(e.currentTarget),\n                col     = $btn.attr('data-col'),\n                hideCol = (col === 'left' ? 'right' : 'left');\n\n            // Add 'active' class to the button\n            this.$('.editor--col--btn.active').removeClass('active');\n            $btn.addClass('active');\n\n            // Show only one column\n            this.$('.-' + hideCol).removeClass('-show');\n            this.$('.-' + col).addClass('-show');\n        },\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/dropbox/classes/adapter.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q'\n], function(_, Q) {\n    'use strict';\n\n    var Adapter = {\n\n        init: function(client, profile) {\n            this.client  = client;\n            this.profile = profile;\n        },\n\n        /**\n         * Save a model to Dropbox.\n         *\n         * @type string type [notes|notebooks|tags]\n         * @type object model\n         * @type array encryptKeys\n         */\n        save: function(type, model, encryptKeys) {\n            if (!model.id) {\n                return new Q();\n            }\n\n            if (model.encryptedData) {\n                model = _.omit(model, encryptKeys);\n            }\n\n            var path = '/' + this.profile + '/' + type + '/' + model.id + '.json';\n\n            return this.client.filesUpload({\n                path: path,\n                autorename: false,\n                mode: {'.tag': 'overwrite'},\n                contents: JSON.stringify(model),\n            });\n        },\n\n        /**\n         * Get all models from Dropbox.\n         *\n         * @type string type [notes|notebooks|tags]\n         */\n        getAll: function(type) {\n            var self = this;\n\n            return this.readdir(type)\n            .then(function(resp) {\n                var promises = [];\n\n                _.each(resp.entries, function(file) {\n                    if (file.name.search('.json') !== -1) {\n                        promises.push(self.getFile(type, file.path_lower));\n                    }\n                });\n\n                return Q.all(promises);\n            });\n        },\n\n        /**\n         * Get a JSON object by ID from Dropbox.\n         *\n         * @type string type [notes|notebooks|tags]\n         * @type string path\n         */\n        getFile: function(type, path) {\n            return this.client.filesDownload({path: path})\n            .then(function(resp) {\n                var defer = Q.defer();\n                var reader = new FileReader();\n\n                reader.addEventListener('loadend', function() {\n                    defer.resolve(JSON.parse(reader.result));\n                });\n                reader.readAsText(resp.fileBlob);\n\n                return defer.promise;\n            });\n        },\n\n        /**\n         * Get a folder stat from Dropbox.\n         *\n         * @type string type [notes|notebooks|tags]\n         * @type object options\n         */\n        readdir: function(type, options) {\n            return this.client.filesListFolder({\n                path            : '/' + this.profile + '/' + type,\n                include_deleted : false,\n            })\n            .catch(function(err) {\n                if (err.status === 409) {\n                    return [];\n                }\n\n                return Promise.reject(err);\n            });\n        },\n\n    };\n\n    return Adapter;\n});\n"
  },
  {
    "path": "app/scripts/modules/dropbox/classes/sync.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'jquery',\n    'q',\n    'backbone',\n    'marionette',\n    'backbone.radio',\n    'dropbox',\n    'modules/dropbox/classes/adapter'\n], function(_, $, Q, Backbone, Marionette, Radio, Dropbox, adapter) {\n    'use strict';\n\n    /**\n     * Dropbox synchronizer.\n     *\n     * Triggers:\n     * 1. `auth:success` on `dropbox` channel\n     *     - after authentication is completed successfully.\n     * 2. `start` on `sync` channel\n     *     when synchronizing starts\n     * 3. `stop` on `sync` channel\n     *     when synchronizing stops\n     *\n     * Replies:\n     * 1. `start` on `sync` channel\n     *     starts synchronizing.\n     */\n    var Sync = Marionette.Object.extend({\n\n        configs  : {\n            // Dropbox app key\n            key         : '10iirspliqts95d',\n\n            // Interval configs\n            interval    : 2000,\n            intervalMax : 15000,\n            intervalMin : 2000,\n\n            // A state which shows if something is changed remotely\n            statRemote  : false\n        },\n\n        initialize: function() {\n            var key = Radio.request('configs', 'get:config', 'dropboxKey');\n            this.configs.key = key || this.configs.key;\n            this.configs.accessToken = Radio.request('configs', 'get:config', 'dropboxAccessToken');\n\n            this.vent = Radio.channel('dropbox');\n\n            this.client = new Dropbox({\n                clientId: this.configs.key\n            });\n\n            // Replies\n            Radio.reply('sync', 'start', this.startSync, this);\n\n            // Listen to Laverna events\n            this.listenTo(Radio.channel('notes'), 'sync:model destroy:model restore:model', this.onSave);\n            this.listenTo(Radio.channel('notebooks'), 'sync:model destroy:model restore:model', this.onSave);\n            this.listenTo(Radio.channel('tags'), 'sync:model destroy:model restore:model', this.onSave);\n\n            // Authorize the app\n            var self = this;\n            this.checkAuth()\n            .then(function(authenticated) {\n                if (authenticated) {\n                    return self.onReady();\n                }\n\n                console.error('Dropbox authentication failed.');\n            })\n            .catch(function(err) {\n                console.log('Dropbox error', err);\n            });\n        },\n\n        /**\n         * Start synchronizing immediately.\n         */\n        startSync: function() {\n            if (this.timeout) {\n                clearTimeout(this.timeout);\n            }\n\n            this.timeout = setTimeout(_.bind(function() {\n                this.checkChanges();\n            }, this), 0);\n        },\n\n        /**\n         * Check if Dropbox was authenticated.\n         */\n        checkAuth: function() {\n            var hash = this.parseHash();\n\n            if (this.configs.accessToken && this.configs.accessToken.length) {\n                this.client.setAccessToken(this.configs.accessToken);\n                return Promise.resolve(true);\n            }\n            else if (hash.access_token && hash.access_token.length) {\n                return this.saveAccessToken(hash.access_token);\n            }\n            else {\n                if (hash.error) {\n                    Radio.request('uri', 'navigate', '/');\n                }\n\n                return this.authenticate();\n            }\n        },\n\n        /**\n         * Parse location hash.\n         *\n         * @returns {Object}\n         */\n        parseHash: function() {\n            var hash = window.location.hash.replace('#', '').split('&');\n            var ret  = {};\n\n            if (!hash.length) {\n                return ret;\n            }\n\n            _.each(hash, function(str) {\n                var parts = str.replace(/\\+/g, ' ').split('=');\n\n                if (parts.length > 1) {\n                    var key  = parts.shift();\n                    var val  = parts.length > 0 ? parts.join('=') : undefined;\n                    val      = undefined ? null : decodeURIComponent(val.trim());\n                    ret[key] = val;\n                }\n            });\n\n            return ret;\n        },\n\n        authenticate: function() {\n            var defer = Q.defer();\n            var authUrl = this.client.getAuthenticationUrl(document.location);\n\n            Radio.once('Confirm', 'cancel',  _.bind(defer.reject, defer));\n            Radio.once('Confirm', 'confirm', function() {\n                window.location = authUrl;\n            });\n\n            Radio.request('Confirm', 'start', {\n                title  : $.t('dropbox.auth title'),\n                content: $.t('dropbox.auth confirm')\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Save the access token in configs.\n         *\n         * @param {String} accessToken\n         * @returns {Promise}\n         */\n        saveAccessToken: function(accessToken) {\n            var self = this;\n            return Radio.request('configs', 'save:object', {\n                name  : 'dropboxAccessToken',\n                value : accessToken,\n            })\n            .then(function() {\n                Radio.request('uri', 'navigate', '/');\n                self.configs.accessToken.accessToken;\n                return true;\n            });\n        },\n\n        /**\n         * Start synchronizing all data after Dropbox client is ready.\n         */\n        onReady: function() {\n            var profile = Radio.request('uri', 'profile') || 'notes-db';\n            var self = this;\n            adapter.init(this.client, profile);\n\n            this.timeout = window.setTimeout(function() {\n                self.checkChanges();\n            }, 500);\n        },\n\n        /**\n         * Check for changes.\n         */\n        checkChanges: function() {\n            var promises = [],\n                self     = this;\n\n            this.configs.statRemote = false;\n            Radio.trigger('sync', 'start', 'dropbox');\n\n            // Synchronize all collections\n            _.each(['notes', 'notebooks', 'tags'], function(module) {\n                promises.push(function() {\n                    return Q.all([\n                        Radio.request(module, 'fetch', {encrypt: true}),\n                        adapter.getAll(module)\n                    ])\n                    .spread(function(localData, remoteData) {\n                        return self.syncAll(localData, remoteData, module);\n                    });\n                });\n            });\n\n            // After synchronizing, start watching for changes\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                Radio.trigger('sync', 'stop', 'dropbox');\n                self.startWatch();\n            })\n            .fail(function(err) {\n                if (err) {\n                    switch (err.status) {\n\n                        // If access was revoked, try to ask for it again\n                        case 401:\n                            self.checkAuth();\n                            break;\n\n                        // On connection error, increase watch interval\n                        case 0:\n                            self.configs.interval = self.configs.intervalMax;\n                            self.startWatch();\n                            break;\n                    }\n                }\n\n                Radio.trigger('sync', 'stop', 'dropbox');\n                Radio.trigger('sync', 'error', {cloud: 'dropbox', error: err});\n                console.error('Error', arguments[0], arguments);\n            });\n        },\n\n        /**\n         * Synchronize a collection.\n         *\n         * @type array localData\n         * @type array remoteData\n         * @type string module\n         * @return promise\n         */\n        syncAll: function(localData, remoteData, module) {\n            var promises,\n                encryptKeys = localData.model.prototype.encryptKeys;\n\n            localData = (localData.fullCollection || localData).toJSON();\n\n            promises = this.checkRemoteChanges(localData, remoteData, module);\n            promises.push.apply(\n                promises,\n                this.checkLocalChanges(localData, remoteData, module, encryptKeys)\n            );\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                return Radio.request(module, 'fetch', {encrypt: true});\n            });\n        },\n\n        /**\n         * Save only models which don't exist locally or which were updated\n         * remotely.\n         */\n        checkRemoteChanges: function(localData, remoteData, module) {\n            var promises = [],\n                newData  = _.filter(remoteData, function(rModel) {\n                    var model = _.findWhere(localData, {id: rModel.id});\n                    return !model || model.updated < rModel.updated;\n                });\n\n            if (newData.length) {\n                console.log('Dropbox changes:', newData);\n                this.configs.statRemote = true;\n\n                promises.push(function() {\n                    return Radio.request(module, 'save:all:raw', newData, {profile: adapter.profile});\n                });\n            }\n\n            return promises;\n        },\n\n        /**\n         * Save only models which don't exist on Dropbox or\n         * which were updated locally.\n         */\n        checkLocalChanges: function(localData, remoteData, module, encryptKeys) {\n            var promises = [];\n\n            _.each(localData, function(lModel) {\n                var model = _.findWhere(remoteData, {id: lModel.id});\n                if (model && model.updated >= lModel.updated) {\n                    return;\n                }\n\n                console.log('Dropbox local changes:', lModel);\n                promises.push(function() {\n                    return adapter.save(module, lModel, encryptKeys);\n                });\n            });\n\n            return promises;\n        },\n\n        startWatch: function() {\n            if (this.timeout) {\n                clearTimeout(this.timeout);\n            }\n\n            this.calcInterval();\n            console.log('interval is', this.configs.interval);\n\n            this.timeout = setTimeout(_.bind(function() {\n                this.checkChanges();\n            }, this), this.configs.interval);\n        },\n\n        /**\n         * Increase or descrease watch interval depending on\n         * whether changes appear on Dropbox.\n         */\n        calcInterval: function() {\n            var range = this.configs.intervalMax - this.configs.intervalMin;\n\n            if (this.configs.statRemote) {\n                this.configs.interval -= (range * 0.4);\n            }\n            else {\n                this.configs.interval += (range * 0.2);\n            }\n\n            this.configs.interval = Math.max(this.configs.intervalMin, this.configs.interval);\n            this.configs.interval = Math.min(this.configs.intervalMax, this.configs.interval);\n        },\n\n        /**\n         * Immediately after a model is changed locally, synchronize it with\n         * Dropbox.\n         */\n        onSave: function(model) {\n            return adapter.save(model.storeName, model.attributes, model.encryptKeys);\n        }\n\n    });\n\n    return Sync;\n});\n"
  },
  {
    "path": "app/scripts/modules/dropbox/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'modules',\n    'modules/dropbox/classes/sync'\n], function(_, Radio, Marionette, Modules, Sync) {\n    'use strict';\n\n    var Dropbox = Modules.module('Dropbox', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    Dropbox.on('start', function() {\n        console.info('Dropbox started');\n        new Sync();\n    });\n\n    Dropbox.on('stop', function() {\n    });\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n        Dropbox.start();\n    });\n\n    return Dropbox;\n});\n"
  },
  {
    "path": "app/scripts/modules/electronSearch/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules/electronSearch/view'\n], function(_, Marionette, Radio, View) {\n    'use strict';\n\n    return Marionette.Object.extend({\n\n        initialize: function() {\n\n            // Create a new region\n            Radio.request('global', 'region:add', 'module--electronSearch');\n\n            // Render the view\n            this.view = new View({});\n            Radio.request('global', 'region:show', 'module--electronSearch', this.view);\n            this.view.trigger('rendered');\n\n            // Destroy the controller\n            this.view.on('destroy', this.destroy, this);\n        }\n\n    });\n\n});\n"
  },
  {
    "path": "app/scripts/modules/electronSearch/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'backbone.radio',\n    'modules',\n    'mousetrap',\n    'modules/electronSearch/controller',\n    'mousetrap.global'\n], function(Radio, Modules, Mousetrap, Controller) {\n    'use strict';\n\n    /**\n     * Adds page search functionality into electron app.\n     */\n    var Search = Modules.module('ElectronSearch', {});\n\n    Search.on('start', function() {\n        this.controller = new Controller();\n\n        this.controller.on('destroy', Search.stop, Search);\n    });\n\n    Search.on('stop', function() {\n        this.controller.destroy();\n    });\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n\n        // Start the module\n        Mousetrap.bind(['ctrl+f', 'command+f'], function(e) {\n            e.preventDefault();\n            Search.start();\n        });\n    });\n\n    return Search;\n});\n"
  },
  {
    "path": "app/scripts/modules/electronSearch/template.html",
    "content": "<form class=\"form-inline\" action=\"\">\n    <div class=\"input-group\">\n        <input class=\"form-control\" type=\"text\" name=\"text\" placeholder=\"{{i18n('Find in page')}}\">\n\n        <div class=\"input-group-btn\">\n            <button title=\"{{i18n('Previous')}}\" id=\"search--prev\" type=\"button\" class=\"btn btn-default\">\n                <i class=\"icon icon-left-open\"></i>\n            </button>\n            <button title=\"{{i18n('Next')}}\" id=\"search--next\" type=\"button\" class=\"btn btn-default\">\n                <i class=\"icon icon-right-open\"></i>\n            </button>\n        </div>\n    </div>\n\n    <div class=\"input-group\">\n        <button title=\"{{i18n('Close')}}\" type=\"button\" class=\"btn btn-default search--close\">\n            &times;\n        </button>\n    </div>\n</form>\n"
  },
  {
    "path": "app/scripts/modules/electronSearch/view.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requireNode */\ndefine([\n    'underscore',\n    'marionette',\n    'text!modules/electronSearch/template.html'\n], function(_, Marionette, Tmpl) {\n    'use strict';\n\n    var remote = requireNode('electron').remote,\n        View;\n\n    View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'electron--search',\n\n        ui: {\n            'search': '[name=\"text\"]'\n        },\n\n        events: {\n            'input @ui.search'     : 'onInput',\n            'keyup @ui.search'     : 'destroyOnEsc',\n\n            'submit form'          : 'next',\n            'click #search--next'  : 'next',\n\n            'click #search--prev'  : 'previous',\n            'click .search--close' : 'destroy'\n        },\n\n        initialize: function() {\n            _.bindAll(this, 'onFind');\n            this.listenTo(this, 'rendered', this.onRendered);\n        },\n\n        onRendered: function() {\n            this.ui.search.focus();\n        },\n\n        onDestroy: function() {\n            remote.getCurrentWindow().webContents.stopFindInPage('clearSelection');\n        },\n\n        destroyOnEsc: function(e) {\n            if (e.keyCode === 27) {\n                this.destroy();\n            }\n        },\n\n        onFind: function() {\n            this.ui.search.focus();\n        },\n\n        onInput: function() {\n            this.search();\n\n            // Prevent it from losing focus\n            remote.getCurrentWindow().webContents.once('found-in-page', this.onFind);\n\n            return true;\n        },\n\n        next: function() {\n            this.search();\n            return false;\n        },\n\n        previous: function() {\n            this.search(true);\n            return false;\n        },\n\n        search: function(backSearch) {\n            var text = this.ui.search.val();\n\n            if (text) {\n                if (backSearch) {\n                    return remote.getCurrentWindow().webContents.findInPage(text, {forward: false});\n                }\n\n                return remote.getCurrentWindow().webContents.findInPage(text);\n            }\n        },\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/fileDialog/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules/fileDialog/views/dialog'\n], function(_, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * File dialog controller.\n     *\n     * Requests:\n     * 1. channel: `files`, request: `save:all`\n     *    in order to save uploaded files.\n     * 2. channel: `uri`, request: `link:file`\n     *    expects to receive a link to a file.\n     * 3. channel: `editor`, request: `generate:link`\n     *    expects to receive link code. For example, Markdown code for a link.\n     * 4. channel: `editor`, request: `generate:image`\n     *    expects to receive image code. For example, Markdown code for an image.\n     *\n     * requests:\n     * 1. channel: `editor`, request: `insert`\n     *    in order to insert some text to the editor.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            this.options = options;\n\n            // Instantiate and show the view\n            this.view = new View();\n            Radio.request('global', 'region:show', 'modal', this.view);\n\n            // Events\n            this.listenTo(this.view, 'save', this.link);\n            this.listenTo(this.view, 'redirect', this.destroy);\n        },\n\n        onDestroy: function() {\n            if (this.options.callback) {\n                this.options.callback(null);\n            }\n\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'modal');\n        },\n\n        /**\n         * Provide the url to editor callback\n         */\n        link: function(isFile) {\n            var self = this,\n                url  = this.view.ui.url.val().trim();\n\n            if (url !== '') {\n                var method = (isFile === true ? 'attachFiles' : 'attachImage');\n                return this[method](url);\n            }\n\n            Radio.request('files', 'save:all', this.view.files, {\n                profile: Radio.request('uri', 'profile')\n            })\n            .then(function(files) {\n                self.options.model.files = _.union(self.options.model.files, files);\n\n                // If there is only 1 file and its type is image\n                if (files.length === 1 && self.isImage(files[0])) {\n                    return self.attachText(self.generateCode(files[0]));\n                }\n\n                // Otherwise, we will generate custom Markdown code\n                self.attachFiles(files);\n            })\n            .fail(function() {\n                console.error('Error while uploading file:', arguments);\n            });\n        },\n\n        /**\n         * Attach files to the editor.\n         */\n        attachFiles: function(files) {\n            var str = '';\n\n            // It is just a link\n            if (typeof files === 'string') {\n                str = Radio.request('editor', 'generate:link', {\n                    text : 'Alt description',\n                    url  : files\n                });\n            }\n            else {\n                _.each(files, function(model) {\n                    str += this.generateCode(model) + '\\n';\n                }, this);\n            }\n\n            this.attachText(str);\n        },\n\n        attachImage: function(url) {\n            var text = Radio.request('editor', 'generate:image', {\n                text : 'Alt description',\n                url  : url\n            });\n\n            this.attachText(text);\n        },\n\n        attachText: function(text) {\n            this.options.callback(text !== '' ? text : null);\n\n            // Close the dialog\n            this.options.callback = null;\n            this.destroy();\n        },\n\n        isImage: function(model) {\n            return (model.get('fileType').indexOf('image') > -1);\n        },\n\n        /**\n         * Generate Markdown code.\n         */\n        generateCode: function(model) {\n            var url     = Radio.request('uri', 'link:file', model),\n                request = 'link';\n\n            // If file type is an image type, generate image MD code\n            if (this.isImage(model)) {\n                request = 'image';\n            }\n\n            return Radio.request('editor', 'generate:' + request, {\n                text : model.get('name'),\n                url  : url\n            });\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/modules/fileDialog/helper.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio'\n], function(_, Radio) {\n    'use strict';\n\n    var Helper = {\n        data: {urls: []},\n\n        /**\n         * Replace all file links with generated URIs\n         *\n         * @var string text\n         * @var array files an array of IDs\n         */\n        toHtml: function(text, model) {\n            // If the note doesn't have attached files, revoke URLs and return text\n            if (!model.files.length) {\n                this.revokeUrls();\n                return text;\n            }\n\n            // If it is not the same note model, revoke URLs\n            if (model.id !== this.data.id) {\n                this.revokeUrls();\n            }\n\n            var url,\n                pattern;\n\n            this.data.id = model.id;\n\n            _.each(model.files, function(fModel) {\n                url = this._generateUrl(fModel);\n\n                /*\n                 * Replace colons in the URL to prevent Pagedown from converting it\n                 * to a link.\n                 */\n                url = url.replace(/:(?!http)/, '&#58;');\n\n                // Replace fake URLs with real ones\n                pattern = new RegExp('#file:' + fModel.id, 'g');\n                text    = text.replace(pattern, url);\n\n            }, this);\n\n            return text;\n        },\n\n        /**\n         * Parse the text for file IDs\n         * @var string text\n         */\n        getFileIds: function(text) {\n            if (text === '') {\n                return text;\n            }\n\n            var ids     = [],\n                results = text.match(/#file:([a-z0-9\\-])+/g);\n\n            // Remove duplicate IDs\n            results = _.uniq(results);\n\n            _.each(results, function(res) {\n                ids.push(\n                    res.replace('#file:', '')\n                );\n            });\n\n            return ids;\n        },\n\n        /**\n         * Revoke object URLs\n         */\n        revokeUrls: function() {\n            var url = window.URL || window.webkitURL;\n\n            _.each(this.data.urls, function(link) {\n                url.revokeObjectURL(link);\n            });\n\n            this.data = {urls: []};\n        },\n\n        /**\n         * Generate object URL to a file.\n         */\n        _generateUrl: function(fModel) {\n            // Do not generate URLs every time\n            var url = _.findWhere(this.data.urls, {id: fModel.id});\n            if (url) {\n                return url.url;\n            }\n\n            url = Radio.request('uri', 'link:file', fModel, true);\n            this.data.urls.push({\n                id  : fModel.id,\n                url : url\n            });\n            return url;\n        }\n\n    };\n\n    return Helper;\n});\n"
  },
  {
    "path": "app/scripts/modules/fileDialog/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'modules',\n    'backbone.radio',\n    'modules/fileDialog/controller',\n    'modules/fileDialog/helper'\n], function(_, Q, Modules, Radio, Controller, Helper) {\n    'use strict';\n\n    /**\n     * File dialog.\n     * It shows a dialog where a user can upload files.\n     *\n     * Listens for events:\n     * 1. channel: `editor`, event: `destroy`\n     *    stops itself\n     * 2. channel: `editor`, event: `converter:init`\n     *    adds hooks.\n     * 3. channel: `noteView`, event: `view:destroy`\n     *    revokes all generated URLs\n     *\n     * Replies to requests on channel `editor`:\n     * 1. `get:files` - parses the text for file links and returns\n     *                  an array of IDs.\n     *\n     * Adds the following hooks to Pagedown editor:\n     * 1. `insertImageDialog`\n     * 2. `postConversion`\n     */\n    var FileDialog = Modules.module('FileDialog', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    FileDialog.on('before:start', function(options) {\n        FileDialog.controller = new Controller(options);\n        this.listenTo(FileDialog.controller, 'destroy', FileDialog.stop);\n    });\n\n    FileDialog.on('before:stop', function() {\n        console.info('FileDialog stopped');\n\n        Helper.revokeUrls();\n        this.stopListening();\n        FileDialog.controller = null;\n    });\n\n    /**\n     * Add converter hooks\n     */\n    function addHook(converter, model) {\n        // Do not add hooks if a model wasn't provided.\n        if (!model) {\n            return;\n        }\n\n        converter.hooks.chain('preConversion', function(text) {\n            return Helper.toHtml(text, model);\n        });\n\n        // Make colons normal again\n        converter.hooks.chain('postConversion', function(text) {\n            return text.replace(/(blob:http)&#58;/g, '$1:');\n        });\n    }\n\n    // Radio.request('init', 'add', 'editor:before', function(editor, model) {\n    //     editor.hooks.set('insertImageDialog', function(fnc) {\n    //         return true;\n    //     });\n    // });\n\n    Radio.request('init', 'add', 'module', function() {\n\n        // Stop the module when editor is closed.\n        Radio.on('editor', 'destroy', FileDialog.stop, FileDialog);\n\n        // Show custom dialog on `insertImageDialog` hook.\n        Radio.reply('editor', 'show:attachment', function(model) {\n            var defer = Q.defer();\n\n            FileDialog.start({model: model, callback: function(link) {\n                defer.resolve(link);\n            }});\n\n            return defer.promise;\n        });\n\n        Radio.reply('editor', 'get:files', Helper.getFileIds, Helper);\n\n        // When editor converter is initialized, add hooks\n        Radio.on('editor', 'converter:init', addHook);\n\n        // Revoke all URLs when a note is closed.\n        Radio.on('noteView', 'view:destroy', Helper.revokeUrls, Helper);\n    });\n\n    return FileDialog;\n});\n"
  },
  {
    "path": "app/scripts/modules/fileDialog/templates/dialog.html",
    "content": "<div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h4 class=\"modal-title\">\n                {{i18n('files.attach')}}\n            </h4>\n        </div>\n        <div class=\"modal-body\">\n            <form action=\"#\" class=\"form\" method=\"post\">\n                <div class=\"form-group\">\n                    <input name=\"url\" class=\"form-control\" type=\"text\" value=\"\" placeholder=\"{{ i18n('files.file-url') }}\" />\n                </div>\n            </form>\n\n            <form class=\"dropzone--container\" action=\"\">\n                <div class=\"dropzone row\">\n                </div>\n            </form>\n        </div>\n        <div class=\"modal-footer\">\n            <button class=\"btn btn-default cancelBtn\">{{ i18n('Cancel') }}</button>\n            <button class=\"btn btn-success ok\" id=\"ok-btn\">{{ i18n('OK') }}</button>\n\n            <div id=\"btn-attach\" class=\"btn-group hidden\">\n                <button class=\"btn btn-default attach-file\">\n                    {{ i18n('files.attachLink') }}\n                </button>\n                <button class=\"btn btn-success ok\">\n                    <i class=\"icon icon-picture\"></i>\n                    {{ i18n('files.attachImage') }}\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/modules/fileDialog/templates/dropzone.html",
    "content": "<div class=\"col-xs-4\">\n    <div class=\"dz-preview dz-file-preview thumbnail\">\n        <div class=\"dz-details\">\n            <img data-dz-thumbnail />\n            <div class=\"dz-filename caption\">\n                <p data-dz-name></p>\n                <p class=\"dz-size\" data-dz-size></p>\n            </div>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/modules/fileDialog/views/dialog.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, Modernizr */\ndefine([\n    'underscore',\n\t'i18next',\n    'marionette',\n    'backbone.radio',\n    'behaviors/modalForm',\n    'dropzone',\n    'text!modules/fileDialog/templates/dialog.html',\n    'text!modules/fileDialog/templates/dropzone.html'\n], function(_, i18n, Marionette, Radio, ModalForm, Dropzone, Tmpl, dropzoneTmpl) {\n    'use strict';\n\n    /**\n     * File dialog view.\n     */\n    var View = Marionette.ItemView.extend({\n        template  : _.template(Tmpl),\n        className : 'modal fade',\n\n        behaviors: {\n            ModalForm: {\n                behaviorClass : ModalForm,\n                uiFocus       : 'url'\n            }\n        },\n\n        ui: {\n            url    : '[name=url]',\n            okBtn  : '#ok-btn',\n            attach : '#btn-attach'\n        },\n\n        events: {\n            'keyup @ui.url' : 'toggleAttachBtn',\n            'click .attach-file': 'attachFile'\n        },\n\n        initialize: function() {\n            this.files = [];\n            this.listenTo(this, 'shown.modal', this.onShown);\n        },\n\n        attachFile: function(e) {\n            e.preventDefault();\n            this.trigger('save', true);\n        },\n\n        toggleAttachBtn: _.debounce(function() {\n            this.ui.okBtn.toggleClass('hidden', this.ui.url.val().trim() !== '');\n            this.ui.attach.toggleClass('hidden', this.ui.url.val().trim() === '');\n        }, 250),\n\n        onShown: function() {\n\n            // File uploading is allowed only if either Indexeddb or WebSQL is supported\n            if (Modernizr.indexeddb || Modernizr.websqldatabase) {\n                new Dropzone('.dropzone', {\n                    url             : '/#notes',\n                    clickable       : true,\n                    accept          : _.bind(this.getImage, this),\n                    thumbnailWidth  : 100,\n                    thumbnailHeight : 100,\n                    previewTemplate: dropzoneTmpl,\n\t\t\t\t\tdictDefaultMessage: i18n.t('Drop files')\n                });\n            }\n        },\n\n        /**\n         * Save file data to a variable.\n         */\n        getImage: function(file) {\n            var reader = new FileReader();\n\n            this.ui.url.val('').trigger('keyup');\n\n            reader.onload = _.bind(function(evt) {\n                this.files.push({\n                    name     : file.name,\n                    src      : evt.target.result,\n                    fileType : file.type\n                });\n            }, this);\n\n            reader.readAsDataURL(file);\n        }\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/fs/classes/adapter.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requireNode */\ndefine([\n    'underscore',\n    'q',\n    'backbone.radio'\n], function(_, Q, Radio) {\n    'use strict';\n\n    // A hack to trick Nodejs modules not to register as RequireJS modules\n    var def    = _.clone(define.amd);\n    define.amd = false;\n\n    var glob     = requireNode(window.nodeDir + 'glob'),\n        fs       = requireNode(window.nodeDir + 'graceful-fs'),\n        chokidar = requireNode(window.nodeDir + 'chokidar'),\n        Adapter;\n\n    define.amd = _.clone(def);\n    def        = undefined;\n\n    Adapter = {\n\n        /**\n         * Since module isn't ready yet, sync to /tmp folder (for now)\n         * (/tmp/laverna folder must exist)\n         */\n        path: '/tmp/laverna/',\n\n        /**\n         * Check if directories exist. If they don't, create them.\n         */\n        checkDirs: function() {\n            if (!this.isDirSync(this.path)) {\n                fs.mkdirSync(this.path);\n            }\n\n            _.each(['notes', 'notebooks', 'tags', 'files'], function(dir) {\n                if (!this.isDirSync(this.path + dir)) {\n                    fs.mkdirSync(this.path + dir);\n                }\n            }, this);\n        },\n\n        /**\n         * Check if a dir exists.\n         */\n        isDirSync: function(path) {\n            try {\n                return fs.statSync(path).isDirectory();\n            } catch (e) {\n                if (e.code === 'ENOENT') {\n                    return false;\n                }\n                else {\n                    throw e;\n                }\n            }\n        },\n\n        /**\n         * Overwrite a file with data.\n         */\n        _write: function(name, data) {\n            var defer = Q.defer();\n\n            fs.writeFile(this.path + name, data, function(err) {\n                if (err) {\n                    return defer.reject(err);\n                }\n\n                defer.resolve();\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Read a file.\n         *\n         * @type string name\n         */\n        _read: function(name) {\n            var defer = Q.defer();\n\n            fs.readFile(name, 'utf8', function(err, data) {\n                if (err) {\n                    return defer.reject(err);\n                }\n\n                defer.resolve(data);\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Save model data to the FS.\n         */\n        writeFile: function(module, model) {\n            var name     = module + '/' + model.id,\n                data     = JSON.stringify(_.omit(model, 'content')),\n                promises = [\n                    this._write(name + '.json', data)\n                ];\n\n            // Save content into a separate Markdown file\n            if (model.content) {\n                promises.push(\n                    this._write(name + '.md', model.content)\n                );\n            }\n\n            // Save all files\n            return Q.all(promises);\n        },\n\n        /**\n         * Watch for changes.\n         */\n        startWatch: function() {\n            var watcher = chokidar.watch(Adapter.path, {\n                persistent: true\n            });\n\n            watcher.on('change', function(file) {\n                Adapter._read(file)\n                .then(function(data) {\n\n                    var obj = Adapter.getFileInfo(file);\n\n                    // The file has Markdown extension\n                    if (obj.ext === 'md') {\n                        data = {content: data, id: obj.id};\n                    }\n                    else if (obj.ext === 'json') {\n                        data = JSON.parse(data);\n                    }\n\n                    // Trigger an event\n                    Radio.trigger('fs', 'change', {\n                        storeName : obj.storeName,\n                        data      : data\n                    });\n                });\n            });\n        },\n\n        /**\n         * Get the content of all files.\n         */\n        getList: function(type) {\n            var defer = Q.defer(),\n                dir   = Adapter.path + (type ? type + '/' : '') + '*.*';\n\n            // First, read get the list of all files in a folder\n            glob(dir, {}, function(err, files) {\n                Adapter.getFiles(files)\n                .then(function(data) {\n                    defer.resolve(type ? data[type] : data);\n                });\n            });\n\n            return defer.promise;\n        },\n\n        /**\n         * Read the content of each file in a list\n         *\n         * @type array files\n         */\n        getFiles: function(files) {\n            var promises = [];\n\n            _.each(files, function(file) {\n\n                // Add the path to a place where the file is located\n                if (file.search(Adapter.path) === -1) {\n                    file = Adapter.path + file;\n                }\n\n                // Read the file\n                promises.push(Adapter._read(file));\n            });\n\n            return Q.all(promises)\n            .then(function(data) {\n                return _.object(files, data);\n            })\n            .then(function(data) {\n                return Adapter.filesToObject(data);\n            });\n        },\n\n        /**\n         * Get extension, type, and ID from file name.\n         */\n        getFileInfo: function(fileName) {\n            var key  = fileName.split('/');\n\n            // Get a model ID\n            fileName  = _.last(key).split('.');\n\n            return {\n                ext      : fileName[1],\n\n                // Get store name (notes|notebooks|tags)\n                storeName: key[key.length - 2],\n                id       : fileName[0]\n            };\n        },\n\n        /**\n         * Convert data to model structure.\n         */\n        filesToObject: function(data) {\n            var obj = {};\n\n            _.each(data, function(value, key) {\n                key = Adapter.getFileInfo(key);\n\n                // Separate models by their store names\n                obj[key.storeName]     = obj[key.storeName] || {};\n                obj[key.storeName][key.id] = obj[key.storeName][key.id] || {};\n\n                // The file has Markdown extension\n                if (key.ext === 'md') {\n                    obj[key.storeName][key.id].content = value;\n                }\n                else if (key.ext === 'json') {\n                    obj[key.storeName][key.id] = _.extend(\n                        obj[key.storeName][key.id], JSON.parse(value)\n                    );\n                }\n            });\n\n            return obj;\n        },\n    };\n\n    return Adapter;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/fs/classes/sync.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'modules/fs/classes/adapter'\n], function(_, Q, Marionette, Radio, FS) {\n    'use strict';\n\n    /**\n     * File system synchronizer.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n\n            FS.path = Radio.request('configs', 'get:config', 'module:fs:folder');\n\n            /**\n             * @todo Show a message or something.\n             * For now disable synchronizing.\n             */\n            if (!FS.path) {\n                return;\n            }\n\n            // Create current profile's folder\n            FS.path = FS.path + '/' + (Radio.request('uri', 'profile') || 'notes-db') + '/';\n            FS.checkDirs();\n\n            // Check for changes on file system\n            this.checkChanges();\n\n            // Listen to Laverna events\n            this.listenTo(Radio.channel('notes'), 'sync:model destroy:model restore:model', this.onSave);\n            this.listenTo(Radio.channel('notebooks'), 'sync:model destroy:model restore:model', this.onSave);\n            this.listenTo(Radio.channel('tags'), 'sync:model destroy:model restore:model', this.onSave);\n\n            // Listen to FS events\n            this.listenTo(Radio.channel('fs'), 'change', this.onFsChange);\n        },\n\n        /**\n         * Check for changes on start.\n         */\n        checkChanges: function() {\n            var promises = [],\n                self     = this;\n\n            _.each(['notes', 'notebooks', 'tags'], function(module) {\n                return Q.all([\n                    Radio.request(module, 'fetch', {encrypt: true}),\n                    FS.getList(module)\n                ])\n                .spread(function(localData, remoteData) {\n                    return self.syncAll(localData, remoteData, module);\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                self.startWatch();\n            })\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        /**\n         * Start watching for FS changes.\n         */\n        startWatch: function() {\n            FS.startWatch();\n        },\n\n        /**\n         * Synchronize FS and IndexedDB.\n         */\n        syncAll: function(localData, remoteData, module) {\n            var promises = [];\n\n            localData = (localData.fullCollection || localData).toJSON();\n\n            // First, check if there are any changes in IndexedDB\n            promises.push.apply(\n                promises,\n                this.checkLocalChanges(localData, remoteData, module)\n            );\n\n            // Then, check if there are any changes on file system\n            promises.push.apply(\n                promises,\n                this.checkRemoteChanges(localData, remoteData, module)\n            );\n\n            return _.reduce(promises, Q.when, new Q())\n            .fail(function(e) {\n                console.error('Error:', e);\n            });\n        },\n\n        /**\n         * Synchronize models from IndexedDB to file system.\n         */\n        checkLocalChanges: function(localData, remoteData, module) {\n            var promises = [];\n\n            _.each(localData, function(lModel) {\n                var model = _.findWhere(remoteData, {id: lModel.id});\n                if (model && model.updated >= lModel.updated) {\n                    return;\n                }\n\n                promises.push(function() {\n                    return FS.writeFile(module, lModel);\n                });\n            });\n\n            return promises;\n        },\n\n        /**\n         * Synchronize models from file system to IndexedDB.\n         */\n        checkRemoteChanges: function(localData, remoteData, module) {\n            var newData = _.filter(remoteData, function(rModel) {\n                var model = _.findWhere(localData, {id: rModel.id});\n                rModel.content = rModel.content || '';\n\n                if (model && model.updated >= rModel.updated &&\n                   _.isEqual(rModel, model)) {\n                    return false;\n                }\n\n                return true;\n            });\n\n            return Radio.request(module, 'save:all:raw', newData);\n        },\n\n        /**\n         * Laverna triggered `change` event.\n         */\n        onSave: function(model) {\n            FS.writeFile(model.storeName, model.attributes)\n            .fail(function(e) {\n                console.error('onSave error:', e);\n            });\n        },\n\n        /**\n         * File system triggered `change` event.\n         */\n        onFsChange: function(data) {\n\n            return Radio.request(data.storeName, 'get:model', {\n                id: data.data.id\n            })\n            .then(function(model) {\n                data.data = _.extend({}, model.attributes, data.data);\n\n                // Don't parse content\n                if (!data.data.content) {\n                    return [data, model];\n                }\n\n                // Parse tasks and tags\n                return Radio.request('markdown', 'parse', data.data.content)\n                .then(function(env) {\n                    data.data = _.extend(\n                        data.data,\n                        _.pick(env, 'tags', 'tasks', 'taskCompleted', 'taskAll', 'files')\n                    );\n\n                    return [data, model];\n                });\n            })\n            .spread(function(data, model) {\n\n                // Nothing's changed\n                if (_.isEqual(data.data, model.attributes)) {\n                    return;\n                }\n\n                return Radio.request(data.storeName, 'save:raw', data.data);\n            })\n            .fail(function(e) {\n                console.error('onFsChange error:', e);\n            });\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/modules/fs/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'modules',\n    'modules/fs/classes/sync'\n], function(_, Radio, Marionette, Modules, Sync) {\n    'use strict';\n\n    /**\n     * Module which synchronizes all models to a file system.\n     * (Works only on Electron app)\n     */\n    var FS = Modules.module('FS', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    FS.on('start', function() {\n        console.info('FS started');\n        new Sync();\n    });\n\n    FS.on('stop', function() {\n    });\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n        FS.start();\n    });\n\n    return FS;\n});\n"
  },
  {
    "path": "app/scripts/modules/fs/templates/settings.html",
    "content": "<div class=\"input-group\">\n    <span class=\"input-group-btn\">\n        <button class=\"btn btn-default btn--fs\" type=\"button\">\n            Choose a folder\n        </button>\n    </span>\n    <input type=\"text\" disabled class=\"form-control input--fs\" value=\"{{ models['module:fs:folder'] }}\" />\n</div>\n"
  },
  {
    "path": "app/scripts/modules/fs/views/settings.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, requireNode */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!modules/fs/templates/settings.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    /**\n     * Shows FS module's configs.\n     */\n    var dialog = requireNode('electron').remote.dialog,\n        View;\n\n    View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        ui: {\n            input: '.input--fs'\n        },\n\n        events : {\n            'click .btn--fs': 'showFolderDialog',\n        },\n\n        initialize: function() {\n        },\n\n        serializeData: function() {\n            return {\n                models: this.collection.getConfigs()\n            };\n        },\n\n        showFolderDialog: function(e) {\n            e.preventDefault();\n\n            var folder = dialog.showOpenDialog({properties: ['openDirectory']});\n\n            if (!folder) {\n                return;\n            }\n\n            this.ui.input.val(folder[0]);\n\n            this.collection.trigger('new:value', {\n                name  : 'module:fs:folder',\n                value : folder[0]\n            });\n        },\n\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/controllers/main.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'modules/fuzzySearch/views/composite'\n], function(_, Q, Marionette, Radio, View) {\n    'use strict';\n\n    /**\n     * Fuzzy search controller\n     *\n     * Listens to\n     * ------------\n     * Events:\n     * 1. channel: `global`, event: `search:change`\n     *    makes fuzzy search and shows the result.\n     *\n     * Triggers\n     * --------\n     * 1. channel: `global`, event: `search:hidden`\n     *    when a model in search results was selected.\n     * 2. channel: `appNote`, request: `filter`\n     *    in order to filter notes in sidebar.\n     * 3. channel: `fuzzySearch`, request: `region:show`\n     *    in order to render the view and show search results\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function() {\n            _.bindAll(this, 'search', 'onFetch');\n\n            // Fetch data\n            this.wait = Radio.request('notes', 'fetch', {\n                profile  : Radio.request('uri', 'profile')\n            }).then(this.onFetch);\n\n            // Listen to events\n            this.listenTo(Radio.channel('global'), 'search:change', _.debounce(this.search, 150));\n        },\n\n        onDestroy: function() {\n            Radio.request('fuzzySearch', 'region:empty');\n        },\n\n        /**\n         * Searches and shows the result.\n         */\n        search: function(text) {\n            // Wait until everything is fetched\n            if (this.wait) {\n                var self = this;\n                return this.wait.then(function() {\n                    self.search(text);\n                });\n            }\n\n            var result = this.notes.fuzzySearch(text);\n            this.notes.reset(result);\n\n            if (!this.view.isRendered) {\n                Radio.request('fuzzySearch', 'region:show', this.view);\n            }\n        },\n\n        onFetch: function(collection) {\n            this.wait = null;\n            this.notes = collection;\n\n            // Instantiate notes collection and a view\n            this.view = new View({collection: this.notes});\n\n            // Events\n            this.listenTo(this.view, 'childview:navigate:search', this.filter, this);\n        },\n\n        /**\n         * It triggers \"filter\" event and stops this module.\n         */\n        filter: function(model) {\n            model = model.model;\n            Radio.request('appNote', 'filter', {\n                filter : 'search',\n                query  : model.get('title')\n            });\n            Radio.trigger('global', 'search:hidden');\n        }\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'modules',\n    'modules/fuzzySearch/regions/sidebar',\n    'modules/fuzzySearch/controllers/main'\n], function(_, Radio, Marionette, Modules, Region, Controller) {\n    'use strict';\n\n    /**\n     * Fuzzy search module.\n     *\n     * It creates a new region on `init` event in order to show search results\n     * there.\n     *\n     * Listens to\n     * ----------\n     * Events:\n     * 1. channel: `global`, event: `search:shown`\n     *    starts itself\n     * 2. channel: `global`, event: `search:hidden`\n     *    stops itself\n     *\n     * Requests:\n     * 1. channel: `fuzzySearch`, request: `region:show`\n     *    renders the provided view in fuzzy search region\n     * 2. channel: `fuzzySearch`, request: `region:empty`\n     */\n    var Fuzzy = Modules.module('FuzzySearch', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    Fuzzy.on('start', function() {\n        console.info('FuzzySearch module has started');\n\n        Fuzzy.controller = new Controller();\n    });\n\n    Fuzzy.on('stop', function() {\n        console.info('FuzzySearch module has stoped');\n\n        Fuzzy.controller.destroy();\n        Fuzzy.controller = null;\n    });\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n        console.info('FuzzySearch module has been initialized');\n\n        // Create a new region\n        $('#sidebar').append(\n            $('<div id=\"sidebar--fuzzy\" class=\"layout--body -scroll hidden\"/>')\n        );\n        var region = new Region();\n\n        // Listen to search events\n        Radio.channel('global')\n        .on('search:shown', Fuzzy.start, Fuzzy)\n        .on('search:hidden', Fuzzy.stop, Fuzzy);\n\n        // Module events\n        Radio.channel('fuzzySearch')\n        .reply('region:show', region.show, region)\n        .reply('region:empty', region.empty, region);\n    });\n\n    return Fuzzy;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/regions/sidebar.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette'\n], function (_, $, Marionette) {\n    'use strict';\n\n    var SidebarRegion = Marionette.Region.extend({\n        el : '#sidebar--fuzzy',\n\n        onShow: function () {\n            this.$body = this.$body || $('body');\n            this.$body.addClass('-fuzzy');\n            this.$el.removeClass('hidden');\n        },\n\n        onEmpty: function () {\n            this.$el.addClass('hidden');\n            this.$body.removeClass('-fuzzy');\n        }\n\n    });\n\n    return SidebarRegion;\n});\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/templates/composite.html",
    "content": "<div class=\"main notes-list\">\n</div>\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/templates/item.html",
    "content": "<a href=\"#{{ link() }}\" class=\"list-group-item list--item\"\n    id=\"note-{{ id }}\" data-id=\"{{id}}\">\n    <h4 class=\"list-group-item-heading\">\n        {{ title }}\n        <span class=\"favorite pull-right icon-star-empty\n            <% if (isFavorite === 1) {%>icon-favorite<% } %>\">\n        </span>\n    </h4>\n</a>\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/views/composite.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'jquery',\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'views/loader',\n    'modules/fuzzySearch/views/item',\n    'text!modules/fuzzySearch/templates/composite.html'\n], function($, _, Marionette, Radio, LoaderView, ItemView, Tmpl) {\n    'use strict';\n\n    /**\n     * Composite view which shows \"fuzzy\" search results\n     */\n    var CompositeView = Marionette.CompositeView.extend({\n        template  : _.template(Tmpl),\n\n        childView          : ItemView,\n        childViewContainer : '.notes-list',\n        childViewOptions   : {},\n        emptyView          : LoaderView\n    });\n\n    return CompositeView;\n});\n"
  },
  {
    "path": "app/scripts/modules/fuzzySearch/views/item.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'text!modules/fuzzySearch/templates/item.html'\n], function(_, Radio, Marionette, Tmpl) {\n    'use strict';\n\n    /**\n     * Item view\n     */\n    var View = Marionette.ItemView.extend({\n        template: _.template(Tmpl),\n\n        className: 'list-group list--group',\n\n        events: {\n            'click .list-group-item': 'triggerSearch'\n        },\n\n        triggerSearch: function() {\n            this.trigger('navigate:search');\n        },\n\n        templateHelpers: function() {\n            return {\n                // Generate link\n                link: function() {\n                    return Radio.request('uri', 'link', {\n                        filter : 'search',\n                        query  : encodeURIComponent(this.title)\n                    }, this);\n                }\n            };\n        }\n    });\n\n    return View;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/importExport/controller.js",
    "content": "/* global define */\ndefine([\n    'underscore',\n    'q',\n    'marionette',\n    'backbone.radio',\n    'jszip',\n    'helpers/fileSaver'\n], function(_, Q, Marionette, Radio, JSZip, fileSaver) {\n    'use strict';\n\n    /**\n     * Import or export all data from Laverna app to a zip file.\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            this.options = options;\n            this[options.method]();\n        },\n\n        /**\n         * Import data from a ZIP archive.\n         */\n        import: function() {\n\n            // Check file extension\n            if (!this.options.files || !this.options.files.length ||\n               (this.options.files[0].type !== 'application/zip' &&\n               _.last(this.options.files[0].name.split('.')) !== 'zip')) {\n                return;\n            }\n\n            var reader = new FileReader(),\n                self   = this;\n\n            this.zip   = new JSZip();\n\n            reader.onload = function(evt) {\n                try {\n                    self.zip.load(evt.target.result);\n\n                    // Start importing process\n                    self.importData()\n                    .then(function() {\n                        document.location.reload();\n                    });\n                } catch (e) {\n                }\n            };\n\n            reader.readAsArrayBuffer(this.options.files[0]);\n        },\n\n        /**\n         * Import data from a loaded ZIP archive to DB.\n         */\n        importData: function() {\n            var self     = this,\n                promises = [];\n\n            _.each(this.zip.files, function(file) {\n\n                // Ignore directories and non JSON files\n                if (file.dir || _.last(file.name.split('.')) !== 'json') {\n                    return;\n                }\n\n                var path = file.name.split('/');\n\n                // Notes should be imported differently\n                if (path[2] === 'notes') {\n                    return promises.push(function() {\n                        return self.importNote(path[1], file.name, self.zip.file(file.name).asText());\n                    });\n                }\n\n                // path[2].split('.json')[0] === [notebooks|tags|configs]\n                promises.push(function() {\n                    return self.importCollection(path[1], path[2].split('.json')[0], self.zip.file(file.name).asText());\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .fail(function(e) {\n                console.error('importProfiles:', e);\n            });\n        },\n\n        /**\n         * Import an array of items.\n         *\n         * @type string profile\n         * @type string type [notebooks|tags|configs]\n         * @type object data\n         */\n        importCollection: function(profile, type, data) {\n            if (_.indexOf(['notebooks', 'tags', 'configs'], type) === -1) {\n                return new Q();\n            }\n\n            return Radio.request(type, 'save:all:raw', JSON.parse(data), {\n                profile: profile\n            });\n        },\n\n        /**\n         * Import a note to DB.\n         *\n         * @type string profile\n         * @type string name\n         * @type object data\n         */\n        importNote: function(profile, name, data) {\n            data = JSON.parse(data);\n\n            name = name.split('.');\n            name[name.length - 1] = 'md';\n            name = name.join('.');\n\n            data.content = this.zip.file(name).asText();\n\n            return Radio.request('notes', 'save:raw', data, {\n                profile: profile\n            });\n        },\n\n        /**\n         * Export all data to a ZIP archive.\n         */\n        export: function() {\n            if (this.options.data) {\n                return this.exportData();\n            }\n\n            var profiles = Radio.request('configs', 'get:config', 'appProfiles'),\n                promises = [],\n                self     = this;\n\n            this.zip = new JSZip();\n\n            // Get data from each profile\n            _.each(profiles, function(profile) {\n                promises.push(function() {\n                    return self.exportProfile(profile);\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                var content = self.zip.generate({type: 'blob'});\n                return self.saveToFile(content, 'laverna-backup.zip');\n            });\n        },\n\n        /**\n         * Export already fetched data.\n         */\n        exportData: function() {\n            if (!_.keys(this.options.data).length) {\n                return;\n            }\n\n            var content;\n            this.zip = new JSZip();\n\n            _.each(this.options.data, function(data, profile) {\n                this.addToZip(profile, data.notes, data.notebooks, data.tags, data.configs);\n            }, this);\n\n            content = this.zip.generate({type: 'blob'});\n            return this.saveToFile(content, 'laverna-backup.zip');\n        },\n\n        /**\n         * Export data from a profile.\n         * @type string profile\n         */\n        exportProfile: function(profile) {\n            var self = this;\n\n            return Q.all([\n                Radio.request('notes', 'fetch', {profile: profile}),\n                Radio.request('notebooks', 'fetch', {profile: profile}),\n                Radio.request('tags', 'fetch', {profile: profile}),\n                Radio.request('configs', 'fetch', {profile: profile})\n            ])\n            .spread(function(notes, notebooks, tags, configs) {\n                notes = notes.fullCollection ? notes.fullCollection : notes;\n                return self.addToZip(profile, notes.toJSON(), notebooks.toJSON(), tags.toJSON(), configs.toJSON());\n            })\n            .fail(function(e) {\n                console.error('exportProfile:', e);\n            });\n        },\n\n        /**\n         * Add collections to ZIP archive.\n         * @type string profile\n         */\n        addToZip: function(profile, notes, notebooks, tags, configs) {\n            var self = this,\n                path = 'laverna-backups/' + profile + '/';\n\n            // Save notes as JSON and Markdown files\n            _.each(notes, function(note) {\n                self.zip.file(path + 'notes/' + note.id + '.md', note.content);\n                self.zip.file(path + 'notes/' + note.id + '.json', JSON.stringify(_.omit(note, 'content')));\n            });\n\n            self.zip.file(path + 'notebooks.json', JSON.stringify(notebooks));\n            self.zip.file(path + 'tags.json', JSON.stringify(tags));\n            self.zip.file(path + 'configs.json', JSON.stringify(configs));\n\n            return;\n        },\n\n        saveToFile: function(data, fileName) {\n            return fileSaver(data, fileName)\n            .then(_.bind(function() {\n\n                // Destroy this controller\n                this.zip = null;\n                this.destroy();\n            }, this));\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/modules/importExport/module.js",
    "content": "/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules',\n    'modules/importExport/controller'\n], function(_, Marionette, Radio, Modules, Controller) {\n    'use strict';\n\n    Radio.request('init', 'add', 'app:before', function() {\n        Radio.reply('importExport', {\n            'import': function(files) {\n                new Controller({method: 'import', files: files});\n            },\n\n            'export': function(data) {\n                new Controller({method: 'export', data: data});\n            }\n        });\n    });\n\n});\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/controller.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules/linkDialog/views/dialog',\n    'modules/linkDialog/views/collection'\n], function(_, Marionette, Radio, Layout, View) {\n    'use strict';\n\n    /**\n     * Link dialog controller\n     */\n    var Controller = Marionette.Object.extend({\n\n        initialize: function(options) {\n            _.bindAll(this, 'renderDropdown');\n            this.options = options;\n\n            // Instantiate and show the view\n            this.layout = new Layout();\n            Radio.request('global', 'region:show', 'modal', this.layout);\n\n            // Prefetch notes\n            this.wait = Radio.request('notes', 'fetch', {\n                pageSize : 10,\n                profile  : Radio.request('uri', 'profile')\n            })\n            .then(this.renderDropdown);\n\n            // Events\n            this.listenTo(this.layout, 'save', this.link);\n            this.listenTo(this.layout, 'redirect', this.destroy);\n            this.listenTo(this.layout, 'search', this.search);\n            this.listenTo(this.layout, 'create:note', this.createNote);\n        },\n\n        onDestroy: function() {\n            if (this.options.callback) {\n                this.options.callback(null);\n            }\n\n            this.stopListening();\n            Radio.request('global', 'region:empty', 'modal');\n        },\n\n        renderDropdown: function(notes) {\n            this.wait  = null;\n            this.notes = notes;\n\n            this.view = new View({\n                collection: notes\n            });\n            this.layout.notes.show(this.view);\n        },\n\n        /**\n         * Provide the url to editor callback\n         */\n        link: function(url) {\n            url  = typeof url === 'string' ? url : this.layout.ui.url.val().trim();\n            this.options.callback(url !== '' ? url : null);\n\n            // Close the dialog\n            this.options.callback = null;\n            this.destroy();\n        },\n\n        /**\n         * Search notes\n         */\n        search: function(text) {\n            // Notes have not been fetched yet\n            if (this.wait) {\n                return this.wait.then(_.bind(function() {\n                    return this.search(text);\n                }, this));\n            }\n\n            this.notes.reset(this.notes.fuzzySearch(text));\n            this.layout.trigger('dropdown:toggle', this.notes.length);\n        },\n\n        /**\n         * Create a new note\n         */\n        createNote: function() {\n            var title = this.layout.ui.url.val().trim(),\n                when  = Radio.request('notes', 'get:model', {\n                    profile: Radio.request('uri', 'profile')\n                });\n\n            when.then(function(model) {\n                return Radio.request('notes', 'save', model, {title: title});\n            })\n            .then(_.bind(function(model) {\n                this.link('#' + Radio.request('uri', 'link', {}, model));\n            }, this));\n        },\n\n    });\n\n    return Controller;\n});\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'modules',\n    'backbone.radio',\n    'modules/linkDialog/controller'\n], function(_, Q, Modules, Radio, Controller) {\n    'use strict';\n\n    /**\n     * Custom link dialog for an editor.\n     *\n     * Listens for events:\n     * 1. channel: `editor`, event: `destroy` - stops itself\n     *\n     * Adds `insertLinkDialog` hook to Pagedown editor.\n     */\n    var LinkDialog = Modules.module('LinkDialog', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    LinkDialog.on('before:start', function(options) {\n        LinkDialog.controller = new Controller(options);\n\n        this.listenTo(LinkDialog.controller, 'destroy', LinkDialog.stop);\n    });\n\n    LinkDialog.on('before:stop', function() {\n        console.info('LinkDialog stopped');\n\n        this.stopListening();\n        LinkDialog.controller = null;\n    });\n\n    // Stop the module when editor is closed\n    Radio.request('init', 'add', 'module', function() {\n        Radio.on('editor', 'destroy', LinkDialog.stop, LinkDialog);\n\n        Radio.reply('editor', 'show:link', function() {\n            var defer = Q.defer();\n\n            LinkDialog.start({callback: function(link) {\n                defer.resolve(link);\n            }});\n\n            return defer.promise;\n        });\n    });\n\n    return LinkDialog;\n});\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/templates/dialog.html",
    "content": "<div class=\"modal-dialog\">\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <button type=\"button\" class=\"close\" data-dismiss=\"modal\" aria-hidden=\"true\">&times;</button>\n            <h4 class=\"modal-title\">\n                {{i18n('Hyperlink')}}\n            </h4>\n        </div>\n        <div class=\"modal-body\">\n            <form action=\"#\" class=\"form\" method=\"post\">\n                <div class=\"form-group\">\n                    <input name=\"url\" class=\"form-control\" type=\"text\" value=\"\" placeholder=\"{{ i18n('notes.hyperlink-dialog') }}\" />\n                    <div id=\"noteMenu\" class=\"dropdown\"></div>\n                </div>\n            </form>\n        </div>\n        <div class=\"modal-footer\">\n            <button class=\"btn btn-default cancelBtn\">{{ i18n('Cancel') }}</button>\n            <button class=\"btn btn-success ok\">{{ i18n('OK') }}</button>\n            <button title=\"{{i18n('notes.create and attach')}}\" class=\"btn btn-info create hidden\">\n                {{ i18n('notes.create') }}\n            </button>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/templates/item.html",
    "content": "<a href=\"#\" data-id=\"{{id}}\">{{title}}</a>\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/views/collection.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'modules/linkDialog/views/item'\n], function(_, Marionette, Radio, ItemView) {\n    'use strict';\n\n    /**\n     * Shows a list of notes\n     */\n    var View = Marionette.CollectionView.extend({\n        tagName   : 'ul',\n        className : 'dropdown-menu',\n\n        childView          : ItemView,\n        childViewContainer : '.dropdown-menu',\n\n        events: {\n            'click a': 'link'\n        },\n\n        link: function(e) {\n            var id    = $(e.currentTarget).attr('data-id'),\n                model = this.collection.get(id);\n\n            Radio.trigger('LinkDialog', 'attach:link', {\n                url   : '#' + Radio.request('uri', 'link', {}, model),\n                model : model\n            });\n\n            return false;\n        }\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/views/dialog.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'behaviors/modalForm',\n    'text!modules/linkDialog/templates/dialog.html'\n], function(_, Marionette, Radio, ModalForm, Tmpl) {\n    'use strict';\n\n    /**\n     * Link dialog view\n     */\n    var View = Marionette.LayoutView.extend({\n        template  : _.template(Tmpl),\n        className : 'modal fade',\n\n        regions: {\n            notes: '#noteMenu'\n        },\n\n        behaviors: {\n            ModalForm: {\n                behaviorClass : ModalForm,\n                uiFocus       : 'url'\n            }\n        },\n\n        ui: {\n            url      : '[name=url]',\n            dropdown : '.dropdown',\n            create   : '.create'\n        },\n\n        events: {\n            'keyup @ui.url' : 'handleUrl'\n        },\n\n        triggers:{\n            'click @ui.create' : 'create:note'\n        },\n\n        initialize: function() {\n            this.listenTo(this, 'dropdown:toggle', this.dropdownToggle);\n            this.listenTo(Radio.channel('LinkDialog'), 'attach:link', this.attachLink);\n        },\n\n        /**\n         * Hide or show the dropdown menu\n         */\n        dropdownToggle: function(length) {\n            this.ui.dropdown.toggleClass('open', length || 0 > 0);\n        },\n\n        /**\n         * When a link is attached, hide create button and dropdown menu\n         */\n        onAttachLink: function() {\n            this.ui.create.addClass('hidden');\n            this.ui.url.focus();\n            this.dropdownToggle();\n        },\n\n        attachLink: function(data) {\n            this.ui.url.val(data.url);\n            this.onAttachLink();\n        },\n\n        handleUrl: _.throttle(function() {\n            var val = this.ui.url.val().trim();\n\n            // If it is a link, we don't have to do anything\n            if (val === '' || val.match(/^(#|(https?|file|ftp):\\/)/) !== null) {\n                return this.onAttachLink();\n            }\n\n            // Search for an existing note and show note creation button\n            this.ui.create.toggleClass('hidden', false);\n            this.trigger('search', val);\n        }, 300),\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/linkDialog/views/item.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'text!modules/linkDialog/templates/item.html'\n], function(_, Marionette, Radio, Tmpl) {\n    'use strict';\n\n    var View = Marionette.ItemView.extend({\n        template : _.template(Tmpl),\n        tagName  : 'li'\n    });\n\n    return View;\n});\n"
  },
  {
    "path": "app/scripts/modules/markdown/libs/markdown-it-file.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, URL, webkitURL */\ndefine([\n    'underscore'\n], function(_) {\n    'use strict';\n\n    /**\n     * A Markdown-it plugin which replaces pseudo file locations\n     * with generated ObjectURLs.\n     */\n    var File = {\n        pattern: /#file:([a-z0-9\\-])+/,\n\n        init: function(md) {\n            var origI = md.renderer.rules.image;\n\n            md.renderer.rules.link_open = function(tokens, idx, opt, env, self) { // jshint ignore:line\n                File._replaceLink(tokens, idx, opt, env);\n                return self.renderToken(tokens, idx, opt);\n            };\n\n            md.renderer.rules.image = function(tokens, idx, opt, env, self) {\n                File._replaceLink(tokens, idx, opt, env);\n                return origI(tokens, idx, opt, env, self);\n            };\n        },\n\n        /**\n         * Revoke all ObjectURLs which aren't under use.\n         */\n        revokeURLs: function(objectURLs, model) {\n            _.each(objectURLs, function(obj, key) {\n                if (_.findWhere(model.files, {id: key})) {\n                    return;\n                }\n\n                (URL || webkitURL).revokeObjectURL(obj);\n                delete objectURLs[key];\n            });\n        },\n\n        /**\n         * Replace all pseudo IDs with objectURLs.\n         */\n        _replaceLink: function(tokens, idx, options, env) {\n            var type = (tokens[idx].type === 'image' ? 'src' : 'href'),\n                attr = tokens[idx].attrs[tokens[idx].attrIndex(type)];\n\n            if (!File.pattern.test(attr[1])) {\n                return;\n            }\n\n            var id  = attr[1].match(File.pattern)[0].replace('#file:', '');\n\n            // Add files IDs to env\n            if (env) {\n                env.files = env.files || [];\n                env.files.push(id);\n            }\n\n            if (!env.modelData) {\n                return;\n            }\n\n            File._createURL(attr, id, env);\n        },\n\n        /**\n         * Create an ObjectURL.\n         */\n        _createURL: function(attr, id, env) {\n            var file = _.findWhere(env.modelData.files, {id: id});\n\n            if (!file) {\n                return;\n            }\n\n            // Generate a new link only if it wasn't generated before\n            var src = env.objectURLs[file.id];\n            src     = src || (URL || webkitURL).createObjectURL(file.src);\n\n            env.objectURLs[file.id] = src;\n            attr[1] = src;\n        },\n    };\n\n    return File;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/markdown/libs/markdown-it-task.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n], function(_) {\n    'use strict';\n\n    /**\n     * Tasks plugin for Markdown-it.\n     */\n    var Task = {\n        pattern    : /\\[(X|\\s|\\_|\\-)?\\]\\s(.*)/i,\n        globPattern: /\\[(X|\\s|\\_|\\-)?\\]\\s(.*)/gi,\n\n        /**\n         * Initialize Markdown-it plugin\n         */\n        init: function(md) {\n            md.core.ruler.push('task', Task._taskReplace(md));\n            md.renderer.rules.task_tag = Task._renderTask; // jshint ignore:line\n        },\n\n        /**\n         * Toggle a Markdown task.\n         */\n        toggle: function(data) {\n            var count = 0;\n\n            data.content = data.content.replace(Task.globPattern, function(match, checked, value) {\n                count++;\n\n                // It's not the task we need to toggle\n                if (count !== data.taskId) {\n                    return match;\n                }\n\n                checked = (checked === 'x' || checked === 'X') ? ' ' : 'x';\n                return '[' + checked + '] ' + value;\n            });\n\n            return data.content;\n        },\n\n        /**\n         * Tasks plugin for Markdown-it.\n         */\n        _taskReplace: function(md) {\n            var arrayReplaceAt = md.utils.arrayReplaceAt;\n\n            return function(state) {\n                var count = 0,\n                    matches;\n\n                // Go through tokens and continue if the block is inline\n                _.each(state.tokens, function(token) {\n\n                    if (token.type !== 'inline') {\n                        matches = token.content.match(Task.globPattern);\n                        if (matches) {\n                            count += matches.length;\n                        }\n\n                        return;\n                    }\n\n                    // Find children which match the pattern\n                    _.each(token.children, function(child, i) {\n\n                        if (child.type === 'text' && Task.pattern.test(child.content)) {\n                            count++;\n\n                            token.children = arrayReplaceAt(\n                                token.children,\n                                i,\n                                Task._replaceToken(child, state.Token, count)\n                            );\n                        }\n                    });\n                });\n            };\n        },\n\n        /**\n         * Replace elements with checkboxes.\n         */\n        _replaceToken: function(original, Token, id) {\n            var matches = original.content.match(Task.pattern),\n                value   = matches[1],\n                label   = matches[2],\n                checked = (value === 'X' || value === 'x'),\n                token;\n\n            // Create a new token\n            token = new Token('task_tag', '', 0);\n            token.meta = {\n                label   : label,\n                checked : checked,\n                id      : id\n            };\n            token.children = [];\n\n            return [token];\n        },\n\n        _renderTask: function(tokens, id, f, env) {\n            var m = tokens[id].meta;\n\n            // Add task counts to env\n            if (env) {\n                env.tasks = env.tasks || [];\n                env.tasks.push(m.label);\n\n                if (m.checked) {\n                    env.taskCompleted = (env.taskCompleted || 0) + 1;\n                }\n                else {\n                    env.taskCompleted = (env.taskCompleted || 0);\n                }\n            }\n\n            return '<label class=\"task task--checkbox\">' +\n                   '<input data-task=\"' + m.id + '\" type=\"checkbox\"' + (m.checked ? 'checked=\"checked\"' : '') + ' class=\"checkbox--input\" />' +\n                   '<svg class=\"checkbox--svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">' +\n                   '<path class=\"checkbox--path\" d=\"M16.667,62.167c3.109,5.55,7.217,10.591,10.926,15.75 c2.614,3.636,5.149,7.519,8.161,10.853c-0.046-0.051,1.959,2.414,2.692,2.343c0.895-0.088,6.958-8.511,6.014-7.3 c5.997-7.695,11.68-15.463,16.931-23.696c6.393-10.025,12.235-20.373,18.104-30.707C82.004,24.988,84.802,20.601,87,16\"></path>' +\n                   '</svg>' +\n                   '<span class=\"checkbox--text\">' + m.label + '</span></label>';\n        },\n    };\n\n    return Task;\n});\n"
  },
  {
    "path": "app/scripts/modules/markdown/libs/markdown-it.js",
    "content": "/* global define */\ndefine([\n    'underscore',\n    'q',\n    'markdown-it',\n    'prism/bundle',\n    'markdown-it-san',\n    'markdown-it-hash',\n    'markdown-it-math',\n    'markdown-it-imsize',\n    'modules/markdown/libs/markdown-it-task',\n    'modules/markdown/libs/markdown-it-file',\n], function(_, Q, MarkdownIt, Prism, sanitizer, hash, math, imsize, task, file) {\n    'use strict';\n\n    /**\n     * Markdown parser helper.\n     */\n    function Markdown() {\n\n        // Initialize MarkdownIt\n        this.md = new MarkdownIt({\n            html     : true,\n            xhtmlOut : true,\n            breaks   : true,\n            linkify  : true,\n            highlight: function(code, lang) {\n                if (!Prism.languages[lang]) {\n                    return '';\n                }\n\n                return Prism.highlight(code, Prism.languages[lang]);\n            }\n        });\n\n        this.configure();\n    }\n\n    _.extend(Markdown.prototype, {\n        objectURLs: {},\n\n        /**\n         * Configure MarkdownIt.\n         */\n        configure: function() {\n            this.md\n            .use(sanitizer)\n            .use(imsize)\n            .use(math, {\n                inlineOpen       : '$',\n                inlineClose      : '$',\n                blockOpen        : '$$',\n                blockClose       : '$$',\n                renderingOptions : {},\n                inlineRenderer   : function(tokens) {\n                    return '<span class=\"math inline\">$' + tokens + '$</span>';\n                },\n                blockRenderer    : function(tokens) {\n                    return '<div class=\"math block\">$$' + tokens + '$$</div>';\n                },\n            })\n            .use(hash, {\n\t\t\t\thashtagRegExp: '[\\\\u0021-\\\\uFFFF\\\\w\\\\-]+|<3',\n\t\t\t\tpreceding: '^|\\\\s'\n\t\t\t\t})\n            .use(task.init)\n            .use(file.init)\n            ;\n\n            // Make table responsive\n            this.md.renderer.rules.table_open  = function() { // jshint ignore:line\n                return '<div class=\"table-responsive\"><table>';\n            };\n            this.md.renderer.rules.table_close  = function() { // jshint ignore:line\n                return '</table></div>';\n            };\n\n            this.md.renderer.rules.hashtag_open  = function(tokens, idx, f, env) { // jshint ignore:line\n                var tagName = tokens[idx].content.toLowerCase();\n\n                if (env) {\n                    env.tags = env.tags || [];\n                    env.tags.push(tagName);\n                }\n\n                return '<a href=\"#/notes/f/tag/q/' + tagName + '\" class=\"label label-default\">';\n            };\n        },\n\n        /**\n         * Convert Markdown to HTML.\n         */\n        render: function(model) {\n            var env  = {\n                modelData : model,\n                objectURLs: this.objectURLs\n            };\n\n            if (model.id) {\n                file.revokeURLs(this.objectURLs, model);\n            }\n\n            return new Q(\n                this.md.render(_.unescape(model.content), env)\n            );\n        },\n\n        /**\n         * Toggle a task's status.\n         */\n        taskToggle: function(data) {\n            data.content = _.unescape(data.content);\n            data.content = task.toggle(data);\n\n            return this.parse(data.content)\n            .then(function(env) {\n                return _.extend({content: data.content}, env);\n            });\n        },\n\n        /**\n         * Parse Markdown for tags, tasks, etc.\n         */\n        parse: function(content) {\n            var env = {};\n\n            return new Q(\n                this.md.render(_.unescape(content), env)\n            )\n            .then(function() {\n                env.tags    = env.tags ? _.uniq(env.tags) : [];\n                env.files   = env.files ? _.uniq(env.files) : [];\n                env.taskAll = env.tasks ? env.tasks.length : 0;\n                return env;\n            });\n        },\n    });\n\n    return Markdown;\n});\n"
  },
  {
    "path": "app/scripts/modules/markdown/libs/markdown.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'q',\n    'underscore',\n    'backbone.radio',\n    'modules/markdown/libs/markdown-it'\n], function(Q, _, Radio, MarkdownIt) {\n    'use strict';\n\n    // Parse Markdown without Webworkers\n    if (!Radio.request('global', 'use:webworkers')) {\n        return MarkdownIt;\n    }\n\n    /**\n     * Parse Markdown in WebWorker.\n     */\n    function Markdown() {\n        var self    = this;\n        this.worker = new Worker('scripts/modules/markdown/workers/markdown.js');\n\n        // Promise which signifies whether the worker is ready\n        this.workerPromise = Q.defer();\n\n        this.worker.onmessage = function(data) {\n            var msg = data.data;\n\n            switch (msg.msg) {\n\n                // Webworker is ready\n                case 'ready':\n                    self.workerPromise.resolve();\n                    break;\n\n                // Request was fullfilled\n                case 'done':\n                    self.promises[msg.promiseId].resolve(msg.data);\n                    delete self.promises[msg.promiseId];\n                    break;\n\n                // Request failed with errors\n                case 'fail':\n                    self.promises[msg.promiseId].reject(msg.data);\n                    delete self.promises[msg.promiseId];\n                    break;\n\n                default:\n            }\n        };\n    }\n\n    _.extend(Markdown.prototype, {\n        promises: [],\n\n        render: function(model) {\n            return this._emit('render', model);\n        },\n\n        parse: function(content) {\n            return this._emit('parse', content);\n        },\n\n        taskToggle: function(data) {\n            return this._emit('taskToggle', data);\n        },\n\n        /**\n         * Send a message to the Webworker.\n         *\n         * @type string msg\n         * @type object data\n         */\n        _emit: function(msg, data) {\n            var self = this;\n\n            // Worker is ready\n            if (!this.workerPromise.promise.isPending()) {\n                return this._send(msg, data);\n            }\n\n            return this.workerPromise.promise\n            .then(function() {\n                return self._send(msg, data);\n            });\n        },\n\n        _send: function(msg, data) {\n\n            // Generate a unique ID for the worker's promise\n            var promiseId = ((1 + Math.random()) * 0x10000);\n            this.promises[promiseId] = Q.defer();\n\n            this.worker.postMessage({\n                msg       : msg,\n                promiseId : promiseId,\n                data      : data\n            });\n\n            return this.promises[promiseId].promise;\n        },\n\n    });\n\n    return Markdown;\n\n});\n"
  },
  {
    "path": "app/scripts/modules/markdown/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'modules/markdown/libs/markdown'\n], function(_, Radio, Markdown) {\n    'use strict';\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n        console.info('Markdown module has been initialized');\n        var md = new Markdown();\n\n        Radio.reply('markdown', {\n            'render'     : function(model) {\n                var data   = (typeof model === 'string' ? {content: model} : _.clone(model.attributes || model));\n                data.files = _.pluck(model.files || [], 'attributes');\n\n                return md.render(data);\n            },\n\n            'parse'      : md.parse,\n            'task:toggle': md.taskToggle,\n        }, md);\n\n        // Make the initilizer wait until the worker has spawned\n        if (md.workerPromise) {\n            return md.workerPromise.promise;\n        }\n    });\n\n});\n"
  },
  {
    "path": "app/scripts/modules/markdown/workers/markdown.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global requirejs, importScripts, self */\n'use strict';\nimportScripts('../../../../bower_components/requirejs/require.js');\n\nrequirejs.config({\n    baseUrl: '../../../',\n    packages: [\n        // Prismjs\n        {\n            name     : 'prism',\n            location : '../bower_components/prism',\n            main     : 'bundle'\n        },\n    ],\n    paths: {\n        q                    : '../bower_components/q/q',\n        underscore           : '../bower_components/underscore/underscore',\n        'markdown-it'        : '../bower_components/markdown-it/dist/markdown-it.min',\n        'markdown-it-san'    : '../bower_components/markdown-it-sanitizer/dist/markdown-it-sanitizer.min',\n        'markdown-it-hash'   : '../bower_components/markdown-it-hashtag/dist/markdown-it-hashtag.min',\n        'markdown-it-math'   : '../bower_components/markdown-it-math/dist/markdown-it-math.min',\n        'markdown-it-imsize' : '../bower_components/markdown-it-imsize/dist/markdown-it-imsize.min',\n    },\n    shim: {\n        'prism/bundle': {\n            exports: 'Prism'\n        },\n    }\n});\n\n// Prevent Prismjs from listening to our events\nself.addEventListener = undefined;\n\nrequirejs([\n    'modules/markdown/libs/markdown-it'\n], function(Markdown) {\n    var markdown = new Markdown();\n\n    // Listen to the webworker messages\n    self.onmessage = function(data) {\n        var msg = data.data;\n\n        if (markdown[msg.msg]) {\n            return markdown[msg.msg](msg.data)\n            .then(function(result) {\n                self.postMessage({\n                    msg       : 'done',\n                    promiseId : msg.promiseId,\n                    data      : result\n                });\n            })\n            .fail(function(e) {\n                self.postMessage({\n                    msg       : 'fail',\n                    promiseId : msg.promiseId,\n                    data      : e\n                });\n            });\n        }\n\n        console.error('MarkdownIt module:', 'Method doesn\\'t exist', msg.msg);\n    };\n\n    // Post a message that the worker is ready\n    self.postMessage({msg: 'ready'});\n});\n"
  },
  {
    "path": "app/scripts/modules/mathjax/libs/mathjax.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'q',\n    'mathjax'\n], function(_, Q, MathJax) {\n    'use strict';\n\n    var MathHelper = {\n\n        /**\n         * Render MathJax.\n         */\n        render: function(view) {\n            var $divs;\n\n            this.view = view || this.view;\n            $divs = this.view.$el.find('.math');\n\n            // Don't bother with processing MathJax\n            if (!$divs.length) {\n                return;\n            }\n\n            return this.preProcess($divs)\n            .then(function() {\n                MathJax.Hub.Queue(['Typeset', MathJax.Hub, $divs.toArray()]);\n            })\n            .fail(function(e) {\n                console.error('MathJax Error:', e);\n            });\n        },\n\n        /**\n         * Process MathJax expressions.\n         */\n        preProcess: function($divs) {\n            var groups   = [],\n                promises = [],\n                defer,\n                ng;\n\n            /**\n             * Divide divs into groups of 5 elements in order to\n             * preprocess MathJax later.\n             */\n            $divs.each(function(i, val) {\n                ng = Math.trunc(i / 5);\n                groups[ng] = groups[ng] || [];\n                groups[ng].push(val);\n            });\n\n            _.each(groups, function(group) {\n                promises.push(function() {\n                    defer = Q.defer();\n                    MathJax.Hub.PreProcess(group, function() {\n                        defer.resolve();\n                    });\n\n                    return defer.promise;\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                promises = null;\n                defer    = null;\n                groups   = null;\n\n                return;\n            });\n        },\n\n    };\n\n    return MathHelper;\n});\n"
  },
  {
    "path": "app/scripts/modules/mathjax/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'modules',\n    'backbone.radio',\n    'mathjax',\n    'modules/mathjax/libs/mathjax'\n], function(_, Modules, Radio, MathJax, math) {\n    'use strict';\n\n    /**\n     * MathJax module. Renders MathJax.\n     */\n    var MathjaxModule = Modules.module('MathjaxModule', {});\n\n    // Configure MathJax\n    MathJax.Hub.Config({\n        jax     : ['input/TeX', 'output/HTML-CSS'],\n        tex2jax : {\n            inlineMath     : [['$', '$']],\n            displayMath    : [['$$', '$$']],\n            processClass   : 'math',\n            ignoreClass    : 'layout',\n            processEscapes : true\n        }\n    });\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    MathjaxModule.on('start', function(view) {\n        // Render MathJax on start\n        math.render(view);\n\n        // Re-render MathJax every time when preview is refreshed.\n        Radio.on('editor', 'preview:refresh', _.debounce(math.render, 350), math);\n    });\n\n    MathjaxModule.on('stop', function() {\n        console.info('MathJax has stopped');\n\n        math.view = null;\n        Radio.off('editor', 'preview:refresh');\n    });\n\n    /**\n     * Start listening to events.\n     */\n    Radio.request('init', 'add', 'module', function() {\n\n        // When a note is shown, start this module\n        Radio.on('noteView', {\n            'view:render'  : MathjaxModule.start,\n            'view:destroy' : MathjaxModule.stop\n        }, MathjaxModule);\n\n        // When an editor view is shown, start this module\n        Radio.on('editor', {\n            'view:render'  : MathjaxModule.start,\n            'view:destroy' : MathjaxModule.stop\n        }, MathjaxModule);\n    });\n\n    return MathjaxModule;\n});\n"
  },
  {
    "path": "app/scripts/modules/modules.json",
    "content": "[\n    {\n        \"id\"          : \"mathjax\",\n        \"name\"        : \"MathJax\",\n        \"description\" : \"MathJax\",\n        \"minVersion\"  : \"\",\n        \"maxVersion\"  : \"\",\n        \"platforms\"   : [\n            \"electron\",\n            \"browser\",\n            \"mobile\"\n        ]\n    },\n    {\n        \"id\"          : \"fuzzySearch\",\n        \"name\"        : \"fuzzySearch\",\n        \"description\" : \"fuzzySearch module\",\n        \"minVersion\"  : \"\",\n        \"maxVersion\"  : \"\",\n        \"platforms\"   : [\n            \"electron\",\n            \"browser\",\n            \"mobile\"\n        ]\n    },\n    {\n        \"id\"          : \"fs\",\n        \"name\"        : \"FS\",\n        \"description\" : \"Synchronizes everything to the file system\",\n        \"hasSettings\" : true,\n        \"minVersion\"  : \"\",\n        \"maxVersion\"  : \"\",\n        \"platforms\"   : [\n            \"electron\"\n        ]\n    },\n    {\n        \"id\"          : \"electronSearch\",\n        \"name\"        : \"Electron Search\",\n        \"description\" : \"\",\n        \"hasSettings\" : false,\n        \"minVersion\"  : \"\",\n        \"maxVersion\"  : \"\",\n        \"platforms\"   : [\n            \"electron\"\n        ]\n    }\n]\n"
  },
  {
    "path": "app/scripts/modules/remotestorage/classes/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, RemoteStorage */\ndefine([\n    'underscore',\n    'modules/remotestorage/classes/rs'\n], function(_, RS) {\n    'use strict';\n\n    /**\n     * RemoteStorage module.\n     */\n    RemoteStorage.defineModule('laverna', function(prClient) {\n        prClient.declareType('notes', {\n            '$schema':     'http://json-schema.org/draft-03/schema#',\n            'description': 'Notes',\n            'type' : 'object',\n            'properties': {\n                'id': {\n                    'type'     : 'string',\n                    'required' : true\n                },\n                'title': {\n                    'type': 'string'\n                },\n                'content': {\n                    'type': 'string'\n                },\n                'taskAll':  {\n                    'type': 'number'\n                },\n                'taskCompleted':  {\n                    'type': 'number'\n                },\n                'created':  {\n                    'type': 'number'\n                },\n                'updated':  {\n                    'type': 'number'\n                },\n                'notebookId':  {\n                    'type': 'string'\n                },\n                'tags':  {\n                    'type': 'array'\n                },\n                'isFavorite':  {\n                    'type': 'number'\n                },\n                'trash':  {\n                    'type': 'number'\n                },\n                /*\n                'files': {\n                    'type': 'array'\n                }\n                */\n            }\n        });\n\n        prClient.declareType('notebooks', {\n            '$schema':     'http://json-schema.org/draft-03/schema#',\n            'description': 'Notebooks',\n            'type' : 'object',\n            'properties': {\n                'id': {\n                    'type'     : 'string',\n                    'format'   : 'id',\n                    'required' : true\n                },\n                'parentId': {\n                    'type': 'string'\n                },\n                'name': {\n                    'type': 'string'\n                },\n                'updated':  {\n                    'type': 'number'\n                }\n            }\n        });\n\n        prClient.declareType('tags', {\n            '$schema':     'http://json-schema.org/draft-03/schema#',\n            'description': 'Tags',\n            'type' : 'object',\n            'properties': {\n                'id': {\n                    'type'     : 'string',\n                    'format'   : 'id',\n                    'required' : true\n                },\n                'name': {\n                    'type': 'string'\n                },\n                'updated':  {\n                    'type': 'number'\n                },\n            }\n        });\n\n        return {\n            exports: {\n                on      : prClient.on,\n                profile : null,\n\n                init: function(profile) {\n                    // RS.caching.set('/', 'FLUSH');\n                    prClient.cache('');\n                    this.profile = profile;\n                },\n\n                /**\n                 * @type string type [notes|notebooks|tags]\n                 * @type object obj model.attributes\n                 * @type array  encryptKeys\n                 */\n                save: function(type, obj, encryptKeys) {\n                    obj = _.clone(obj);\n                    obj.id = obj.id.toString();\n\n                    if (typeof obj.notebookId !== 'undefined') {\n                        obj.notebookId = obj.notebookId.toString();\n                    }\n                    if (typeof obj.parentId !== 'undefined') {\n                        obj.parentId = obj.parentId.toString();\n                    }\n\n                    // Send only encrypted data\n                    if (obj.encryptedData) {\n                        obj = _.omit(obj, encryptKeys);\n                    }\n\n                    return prClient.storeObject(type, this.profile + '/' + type + '/' + obj.id, obj);\n                },\n\n                destroy: function(type, obj) {\n                    obj.id = obj.id.toString();\n                    return prClient.remove(this.profile + '/' + type + '/' + obj.id);\n                },\n\n                getAll: function(type) {\n                    return prClient.getAll(this.profile + '/' + type + '/');\n                },\n\n                getById: function(type, obj) {\n                    obj.id = obj.id.toString();\n                    return prClient.getObject(this.profile + '/' + type + '/' + obj.id);\n                }\n            }\n        };\n    });\n\n    return RS.laverna;\n});\n"
  },
  {
    "path": "app/scripts/modules/remotestorage/classes/rs.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define, RemoteStorage */\ndefine([\n    'tv4',\n    'backbone.radio',\n    'remotestorage'\n], function(tv4, Radio) {\n    'use strict';\n\n    // Make TV4 globally available because RemoteStorage needs it.\n    window.tv4 = tv4;\n    var RS = new RemoteStorage({\n        logging            : false,\n        cordovaRedirectUri : 'https://laverna.cc',\n        changeEvents : {\n            local    : false,\n            window   : false,\n            remote   : true,\n            conflict : true\n        }\n    });\n\n\n    /**\n     * Sometimes hash is not saved automatically after starting Backbone router.\n     */\n    var md = Radio.request('global', 'hash:original');\n    md = md.match(/access_token=([^&]+)/);\n    if (md && !RS.remote.token) {\n        RS.remote.configure({\n            token: md[1]\n        });\n    }\n\n    // Make remoteStorage globally available\n    window.remoteStorage = RS;\n    return window.remoteStorage;\n});\n"
  },
  {
    "path": "app/scripts/modules/remotestorage/classes/sync.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'backbone.radio',\n    'q',\n    'modules/remotestorage/classes/rs',\n    'modules/remotestorage/classes/module'\n], function(_, Marionette, Radio, Q, RS, RsModule) {\n    'use strict';\n\n    /**\n     * RemoteStorage sync controller.\n     *\n     * Listens to events:\n     * 1. event: `save:after:encrypted`, channel: [notes|notebooks|tags]\n     *    syncs changes to RemoteStorage.\n     * 2. `RemoteStorage`, event: `ready`\n     *    syncs local and remote changes.\n     * 3. `RemoteStorage`, event: `change`\n     *    syncs remote data to local storage.\n     *\n     * Triggers:\n     * 1. channel: [notes|notebooks|tags], request: 'save:raw'\n     *    passes new changes to a model.\n     * 2. channel: [notes|notebooks|tags], request: 'fetch'\n     *    expects to get a collection of notes,notebooks, or tags.\n     * 3. channel: [notes|notebooks|tags], request: 'save:all:raw'\n     *    passes new models.\n     */\n    var Sync = Marionette.Object.extend({\n\n        initialize: function() {\n            _.bindAll(this, 'onReady', 'syncAll', 'onRsChange');\n\n            RS.access.claim('laverna', 'rw');\n            RS.displayWidget();\n\n            // Listen to RemoteStorage events\n            RS.on('ready', this.onReady);\n\n            // Listen to Laverna events\n            this.listenTo(Radio.channel('notes'), 'sync:model destroy:model restore:model', this.onSave);\n            this.listenTo(Radio.channel('notebooks'), 'sync:model destroy:model restore:model', this.onSave);\n            this.listenTo(Radio.channel('tags'), 'sync:model destroy:model restore:model', this.onSave);\n        },\n\n        isConnected: function() {\n            return (RS.remote.connected && RS.remote.online);\n        },\n\n        /**\n         * Got changes from RemoteStorage.\n         */\n        onRsChange: function(change) {\n\n            // Don't do anything if changes are originated from local storage\n            if (change.origin === 'local') {\n                return;\n            }\n\n            var path    = change.relativePath.split('/'),\n                channel = (change.newValue || change.oldValue).type || path[1];\n\n            if (change.newValue) {\n                change.newValue['@context'] = null;\n            }\n\n            Radio.request(channel, 'save:raw', change.newValue, {profile: path[0]});\n        },\n\n        /**\n         * RemoteStorage is ready.\n         */\n        onReady: function() {\n            var promises = [],\n                self     = this,\n                profile  = (Radio.request('uri', 'profile') || 'notes-db');\n\n            RsModule.init(profile);\n\n            // Synchronize all collections\n            _.each(['notes', 'notebooks', 'tags'], function(module) {\n                promises.push(function() {\n                    return Q.all([\n                        Radio.request(module, 'fetch', {encrypt: true, profile: profile}),\n                        new Q(RsModule.getAll(module))\n                    ])\n                    .spread(function(localData, remoteData) {\n                        return self.syncAll(localData, remoteData, module);\n                    });\n                });\n            });\n\n            return _.reduce(promises, Q.when, new Q())\n            .then(function() {\n                return RS.laverna.on('change', self.onRsChange);\n            })\n            .fail(function() {\n                console.error('Error', arguments);\n            });\n        },\n\n        /**\n         * Synchronize local and remote data.\n         */\n        syncAll: function(localData, remoteData, module) {\n            var promises = [],\n                newData;\n\n            localData    = localData.fullCollection || localData;\n\n            // Find notes which don't exist in local storage or were updated\n            // remotely.\n            newData = _.filter(remoteData, function(rModel) {\n                var lmodel = _.findWhere(localData.models, {id: rModel.id});\n                return !lmodel || lmodel.attributes.updated < rModel.updated;\n            });\n\n            if (newData.length) {\n                promises.push(\n                    Radio.request(module, 'save:all:raw', newData, {profile: RsModule.profile})\n                );\n            }\n\n            // Find notes which don't exist in RemoteStorage or have been\n            // updated locally.\n            _.each(localData.models, function(lModel) {\n                var rmodel = _.findWhere(remoteData, {id: lModel.id});\n                if (rmodel && rmodel.updated >= lModel.attributes.updated) {\n                    return;\n                }\n\n                promises.push(\n                    RsModule.save(module, lModel.attributes, lModel.encryptKeys)\n                );\n            });\n\n            return Q.all(promises)\n            .fail(function() {\n                console.error('Error', arguments);\n            });\n        },\n\n        /**\n         * Local model was updated.\n         */\n        onSave: function(model) {\n            return RsModule.save(model.storeName, model.attributes, model.encryptKeys);\n        },\n\n    });\n\n    return Sync;\n});\n"
  },
  {
    "path": "app/scripts/modules/remotestorage/module.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'marionette',\n    'modules',\n    'modules/remotestorage/classes/sync'\n], function(_, Radio, Marionette, Modules, Sync) {\n    'use strict';\n\n    var RemoteStorage = Modules.module('RemoteStorage', {});\n\n    /**\n     * Initializers & finalizers of the module\n     */\n    RemoteStorage.on('start', function() {\n        console.info('RemoteStorage started');\n        new Sync();\n    });\n\n    RemoteStorage.on('stop', function() {\n    });\n\n    // Add a global module initializer\n    Radio.request('init', 'add', 'module', function() {\n        RemoteStorage.start();\n    });\n\n    return RemoteStorage;\n});\n"
  },
  {
    "path": "app/scripts/modules.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette'\n], function(_, Marionette) {\n    'use strict';\n\n    /**\n     * This class is used to create modules without requiring app.js\n     */\n    var Modules = Marionette.Object.extend({\n        initialize: function() {\n            this.submodules = {};\n        },\n\n        module: function(moduleName, moduleDefinition) {\n            // Overwrite the module class if the user specifies one\n            var ModuleClass = Marionette.Module.getClass(moduleDefinition),\n                args = _.toArray(arguments);\n\n            args.unshift(this);\n\n            // see the Marionette.Module object for more information\n            return ModuleClass.create.apply(ModuleClass, args);\n        },\n\n        addInitializer: function() {}\n    });\n\n    return new Modules();\n});\n"
  },
  {
    "path": "app/scripts/regions/regionManager.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'marionette',\n    'backbone.radio',\n    'modalRegion',\n    'brandRegion'\n], function(Marionette, Radio, ModalRegion, BrandRegion) {\n    'use strict';\n\n    /**\n     * Main region manager\n     */\n    var rm    = new Marionette.RegionManager(),\n        $body = $('body');\n\n    rm.addRegions({\n        sidebarNavbar : '#sidebar--navbar',\n        sidebar       : '#sidebar--content',\n        content       : '#content',\n        brand         : BrandRegion,\n        modal         : ModalRegion\n    });\n\n    Radio.channel('global')\n    .reply('region:show', function(region, view) {\n        // There should be only one modal window at a time\n        if (region === 'modal' && rm.get(region).currentView) {\n            rm.get(region).currentView.trigger('hidden.modal');\n        }\n\n        rm.get(region).show(view);\n        Radio.trigger('region', region + ':shown');\n    })\n    .reply('region:add', function(name, block) {\n\n        // The region already exists\n        if (rm.get(name)) {\n            return;\n        }\n\n        $body.append(block || '<div id=\"' + name + '\"></div>');\n        rm.addRegion(name, '#' + name);\n    })\n    .reply('region:empty', function(region) {\n        Radio.trigger('region', region + ':hidden');\n        rm.get(region).empty();\n    })\n    .reply('region:visible', function(region, hideClass) {\n        region = rm.get(region);\n        region.$el.removeClass(hideClass || 'hidden');\n    })\n    .reply('region:hide', function(region, hideClass) {\n        region = rm.get(region);\n        region.$el.addClass(hideClass || 'hidden');\n    });\n\n    return rm;\n});\n\n"
  },
  {
    "path": "app/scripts/templates/loader.html",
    "content": "<p class=\"text-center loader\">\n    <i class=\"icon icon-arrows animate-spin\"></i>\n</p>\n"
  },
  {
    "path": "app/scripts/views/brand.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'backbone',\n    'marionette'\n], function (_, $, Backbone) {\n    'use strict';\n\n    var BrandRegion = Backbone.Marionette.Region.extend({\n        el : '#layout--brand',\n\n        onShow: function () {\n            // this.$el.html(view.el);\n            this.$el.slideDown('fast');\n        },\n\n        onEmpty: function () {\n            this.$el.slideUp('fast');\n        }\n\n    });\n\n    return BrandRegion;\n});\n"
  },
  {
    "path": "app/scripts/views/loader.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global define */\ndefine([\n    'underscore',\n    'marionette',\n    'text!templates/loader.html'\n], function(_, Marionette, Tmpl) {\n    'use strict';\n\n    /**\n     * Just a view which shows spinning icon.\n     */\n    var Loader = Marionette.ItemView.extend({\n        template  : _.template(Tmpl)\n    });\n\n    return Loader;\n});\n"
  },
  {
    "path": "app/scripts/views/modal.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n * \n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/*global define*/\ndefine([\n    'underscore',\n    'jquery',\n    'marionette',\n    'mousetrap',\n    'bootstrap'\n], function(_, $, Marionette, Mousetrap) {\n    'use strict';\n\n    /**\n     * Modal region\n     * @source: http://lostechies.com/derickbailey/2012/04/17/managing-a-modal-dialog-with-backbone-and-marionette/\n     */\n    var ModalRegion = Marionette.Region.extend({\n        el: '#modal',\n\n        initialize: function() {\n            this.$window = $(window);\n        },\n\n        onShow: function(view) {\n            view.$el.modal({\n                show     : true,\n                backdrop : 'static',\n                keyboard : false\n            });\n\n            // Trigger shown event\n            view.$el.on('shown.bs.modal', function() {\n                view.trigger('shown.modal');\n            });\n\n            // Trigger hidden event\n            view.$el.on('hidden.bs.modal', function() {\n                view.trigger('hidden.modal');\n            });\n\n            // Hide on close event\n            view.on('close', function() {\n                view.$el.modal('hide');\n            });\n\n            // Close on ESC\n            Mousetrap.bind('esc', function() {\n                view.$el.modal('hide');\n            });\n\n            // If url is changed we should close modal window\n            if (view.stayOnHashchange !== true) {\n                this.$window.on('hashchange.modal', function() {\n                    view.$el.modal('hide');\n                });\n            }\n        },\n\n        onBeforeEmpty: function(view) {\n            view.$el.modal('hide');\n            this.onBeforeSwap();\n        },\n\n        /**\n         * Because sometimes backdrop is duplicated\n         */\n        onBeforeSwap: function() {\n            var backdrop = $('.modal-backdrop');\n            if (backdrop.length) {\n                backdrop.remove();\n            }\n            this.$window.off('hashchange.modal');\n        }\n\n    });\n\n    return ModalRegion;\n});\n"
  },
  {
    "path": "app/scripts/workers/localForage.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global requirejs, importScripts, self */\n'use strict';\nimportScripts('../../bower_components/requirejs/require.js');\n\nrequirejs.config({\n    baseUrl: '../',\n    paths: {\n        q           : '../bower_components/q/q',\n        underscore  : '../bower_components/underscore/underscore',\n        localforage : '../bower_components/localforage/dist/localforage',\n    }\n});\n\nrequirejs([\n    'helpers/db'\n], function(db) {\n\n    // Listen to the webworker messages\n    self.onmessage = function(data) {\n        var msg = data.data;\n\n        if (db[msg.msg]) {\n            return db[msg.msg](msg.data)\n            .then(function(result) {\n                self.postMessage({\n                    msg       : 'done',\n                    promiseId : msg.promiseId,\n                    data      : result\n                });\n            })\n            .fail(function(e) {\n                self.postMessage({\n                    msg       : 'fail',\n                    promiseId : msg.promiseId,\n                    data      : e\n                });\n            });\n        }\n\n        console.error('localForage module:', 'Method doesn\\'t exist', msg.msg);\n    };\n\n    // Post a message that the worker is ready\n    self.postMessage({msg: 'ready'});\n});\n"
  },
  {
    "path": "app/scripts/workers/sjcl.js",
    "content": "/**\n * Copyright (C) 2015 Laverna project Authors.\n *\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at http://mozilla.org/MPL/2.0/.\n */\n/* global requirejs, importScripts, self */\n'use strict';\nimportScripts('../../bower_components/requirejs/require.js');\n\nrequirejs.config({\n    baseUrl: '../',\n\n    paths: {\n        q          : '../bower_components/q/q',\n        underscore : '../bower_components/underscore/underscore',\n        sjcl       : '../bower_components/sjcl/sjcl'\n    },\n\n    shim: {\n        sjcl: {\n            exports: 'sjcl'\n        }\n    }\n});\n\nrequirejs([\n    'q',\n    'classes/sjcl'\n], function(Q, Sjcl) {\n    var sjcl = new Sjcl();\n\n    // Listen to the Webworker messages\n    self.onmessage = function(data) {\n        var msg = data.data;\n\n        if (sjcl[msg.msg]) {\n            return new Q(\n                sjcl[msg.msg](msg.data)\n            )\n            .then(function(res) {\n                self.postMessage({\n                    msg       : 'done',\n                    promiseId : msg.promiseId,\n                    data      : res\n                });\n            });\n        }\n\n        console.error('sjcl worker:', 'Method doesn\\'t exist', msg.msg);\n    };\n\n    // Post a message that the worker is ready to listen to events\n    self.postMessage({msg: 'ready'});\n});\n"
  },
  {
    "path": "app/styles/core/bootstrap.less",
    "content": "/*!\n * Bootstrap v3.3.5 (http://getbootstrap.com)\n * Copyright 2011-2015 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n// Core variables and mixins\n@import \"bootstrap/less/variables.less\";\n@import \"bootstrap/less/mixins.less\";\n\n// Reset and dependencies\n@import \"bootstrap/less/normalize.less\";\n@import \"bootstrap/less/print.less\";\n@import \"bootstrap/less/glyphicons.less\";\n\n// Core CSS\n@import \"bootstrap/less/scaffolding.less\";\n@import \"bootstrap/less/type.less\";\n// @import \"bootstrap/less/code.less\";\n@import \"bootstrap/less/grid.less\";\n@import \"bootstrap/less/tables.less\";\n@import \"bootstrap/less/forms.less\";\n@import \"bootstrap/less/buttons.less\";\n\n// Components\n@import \"bootstrap/less/component-animations.less\";\n@import \"bootstrap/less/dropdowns.less\";\n@import \"bootstrap/less/button-groups.less\";\n@import \"bootstrap/less/input-groups.less\";\n@import \"bootstrap/less/navs.less\";\n@import \"bootstrap/less/navbar.less\";\n@import \"bootstrap/less/breadcrumbs.less\";\n@import \"bootstrap/less/pagination.less\";\n@import \"bootstrap/less/pager.less\";\n@import \"bootstrap/less/labels.less\";\n@import \"bootstrap/less/badges.less\";\n@import \"bootstrap/less/jumbotron.less\";\n@import \"bootstrap/less/thumbnails.less\";\n@import \"bootstrap/less/alerts.less\";\n@import \"bootstrap/less/progress-bars.less\";\n@import \"bootstrap/less/media.less\";\n@import \"bootstrap/less/list-group.less\";\n@import \"bootstrap/less/panels.less\";\n@import \"bootstrap/less/responsive-embed.less\";\n@import \"bootstrap/less/wells.less\";\n@import \"bootstrap/less/close.less\";\n\n// Components w/ JavaScript\n@import \"bootstrap/less/modals.less\";\n@import \"bootstrap/less/tooltip.less\";\n@import \"bootstrap/less/popovers.less\";\n@import \"bootstrap/less/carousel.less\";\n\n// Utility classes\n@import \"bootstrap/less/utilities.less\";\n@import \"bootstrap/less/responsive-utilities.less\";\n"
  },
  {
    "path": "app/styles/core/codemirror/core.less",
    "content": "/**\n * Core Codemirror styles.\n */\n.CodeMirror {\n    /* Set height, width, borders, and global font properties here */\n    font-family    : @font-family-monospace;\n    height         : auto;\n    viewportMargin : Infinity;\n    position       : relative;\n    overflow       : hidden;\n}\n\n.editor--fullscreen.-preview {\n    .CodeMirror {\n        height      : 100% !important;\n        position    : absolute !important;\n        top         : 0 !important;\n        bottom      : 0 !important;\n        left        : 0 !important;\n        right       : 0 !important;\n    }\n    .CodeMirror-scroll {\n        overflow: scroll !important;\n    }\n}\n\n/**\n * Scrollbar\n */\n.CodeMirror-scroll {\n    overflow: scroll !important; /* Things will break if this is overridden */\n    /* 30px is the magic margin used to hide the element's real scrollbars */\n    /* See overflow: hidden in .CodeMirror */\n\n    margin-bottom  : -30px;\n    margin-right   : -30px;\n    padding-bottom : 30px;\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 : 30px solid transparent;\n}\n\n/* The fake, visible scrollbars. Used to force redraw during scrolling\nbefore actual scrolling happens, thus preventing shaking and\nflickering artifacts. */\n.CodeMirror-vscrollbar,\n.CodeMirror-hscrollbar,\n.CodeMirror-scrollbar-filler,\n.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/**\n * Paddings\n */\n.CodeMirror-lines {\n    padding    : 15px 0 100px; // Vertical padding around content\n    cursor     : text;\n    min-height : 1px;       // prevents collapsing before first draw\n}\n.CodeMirror pre {\n    padding: 0 4px; // Horizontal padding of content\n\n    /* Reset some styles that the rest of the page might have set */\n    -moz-border-radius    : 0;\n    -webkit-border-radius : 0;\n    border-radius         : 0;\n\n    border-width  : 0;\n    background    : transparent;\n    font-family   : inherit;\n    font-size     : @font-size-base * 1;\n    line-height   : floor((@font-size-base * 1 * @line-height-base));\n    margin        : 0;\n    white-space   : pre;\n    word-wrap     : normal;\n    color         : inherit;\n    z-index       : 2;\n    position      : relative;\n    overflow      : visible;\n    -webkit-tap-highlight-color: transparent;\n}\n.CodeMirror-wrap pre {\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;\n    right    : 0;\n    top      : 0;\n    bottom   : 0;\n    z-index  : 0;\n}\n\n.CodeMirror-linewidget {\n    position : relative;\n    z-index  : 2;\n    overflow : auto;\n}\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// The little square between H and V scrollbars\n.CodeMirror-scrollbar-filler,\n.CodeMirror-gutter-filler {\n    background-color: white;\n}\n\n.CodeMirror-measure pre {\n    position: static;\n}\n\n.CodeMirror-selected {\n    background: #d9d9d9;\n}\n.CodeMirror-focused .CodeMirror-selected {\n    background: #d7d4f0;\n}\n.CodeMirror-crosshair {\n    cursor: crosshair;\n}\n.CodeMirror-line::selection,\n.CodeMirror-line > span::selection,\n.CodeMirror-line > span > span::selection {\n    background: #d7d4f0;\n}\n.CodeMirror-line::-moz-selection,\n.CodeMirror-line > span::-moz-selection,\n.CodeMirror-line > span > span::-moz-selection {\n    background: #d7d4f0;\n}\n.cm-searching {\n    background : #ffa;\n    background : rgba(255, 255, 0, .4);\n}\n\n// IE7 hack to prevent it from returning funny offsetTops on the spans\n.CodeMirror span { *vertical-align: text-bottom; }\n\n// Used to force a border model for a node\n.cm-force-border { padding-right: .1px; }\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 * Gutter\n */\n.CodeMirror-gutters {\n    border-right     : 1px solid #ddd;\n    background-color : #f7f7f7;\n    white-space      : nowrap;\n    position         : absolute;\n    left             : 0;\n    top              : 0;\n    z-index          : 3;\n}\n.CodeMirror-gutter {\n    white-space   : normal;\n    height        : 100%;\n    display       : inline-block;\n    margin-bottom : -30px;\n    /* Hack to make IE7 behave */\n    *zoom:1;\n    *display:inline;\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;\n    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 {\n    -webkit-user-select : none;\n    -moz-user-select    : none;\n    user-select         : none;\n}\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 {\n    color: black;\n}\n.CodeMirror-guttermarker-subtle {\n    color: #999;\n}\n\n/**\n * Cursors\n */\n.CodeMirror-cursor {\n    position     : absolute;\n    border-left  : 1px solid black;\n    border-right : none;\n    width        : 0;\n}\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;\n    background: #7e7;\n}\n.cm-fat-cursor div.CodeMirror-cursors {\n    z-index: 1;\n}\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\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@-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 {\n    display         : inline-block;\n    text-decoration : inherit;\n}\n\n.CodeMirror-ruler {\n    border-left : 1px solid #ccc;\n    position    : absolute;\n}\n\n@media print {\n    // Hide the cursor when printing\n    .CodeMirror div.CodeMirror-cursors {\n        visibility: hidden;\n    }\n}\n\n.editor--fullscreen.-preview {\n    .CodeMirror-lines {\n        padding-left: 10px;\n        padding-right: 10px;\n    }\n}\n"
  },
  {
    "path": "app/styles/core/codemirror/theme.less",
    "content": "/**\n * Default theme for Codemirror.\n */\n\n/**\n * Headers\n */\n.cm-s-default .cm-header {\n    color: #000;\n}\n.cm-s-default .cm-header-1 {\n    &:extend(h1);\n    line-height: floor((@font-size-h1 * @line-height-base));\n}\n.cm-s-default .cm-header-2 {\n    &:extend(h2);\n    line-height: floor((@font-size-h2 * @line-height-base));\n}\n.cm-s-default .cm-header-3 {\n    &:extend(h3);\n    line-height: floor((@font-size-h3 * @line-height-base));\n}\n.cm-s-default .cm-header-4 {\n    &:extend(h4);\n    line-height: floor((@font-size-h4 * @line-height-base));\n}\n.cm-s-default .cm-header-5 {\n    &:extend(h5);\n    line-height: floor((@font-size-h5 * @line-height-base));\n}\n.cm-s-default .cm-header-6 {\n    &:extend(h6);\n    line-height: floor((@font-size-h6 * @line-height-base));\n}\n\n/**\n * Other styles\n */\n.CodeMirror-composing {\n    border-bottom: 2px solid;\n}\n\n.cm-header,\n.cm-strong:extend(strong) {}\n\n.cm-em {\n    font-style: italic;\n}\n\n.cm-strikethrough {\n    text-decoration: line-through;\n}\n\n.cm-link {\n    text-decoration: underline;\n}\n\n/**\n * Default styles for common addons\n */\ndiv.CodeMirror span.CodeMirror-matchingbracket {\n    color: #0f0;\n}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {\n    color: #f22;\n}\n.CodeMirror-matchingtag {\n    background: rgba(255, 150, 0, .3);\n}\n.CodeMirror-activeline-background {\n    background: #e8f2ff;\n}\n"
  },
  {
    "path": "app/styles/core/codemirror.less",
    "content": "@import 'codemirror/core.less';\n@import 'codemirror/theme.less';\n"
  },
  {
    "path": "app/styles/core/editor.less",
    "content": "/**\n * Editor module\n */\n\n/**\n * Mixin for creating columns\n */\n@pagedown-breakpoint: 500px;\n.make-pg-columns(@columns) {\n    .make-lg-column(@columns);\n    .make-md-column(@columns);\n    .make-sm-column(@columns);\n    .make-xs-column(@columns);\n\n    // Stretch columns on small screens\n    @media (max-width: @pagedown-breakpoint) {\n        width: percentage((12 / 12));\n    }\n}\n\n.editor--form {\n    margin-top: 15px;\n}\n\n/**\n * WYSIWYG bar\n */\n.editor--bar {\n    &:extend(.navbar);\n    margin-bottom : 0;\n    border-color  : rgb(217, 217, 217);\n    border-top    : 1px solid rgb(229, 229, 229);\n    border-bottom : 1px solid rgb(229, 229, 229);\n    padding       : 6px 0;\n\n    &.-fixed {\n        &:extend(.navbar-static-top);\n        position    : fixed;\n        z-index     : @zindex-navbar - 1;\n        top         : @navbar-height;\n        margin-left : -15px;\n    }\n}\n\n.editor--input {\n    overflow    : hidden;\n    font-weight : 400;\n    font-size   : 14px;\n    line-height : 20px;\n    margin-left : 0;\n    border      : none;\n    transition  : border-color 0.15s ease-in-out 0s, box-shadow 0.15s ease-in-out 0s;\n    width       : 100%;\n}\n.editor--input,\n.editor--preview {\n    width: 100%;\n}\n.editor--preview {\n    display: none;\n}\n\n/**\n * Editor in fullscreen mode\n */\n.editor--fullscreen {\n    /**\n     * We don't need to show the sidebar on fullscreen mode.\n     */\n    .layout--sidebar {\n        display: none;\n    }\n    .layout--content {\n        width: 100%;\n    }\n\n    /**\n     * Show WYSIWG bar on top of everything\n     */\n    .editor--bar {\n        &:extend(.navbar-fixed-top);\n        top: @navbar-height;\n    }\n\n    /**\n     * Prevent WYSIWG bar from overflowing the main navbar\n     */\n    .layout--navbar {\n        z-index: @zindex-navbar-fixed + 1;\n    }\n\n    /**\n     * Since we have two navbars, we need to change the position\n     * of the form.\n     */\n    .layout--body.-form {\n        top: @navbar-height * 2;\n    }\n}\n\n/**\n * Pagedown in preview mode\n */\n.editor--fullscreen.-preview {\n    overflow: hidden;\n\n    // Main layout shouldn't have scrollbars\n    .layout--body.-form {\n        overflow: hidden;\n    }\n\n    .navbar-form {\n        padding: 0;\n    }\n\n    // This is the main wrapper now\n    .editor--row {\n        &:extend(.layout--body);\n    }\n\n    /**\n     * We use it to create a two column view:\n     * the editor and the preview.\n     */\n    .editor--layout {\n        .make-pg-columns(5.94);\n        position: absolute;\n        height: auto;\n        top: 0;\n        bottom: @navbar-height + 35px; // Leave room for footer\n        overflow: hidden;\n        background-color: #FFF;\n\n        &.-left {\n            left: 0;\n            padding-left: 0;\n            padding-right: 0;\n        }\n        &.-right {\n            right: 0;\n        }\n    }\n\n    /**\n     * Add scrollbars to Pagedown editor and its preview.\n     */\n    .editor--preview,\n    .editor--input {\n        background-color: #FFF;\n        width: 100%;\n        min-height: 1px;\n        position: absolute;\n        overflow: auto;\n        top: 0;\n        bottom: 0;\n        left: 0;\n    }\n\n    // Pagedown preview styles\n    .editor--preview {\n        display: block;\n        word-wrap: break-word;\n        padding: 15px 20px 0;\n    }\n    .editor--preview--block {\n        padding-bottom: 100px;\n    }\n\n    // Preview mode on small screens\n    @media (max-width: @pagedown-breakpoint) {\n        // Fix the scrollbar\n        .editor--layout {\n            bottom: 100px;\n        }\n\n        // Hide both columns\n        .editor--layout.-left,\n        .editor--layout.-right {\n            display: none;\n        }\n\n        .-show,\n        .editor--layout.-show,\n        .editor--show--column {\n            display: block !important;\n        }\n    }\n}\n\n// Notebook list\n.editor--notebooks {\n    position: relative;\n\n    &:before {\n        font-size   : 14px;\n        font-family : \"fontello\";\n        content     : '\\e808';\n        position    : absolute;\n        top         : 6px;\n        left        : 7px;\n    }\n    &:after {\n        &:extend(.caret);\n        content  : ' ';\n        position : absolute;\n        top      : 15px;\n        right    : 7px;\n    }\n}\n.editor--notebooks--list {\n    max-width    : 180px;\n    padding-left : 20px;\n\n    -webkit-appearance: none;\n       -moz-appearance: none;\n            appearance: none;\n}\n@media (max-width: 400px) {\n    .editor--notebooks {\n        &:after {\n            display: none;\n        }\n    }\n    .editor--notebooks--list {\n        max-width    : 52px;\n        padding      : 6px;\n        padding-left : 15px;\n    }\n}\n\n@media (min-width: @screen-xs) {\n    // Center the editor in fullscreen mode\n    .editor--fullscreen {\n        .editor--container {\n            width: 70%;\n            margin-left: auto;\n            margin-right: auto;\n        }\n    }\n    .editor--fullscreen.-preview {\n        .editor--container {\n            width: 100%;\n        }\n    }\n}\n\n#editor--footer{\n\tposition:absolute;\n\tbottom:0;\n\tmargin-left:auto;\n\tpadding-left: 15px;\n\toverflow:visible;\n    font-size   : 14px;\n    font-family : \"fontello\";\n\n    -webkit-touch-callout: none;\n    -webkit-user-select: none;\n  \t-khtml-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n"
  },
  {
    "path": "app/styles/core/fontello/LICENSE.txt",
    "content": "Font license info\n\n\n## Font Awesome\n\n   Copyright (C) 2012 by Dave Gandy\n\n   Author:    Dave Gandy\n   License:   SIL ()\n   Homepage:  http://fortawesome.github.com/Font-Awesome/\n\n\n## Entypo\n\n   Copyright (C) 2012 by Daniel Bruce\n\n   Author:    Daniel Bruce\n   License:   SIL (http://scripts.sil.org/OFL)\n   Homepage:  http://www.entypo.com\n\n\n## Elusive\n\n   Copyright (C) 2013 by Aristeides Stathopoulos\n\n   Author:    Aristeides Stathopoulos\n   License:   SIL (http://scripts.sil.org/OFL)\n   Homepage:  http://aristeides.com/\n\n\n## Modern Pictograms\n\n   Copyright (c) 2012 by John Caserta. All rights reserved.\n\n   Author:    John Caserta\n   License:   SIL (http://scripts.sil.org/OFL)\n   Homepage:  http://thedesignoffice.org/project/modern-pictograms/\n\n\n"
  },
  {
    "path": "app/styles/core/fontello/README.txt",
    "content": "This webfont is generated by http://fontello.com open source project.\n\n\n================================================================================\nPlease, note, that you should obey original font licences, used to make this\nwebfont pack. Details available in LICENSE.txt file.\n\n- Usually, it's enough to publish content of LICENSE.txt file somewhere on your\n  site in \"About\" section.\n\n- If your project is open-source, usually, it will be ok to make LICENSE.txt\n  file publically available in your repository.\n\n- Fonts, used in Fontello, don't require a clickable link on your site.\n  But any kind of additional authors crediting is welcome.\n================================================================================\n\n\nComments on archive content\n---------------------------\n\n- /font/* - fonts in different formats\n\n- /css/*  - different kinds of css, for all situations. Should be ok with \n  twitter bootstrap. Also, you can skip <i> style and assign icon classes\n  directly to text elements, if you don't mind about IE7.\n\n- demo.html - demo file, to show your webfont content\n\n- LICENSE.txt - license info about source fonts, used to build your one.\n\n- config.json - keeps your settings. You can import it back into fontello\n  anytime, to continue your work\n\n\nWhy so many CSS files ?\n-----------------------\n\nBecause we like to fit all your needs :)\n\n- basic file, <your_font_name>.css - is usually enough, it contains @font-face\n  and character code definitions\n\n- *-ie7.css - if you need IE7 support, but still don't wish to put char codes\n  directly into html\n\n- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face\n  rules, but still wish to benefit from css generation. That can be very\n  convenient for automated asset build systems. When you need to update font -\n  no need to manually edit files, just override old version with archive\n  content. See fontello source code for examples.\n\n- *-embedded.css - basic css file, but with embedded WOFF font, to avoid\n  CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.\n  We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`\n  server headers. But if you ok with dirty hack - this file is for you. Note,\n  that data url moved to separate @font-face to avoid problems with <IE9, when\n  string is too long.\n\n- animate.css - use it to get ideas about spinner rotation animation.\n\n\nAttention for server setup\n--------------------------\n\nYou MUST setup server to reply with proper `mime-types` for font files -\notherwise some browsers will fail to show fonts.\n\nUsually, `apache` already has necessary settings, but `nginx` and other\nwebservers should be tuned. Here is list of mime types for our file extensions:\n\n- `application/vnd.ms-fontobject` - eot\n- `application/x-font-woff` - woff\n- `application/x-font-ttf` - ttf\n- `image/svg+xml` - svg\n"
  },
  {
    "path": "app/styles/core/fontello/config.json",
    "content": "{\n  \"name\": \"\",\n  \"css_prefix_text\": \"icon-\",\n  \"css_use_suffix\": false,\n  \"hinting\": true,\n  \"units_per_em\": 1000,\n  \"ascent\": 850,\n  \"glyphs\": [\n    {\n      \"uid\": \"9dd9e835aebe1060ba7190ad2b2ed951\",\n      \"css\": \"search\",\n      \"code\": 59392,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"381da2c2f7fd51f8de877c044d7f439d\",\n      \"css\": \"picture\",\n      \"code\": 59406,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"12f4ece88e46abd864e40b35e05b11cd\",\n      \"css\": \"ok\",\n      \"code\": 59427,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"1400d5103edd2fa6d2d61688fee79a5a\",\n      \"css\": \"ok-squared\",\n      \"code\": 59428,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"861ab06e455e2de3232ebef67d60d708\",\n      \"css\": \"minus\",\n      \"code\": 59410,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"17ebadd1e3f274ff0205601eef7b9cc4\",\n      \"css\": \"help-circled\",\n      \"code\": 59431,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"c1f1975c885aa9f3dad7810c53b82074\",\n      \"css\": \"lock\",\n      \"code\": 59421,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"657ab647f6248a6b57a5b893beaf35a9\",\n      \"css\": \"lock-open\",\n      \"code\": 59422,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"05376be04a27d5a46e855a233d6e8508\",\n      \"css\": \"lock-open-alt\",\n      \"code\": 59423,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"c5fd349cbd3d23e4ade333789c29c729\",\n      \"css\": \"eye\",\n      \"code\": 59415,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"3db5347bd219f3bce6025780f5d9ef45\",\n      \"css\": \"tag\",\n      \"code\": 59419,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"c6be5a58ee4e63a5ec399c2b0d15cf2c\",\n      \"css\": \"reply\",\n      \"code\": 59411,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"7034e4d22866af82bef811f52fb1ba46\",\n      \"css\": \"code\",\n      \"code\": 59405,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"178053298e3e5b03551d754d4b9acd8b\",\n      \"css\": \"note\",\n      \"code\": 59420,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"559647a6f430b3aeadbecd67194451dd\",\n      \"css\": \"menu\",\n      \"code\": 59435,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"98d9c83c1ee7c2c25af784b518c522c5\",\n      \"css\": \"block\",\n      \"code\": 59424,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"b013f6403e5ab0326614e68d1850fd6b\",\n      \"css\": \"fullscreen\",\n      \"code\": 59414,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"d870630ff8f81e6de3958ecaeac532f2\",\n      \"css\": \"left-open\",\n      \"code\": 59436,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"399ef63b1e23ab1b761dfbb5591fa4da\",\n      \"css\": \"right-open\",\n      \"code\": 59437,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"f9c3205df26e7778abac86183aefdc99\",\n      \"css\": \"undo\",\n      \"code\": 59416,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"a73c5deb486c8d66249811642e5d719a\",\n      \"css\": \"arrows\",\n      \"code\": 59393,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"197375a3cea8cb90b02d06e4ddf1433d\",\n      \"css\": \"globe\",\n      \"code\": 59403,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"2c413e78faf1d6631fd7b094d14c2253\",\n      \"css\": \"cloud\",\n      \"code\": 59399,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"f9cbf7508cd04145ade2800169959eef\",\n      \"css\": \"font\",\n      \"code\": 59409,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"02cca871bb69da75e8ee286b7055832c\",\n      \"css\": \"bold\",\n      \"code\": 59401,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"a8cb1c217f02b073db3670c061cc54d2\",\n      \"css\": \"italic\",\n      \"code\": 59402,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"25a81737628d1e654a30ad412d7d6dd7\",\n      \"css\": \"align-justify\",\n      \"code\": 59396,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"4d2dfc45d8176b1f26aed973fa84a91e\",\n      \"css\": \"indent\",\n      \"code\": 59404,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"a2a74f5e7b7d9ba054897d8c795a326a\",\n      \"css\": \"list-bullet\",\n      \"code\": 59408,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"f6766a8b042c2453a4e153af03294383\",\n      \"css\": \"list-numbered\",\n      \"code\": 59407,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"9755f76110ae4d12ac5f9466c9152031\",\n      \"css\": \"notebook\",\n      \"code\": 59400,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"dd6c6b221a1088ff8a9b9cd32d0b3dd5\",\n      \"css\": \"check\",\n      \"code\": 59429,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"4b900d04e8ab8c82f080c1cfbac5772c\",\n      \"css\": \"check-empty\",\n      \"code\": 59430,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"f4445feb55521283572ee88bc304f928\",\n      \"css\": \"save\",\n      \"code\": 59413,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"0f6a2573a7b6df911ed199bb63717e27\",\n      \"css\": \"github-circled\",\n      \"code\": 59425,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"25b34dea4784f72c710454f32ff8fa2b\",\n      \"css\": \"favorite\",\n      \"code\": 59395,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"0e3f1335f83cca67329b2c4bcb3aaec5\",\n      \"css\": \"star-empty\",\n      \"code\": 59394,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"2626e3a1bbcd90e45849af3b58a1d594\",\n      \"css\": \"trashed\",\n      \"code\": 59397,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"884cfc3e6e2d456dd2a2ca0dbb9e6337\",\n      \"css\": \"left-open-big\",\n      \"code\": 59433,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"004882ab2d5c418c5b2060e80596279b\",\n      \"css\": \"right-open-big\",\n      \"code\": 59434,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"8beac4a5fd5bed9f82ca7a96cc8ba218\",\n      \"css\": \"key\",\n      \"code\": 59426,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"a58a5236139282f6c7f906634233157a\",\n      \"css\": \"squares\",\n      \"code\": 59417,\n      \"src\": \"modernpics\"\n    },\n    {\n      \"uid\": \"d5937c5767642d0336ad599af0e9c992\",\n      \"css\": \"share\",\n      \"code\": 59412,\n      \"src\": \"elusive\"\n    },\n    {\n      \"uid\": \"62b0580ee8edc3a3edfbf68a47c852d5\",\n      \"css\": \"pencil\",\n      \"code\": 59418,\n      \"src\": \"elusive\"\n    },\n    {\n      \"uid\": \"9e0404ba55575a540164db9a5ad511df\",\n      \"css\": \"doc-new\",\n      \"code\": 59432,\n      \"src\": \"elusive\"\n    },\n    {\n      \"uid\": \"6083c8aa5e3345476a24a28ac1afaa61\",\n      \"css\": \"cog\",\n      \"code\": 59398,\n      \"src\": \"elusive\"\n    }\n  ]\n}"
  },
  {
    "path": "app/styles/core/fontello/css/animation.less",
    "content": "/*\n   Animation example, for spinners\n*/\n.animate-spin {\n  -moz-animation: spin 2s infinite linear;\n  -o-animation: spin 2s infinite linear;\n  -webkit-animation: spin 2s infinite linear;\n  animation: spin 2s infinite linear;\n  display: inline-block;\n}\n@-moz-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@-webkit-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@-o-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@-ms-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n"
  },
  {
    "path": "app/styles/core/fontello/css/fontello-codes.less",
    "content": "\n.icon-search:before { content: '\\e800'; } /* '' */\n.icon-arrows:before { content: '\\e801'; } /* '' */\n.icon-star-empty:before { content: '\\e802'; } /* '' */\n.icon-favorite:before { content: '\\e803'; } /* '' */\n.icon-align-justify:before { content: '\\e804'; } /* '' */\n.icon-trashed:before { content: '\\e805'; } /* '' */\n.icon-cog:before { content: '\\e806'; } /* '' */\n.icon-cloud:before { content: '\\e807'; } /* '' */\n.icon-notebook:before { content: '\\e808'; } /* '' */\n.icon-bold:before { content: '\\e809'; } /* '' */\n.icon-italic:before { content: '\\e80a'; } /* '' */\n.icon-globe:before { content: '\\e80b'; } /* '' */\n.icon-indent:before { content: '\\e80c'; } /* '' */\n.icon-code:before { content: '\\e80d'; } /* '' */\n.icon-picture:before { content: '\\e80e'; } /* '' */\n.icon-list-numbered:before { content: '\\e80f'; } /* '' */\n.icon-list-bullet:before { content: '\\e810'; } /* '' */\n.icon-font:before { content: '\\e811'; } /* '' */\n.icon-minus:before { content: '\\e812'; } /* '' */\n.icon-reply:before { content: '\\e813'; } /* '' */\n.icon-share:before { content: '\\e814'; } /* '' */\n.icon-save:before { content: '\\e815'; } /* '' */\n.icon-fullscreen:before { content: '\\e816'; } /* '' */\n.icon-eye:before { content: '\\e817'; } /* '' */\n.icon-undo:before { content: '\\e818'; } /* '' */\n.icon-squares:before { content: '\\e819'; } /* '' */\n.icon-pencil:before { content: '\\e81a'; } /* '' */\n.icon-tag:before { content: '\\e81b'; } /* '' */\n.icon-note:before { content: '\\e81c'; } /* '' */\n.icon-lock:before { content: '\\e81d'; } /* '' */\n.icon-lock-open:before { content: '\\e81e'; } /* '' */\n.icon-lock-open-alt:before { content: '\\e81f'; } /* '' */\n.icon-block:before { content: '\\e820'; } /* '' */\n.icon-github-circled:before { content: '\\e821'; } /* '' */\n.icon-key:before { content: '\\e822'; } /* '' */\n.icon-ok:before { content: '\\e823'; } /* '' */\n.icon-ok-squared:before { content: '\\e824'; } /* '' */\n.icon-check:before { content: '\\e825'; } /* '' */\n.icon-check-empty:before { content: '\\e826'; } /* '' */\n.icon-help-circled:before { content: '\\e827'; } /* '' */\n.icon-doc-new:before { content: '\\e828'; } /* '' */\n.icon-left-open-big:before { content: '\\e829'; } /* '' */\n.icon-right-open-big:before { content: '\\e82a'; } /* '' */\n.icon-menu:before { content: '\\e82b'; } /* '' */\n.icon-left-open:before { content: '\\e82c'; } /* '' */\n.icon-right-open:before { content: '\\e82d'; } /* '' */"
  },
  {
    "path": "app/styles/core/fontello/css/fontello-embedded.less",
    "content": "@font-face {\n  font-family: 'fontello';\n  src: url('../font/fontello.eot?90323576');\n  src: url('../font/fontello.eot?90323576#iefix') format('embedded-opentype'),\n       url('../font/fontello.svg?90323576#fontello') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n@font-face {\n  font-family: 'fontello';\n  src: url('data:application/octet-stream;base64,d09GRgABAAAAACLUAA4AAAAANmAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPihJJGNtYXAAAAGIAAAAOgAAAUrQPhm3Y3Z0IAAAAcQAAAAKAAAACgAAAABmcGdtAAAB0AAABZQAAAtwiJCQWWdhc3AAAAdkAAAACAAAAAgAAAAQZ2x5ZgAAB2wAABcEAAAhcAQekYZoZWFkAAAecAAAADUAAAA2BuIDA2hoZWEAAB6oAAAAIAAAACQH4AOwaG10eAAAHsgAAABSAAAAvJ9oAABsb2NhAAAfHAAAAGAAAABg27zl1m1heHAAAB98AAAAIAAAACAAyw16bmFtZQAAH5wAAAF3AAACzcydGx1wb3N0AAAhFAAAAVYAAAH3iobjVHByZXAAACJsAAAAZQAAAHvdawOFeJxjYGROYZzAwMrAwVTFtIeBgaEHQjM+YDBkZGJgYGJgZWbACgLSXFMYHF4wvNBlDvqfxRDFHMYwDSjMCJIDAOV9C8p4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF7o/v8PUvCCAURLMELVAwEjG8OIBwCV4AbbAAAAAAAAAAAAAAAAAAB4nK1WaXMTRxCd1WHLNj6CDxI2gVnGcox2VpjLCBDG7EoW4BzylexCjl1Ldu6LT/wG/ZpekVSRb/y0vB4d2GAnVVQoSv2m9+1M9+ueXpPQksReWI+k3HwpprY2aWTnSUg3bFqO4kPZ2QspU0z+LoiCaLXUvu04JCISgap1hSWC2PfI0iTjQ48yWrYlvWpSbulJd9kaD+qt+vbT0FGO3QklNZuhQ+uRLanCqBJFMu2RkjYtw9VfSVrh5yvMfNUMJYLoJJLGm2EMj+Rn44xWGa3GdhxFkU2WG0WKRDM8iCKPslpin1wxQUD5oBlSXvk0onyEH5EVe5TTCnHJdprf9yU/6R3OvyTieouyJQf+QHZkB3unK/ki0toK46adbEehivB0fSfEI5uT6p/sUV7TaOB2RaYnzQiWyleQWPkJZfYPyWrhfMqXPBrVkoOcCFovc2Jf8g60HkdMiWsmyILujk6IoO6XnKHYY/q4+OO9XSwXIQTIOJb1jkq4EEYpYbOaJG0EOYiSskWV1HpHTJzyOi3iLWG/Tu3oS2e0Sag7MZ6th46tnKjkeDSp00ymTu2k5tGUBlFKOhM85tcBlB/RJK+2sZrEyqNpbDNjJJFQoIVzaSqIZSeWNAXRPJrRm7thmmvXokWaPFDPPXpPb26Fmzs9p+3AP2v8Z3UqpoO9MJ2eDshKfJp2uUnRun56hn8m8UPWAiqRLTbDlMVDtn4H5eVjS47CawNs957zK+h99kTIpIH4G/AeL9UpBUyFmFVQC9201rUsy9RqVotUZOq7IU0rX9ZpAk05Dn1jX8Y4/q+ZGUtMCd/vxOnZEZeeufYlyDSH3GZdj+Z1arFdgM5sz+k0y/Z9nebYfqDTPNvzOh1ha+t0lO2HOi2w/UinY2wvaEGT7jsEchGBXMAGEoGwdRAI20sIhK1CIGwXEQjbIgJhu4RA2H6MQNguIxC2l7Wsmn4qaRw7E8sARYgDoznuyGVuKldTyaUSrotGpzbkKXKrpKJ4Vv0rA/3ikTesgbVAukTW/IpJrnxUleOPrmh508S5Ao5Vf3tzXJ8TD2W/WPhT8L/amqqkV6x5ZHIVeSPQk+NE1yYVj67p8rmqR9f/i4oOa4F+A6UQC0VZlg2+mZDwUafTUA1c5RAzGzMP1/W6Zc3P4fybGCEL6H78NxQaC9yDTllJWe1gr9XXj2W5twflsCdYkmK+zOtb4YuMzEr7RWYpez7yecAVMCqVYasNXK3gzXsS85DpTfJMELcVZYOkjceZILGBYx4wb76TICRMXbWB2imcsIG8YMwp2O+EQ1RvlOVwe6F9Ho2Uf2tX7MgZFU0Q+G32Rtjrs1DyW6yBhCe/1NdAVSFNxbipgEsj5YZq8GFcrdtGMk6gr6jYDcuyig8fR9x3So5lIPlIEatHRz+tvUKd1Ln9yihu3zv9CIJBaWL+9r6Z4qCUd7WSZVZtA1O3GpVT15rDxasO3c2j7nvH2Sdy1jTddE/c9L6mVbeDg7lZEO3bHJSlTC6o68MOG6jLzaXQ6mVckt52DzAsMKDfoRUb/1f3cfg8V6oKo+NIvZ2oH6PPYgzyDzh/R/UF6OcxTLmGlOd7lxOfbtzD2TJdxV2sn+LfwKy15mbpGnBD0w2Yh6xaHbrKDXynBjo90tyO9BDwse4K8QBgE8Bi8InuWsbzKYDxfMYcH+Bz5jBoMofBFnMYbDNnDWCHOQx2mcNgjzkMvmDOOsCXzGEQModBxBwGT5gTADxlDoOvmMPga+Yw+IY59wG+ZQ6DmDkMEuYw2Nd0ayhzixd0F6htUBXowPQTFvewONRUGbK/44Vhf28Qs38wiKk/aro9pP7EC0P92SCm/mIQU3/VdGdI/Y0Xhvq7QUz9wyCmPtMvxnKZwV9GvkuFA8ouNp/z98T7B8IaQLYAAQAB//8AD3icjVkNkBvled73+/ZPq9VqJa12ZZ1Op5PupON+dGedfsydkY+zMcbYztkcDj6MMYzjgHGIcY3jEKD8Tcu0xmYc10M8DrGp8SSdpgO4lITQkEk9lDKkOGlKSCAZSDKpSRvCpHQKjm/d51vpzJGmmdqnb/f7e7/3e//fdyV2/vz5bfwYj0i61C3pTy8Iq5wNDVC1TGqWmkSOmh8mtZAvs6acZZ6jeS67/+DLh+R9399L+cEx5/ktn5s6cNMEW7x93/G9Oxbxy55P0n2fPMAOvfSIutc/3NmffP6y5raHH9t365g8ufXQ6s9teT4pSUySzj/Jr+VhqUe6HOcu6bNlca6TtKiQL5Y0NV+8hKr1caq4apSyNFqpN/Bf9BNJRx2gfLG+hIqFvKrhv+g2ak2scr1GveLyK3t+/oW1jyxeEc64rnXWcllodd+Wxsr7SqqnmLfohuXYrdF1O65sDX5aM6nnF19Ye1hsSpHC6dCzzYtXhIPtbia8urdvZdNYFDHpa+2B1a2+KrcXSlLrXif4Du5KISmBe0XDOhP3UhK9+OvuIM0rNbSSVvI0vsM/u8l/1393k3+WJvbs2XOo+fATi+pP7GvSC6f846dP08ZTNOU/8c6dT+2UX3pJ3vkU4NM8+OpJWSLATnS3odPX9tDEfKDc9Z+YAyVJMvZ+wM+A5lGpSxqWlkiRCWN8dKCQcUxZHhroBV21bhC/Vq03uitu4/f0C+2+1+6X2v1R9PkZ1zq3OeqQa/GjweP/2dtyefAatC/Oe/+nFSC0hYacfwzaqBiQOO5xD3ufT0hh3GNAMp4p5DrimsSHBhJursEtli9TCTLTCenNBWK0hITkeNpomSAqURptjFbuoKN31Nnknxx8YBmjpzbd86b/4pv3bOp3RzZW4+b0xnR8q2tsm55YRy8tX+6nrcH+/kGLx+Mr88bYunVjRn5lfCSdPnprzr57ssX3baDtOqkkjYDv5f6sAb7HahDaUsMDAqMeRNPT3C5KQmI9rQSxLpQg6aVGsdRw3Eq9WrTokJXa/KnbLxq2j9hDA7d/anPKOtQaGhjC0PBFwdB3pu+Yxh+1F13viUXe9R9ddGgO1KbF09N71rN1EkHXP5BHwP8OyI7FITu9LQ43wOCi4CLURzRJR8uSPHznzOyxTx6kqQn68u5r9uVL9fFpb8WmVzbcSQduWnlHNhra/eUtawrT4/2F2C7IJj//AXT6K4C/VrpBug182f6J665uqOALuRq3KBCmInjRqBZrIEmTGvXi3LPReqkWm7SE4UdN5iUDuWpvVT0XvXojUS+5iupCAgVbi5BAUPLDvlip8c+75mFjond2dmAqakWImExyPJJSmCxrhulykhkzIkaay0xXjZCiqmHFceykRl+5aNB8xOmr+h1RU7GWMm5n5S/LlJx9lqk6XaVbzGS65n9Vs9gY1zW6SrN4mIsR3ZTZ5bk+X+sZocWDvfloRFMsWVXTycVJ1Q2HIk7TUVI4P2JNJmUnZGrhmOEYcd0AdspEn69OFikWLcUtVowk9JhmkKn2BO3J9rtJhh7I/5M8wQ3Jkfql60DnSwe63TCBzr3uaKVWLZXlQl4DE5OOyzHSZI1qmZXymuOKUYcaIHSTw2LaYmmp3gPlyPKkY3HwXcMDJljLa1S/qPd7XrQvv6iY77NJ5l6xvKyeodxYs5gZUon8f6HQGkdWmSqDKk42neGsQdYVq0orl1dy8XS5p3P9LuqbmNlYXXDjgvEdFP/GRG8qHzeYes149tpklQ7JVs/EcGksDxZndnQOZa20z2vM0jXbtTJKD+tJgTdMLpNh5QdXljor2bxtObdON2YWFW2Z1LZNfJKNQ+7GIdcVG3KdFEImxCF+CZUpX2pA7dyGEBBVw0kQGNGyKFOTUEtVy8PDFQuBNzGG+h1bK+THIvlBw0iMTPd0dSwqNZgem3IUDgFi0WWheEim0CamqSnVnoyuLma2xtaMmGEj0fvYo5R3LE3nYwxLiXHZzahOyIh4Rja0zj9dvqrsGgaPproYmElSoDez4OcG4G/SN+i/pfjz9BN6jirSI5K4HpegP5UsORYJL0wC9UqTakJrvGKT4U7FmqNVi2qtLA9TEaRMFvNitVYvFbV6tTQK5sOfO6ABnLrWUhcXnh0vUEgsEr9KUWtSQRCt5Aa0q7ijbrVUCRaonlgM6JCierEAwKKfhU11Qby86mowue4w4b0Ec+KVVK0CELU6bB52a64GDLBX1bLMabga9mFnqai6owJQFzBqqF0cEYYqANawCkiUyqw2WsFklo0C8UpW7uKugIvNjXwXTHwyS169BihogF21WPcqdVwX93LUZKFexWKMQ5gtXgQKol9qu4N6cZjcOiABY7eRZSBPveF6Iuwp1kq1srBTATkqWJEHNogv3ODRcOswU4g0CqIPG1Sv1EASDiuKaEnAFX9R4WmSoBjeVUH1Yr0oaF9XEeQky9QA7q6IXjxHdemru17YufOFMy/eqt7xHMVJ57BZPJZMQPFlk4Nrsmwoqkw6qSTLMpNVUpkeUmQFoYpukpLRGMOsRUwLcRn2jmEXTIsSkZljJWRdJcijEmKUCGG3ohpclzWFMzUEWEpIVjhnikyWFo7KNgdQWRdo6DiNw07KcYWbJmGRuaCDc11JKDwsR8I4SJV1OSSvrcgKU4FNygAOigwkZBwJ42toWlzWQjIOZBb6zIJusKjOZc64QrJhECAopsa4zkOaq6qKrtuyAzgAzi0uk6HoMYPhH0EFyWDc5EwnBvw408I4h+kO17FB3Fth4okxOcVDHBjwCLMEPWRMqUCCiVtpuqIh8AFMXD/AREMbF65BMVkIDkIHtbDaCEVCN+9cSyZFACFJAMIFIiYuoECJcQLQN4RFEabcFDeUw1FiIcyFd5365aldQeP/mHSmgn06V8JkYhFMAO4M2hJTTUXlBoHDJG4JMpMgl8pAe0VWNVnVDU1WVMUUkoFJMwS6KLgEjzFuaSrGeYhpBlfJkg1AVHAxQ9Y0jUKKDovENC7ICYkwOIIkBQcpssbgUqIgIsPlLSzgGoeoEQ19DLIGHqpRWGtgoFghJ8xITTPygA9XHM5tEFrWFV2msGcp4kKyqVuyRUbY0XTE0BHBiDg3ZBkulnFDENlkdiiBS8FtyAasseAnSG4rUYVBGsOguCw4Z4UsJUQQTSY4KHMoisKihuArKAD58+C7wYEoMwxFGNpwSBECAiZgpRBaCBa244boQ4QIXGZ+JHk1+IvDoyT0AbLBQA4BlCFusFQmVgWSBZooGT0WskImk21NUhA/vc/fhp22pKxUlprSGik6YV45OTban++IG7KCANSBgiOgilXrsFsutUPkRjuELs3re+2+1u6PtvuFeetPKUeVKITExpP3GPq5xUEIwE8Fj9ljwYNd/3t681fepyvHFEOnS3UDcPz/uCWYukXEFKfn3tH+czCiIjsyXhFNa4UUxBtfl132LOKNHulixBvVUi4FFUS8gTytBLPboOC6uDz8CiFeG4bbbSSaJGx0iEQOWYTpLYmmXvGwkq7JKJbqnxPqoNhyRlVff12lZaeZpeZhqU5zUytAhLZjhaVmFOX11xUlg1esvgxdyIV4VzFly2+8rlpszewwZDzETZ2dBgCDWf4+/1xr0+tv4Cwcgb6NPAh8fFIeBh/1diaEPGiwp8uLGiqXRRJqAVu4P0p21xoe9YqIMuYE0WcpVi16sTbTYi0m0SeaM038scXn3nlqBllH9tz9ASX5XYKsa6u95+7vqVO1l9/VW2WxoSabXD8x4ftnbzm5gTqPgXMzgtrshGYmZmd6q1TvYSfEA85fDfKKt/lq4HmJtBrR3qeku6TYhPXZnTffcO3Hrri00p3m6tAAXIgmwhykybWqFni4Ri2vlpKF2mgSQV0iWUCA3+SIr+ELS0GmjHiPaaM1pS1zIk8SMkhiT63aqBVU5Cajyd+d522Znev/a3NwcU9XJmenxqH6KcMI9YTGn61l8vSSnMn3uTweWeAMR+q5vkXFcpEu56vn3/jcr76xGIrp9a0a52vmT9D8Tn2oQr0LF9h2Qe5S410R6Cdlr154qdpPkxsco7OcTaUilk257kyu3Jcpp7PR/FMtGdYNk95dtGhZOakZTv/lb722XTMMTTTkfijwUpvW3+dvs3+DXMSkbsTWY9JKQevlS+rlvp5sMhrWGGjdcFQEEk1yawih2oLSUmtBkkAi5k/8Idq92pKc1h+Pf5Qwv2/0I1SZGJxgizcuZhO/GQpA+P8+T7OZNDf4f124VYM5Bj3QpCnkrDGdS6JWoSHRqjSKSBHkRgfy1oQrtNgTkZHnJkUoWBQBc3UJE2ImAqRqcRh+3XOyJMJskT7mRSxdUukvrl811hVOjvg1ivRkMq76wJEV6s7ERUtCI3ZYD68KCbNc2NfTfyTJrtBUHpOFD8h7qd9aY2TnwrkkbHTuvpzisCGW/i0WP+Srg9B7I2XTNXTQ9N8b3PKFxJ25BWrI5q6BeMYgPZWwsRLxEDxZaKa+iLofiZrIOhCbhJNK2FRJb+UNMG5x+hWsgHrSFPkwzStpCEbyeM6dncHFcy474ebomVvQZL1bxEgr70DOX5YGsL+gBfk0UgpRuyphPzSxrAQgL9hI5F+jOc/lZy6Hr1HUuJBj7uQXNdevb9zl5EL+z8Jh6gxnUuwu2jeTfeu6L0ViiCxMHd622LVoZmIkG1f3W0j2skbWQONE9791ZTsH+lPgUpMSwCUk6kLJalMZrYUolreUkRW7Nx+cWkp/5D+09cbDK2+sDH52/08PbK2k5f/y9/oPQVri+WU3HmrViFq5CIcGLJQuE7bxktpQL3JbCbbR6651K6MwgsLAaxcKQoXYaNuDabxWLQ4gD/CQfIogW4m1F8VaC4QsvUpf9a+i91aYyhfhYbOe/003SytW5JL0XdD31bY/CtotWW823mKA4tWjR2xXe/VVek9Pa0dUk864uZx7ZrYePCn7lNAM0RizZ8UIe8fNpawvRuttXs3gXktBn8EQBXLeqLuiRgR0RVmipIlwXJQSBfu7qFHSkBaWLtQh4dyQCASvIo0Wc+zlF16Ysocc+6wXMmJr0YmFDO9s1KHB2BR6YUvTRAHSS4ju2lgCcy65mmaFWQ/m44nUWcv1kmLr2hhyUetsKhEPdvKQmBqyxYw9JCZCAVeCGh6DD5Dy0ih88UipA0InaizFkitkDXa+gTSLcnA5QlGTUMt6UNypK8BazvXADTfqgyRDEI9dfU/+wOkD+XuuvvJNkt/0v26Hl2+xXXvZSNimH4bX+O/7P/LfXxMOr0FqUSR9TZjGHrh0bOlNBw7ctHTs0gd2PvggXYGlWy4zLRY3RpbZLycS9x4+fG+i6NxzmD16t9POzwOZWihoL1PgYpko8wbxgDAso5UmLzP4Wc+dxwHhprKMb1i28+j6zX+zWFZWqTH5ot1Lx26a6mflVds/vaVvdUBTQe8Viw+v+/ijuyZpE9pl665QLXm1QmptrLxq265tq8p9vatbdEzEV1zcXLbr0UD9gdtP+Q3sR7ABCakIeubTcVsXtUTqhr2LidITMtdYk5TuWDcv1Lpjo8lCa6QhVlCD3s70EPVkfK+jl6iX3vKvo7888An/+rHrMmIgQ2fEgvept8PfHizpoIMdvdf5J1h00H/ietH/89bKAJ/b+E/4KlhnFRgZz9gGUgKBTzKB+J43RZpqMcRTyyj+HTpB3L+WomxsKn3/gqDt2E8f939Nk3TCP+fPsN+UFrOO+9JTY2zB/R0tm//3fHUQ+3TC5nsRTdSnE6L2mGV1cjQS1qpMgSsT9UByz/aPDbK+ZpFd7fjvuOOOf1sy29/5dueKJO132DXZflac6FFH/B9mk/47SQwmV3Se6ewndG9LSnO10Q18HW6UEmdGKfAzMVGU8RQk+t3tiKoURFRsPKL/TNf9J6HSwxfUn77JqqTr+s+1SBxGYvZkMMrvxKN9xgfs/sCXdeGMlCmLMxLdNXEzT5l3gCdMVWCJK+5J6pzePU10Gmb+7QBg7ODLh5g48fHt49Ns6pJjgW2CZZrEodu3Hjy4dXu2bW8/gO80pV7IdI/X9h1aIfDtODIA780/FqggZuDHXKvHctftWUe14MD2ufSZA6cfZrH9QfV7f3D09rZhnDtcfFSR5s7GXa+SCjg7IfwO/c5Z4oqikhcgBBIIc8xWzz8vuKcTBSoBAR7/6FknP3mQPXxzgMv49PT4SYkjZn4CPLSlkBTHucYzOc805MDuVEsUFBkV8TkGN3d6od1lLr4XsTdG/GtXzTR3TFVmv0ePX7nxqr1TxN6APv/1YzuWs8ldjz5xZPcEbZ5Z6W+sVKZ23kyPV6Yenrr22muO7sT07iN/e3hPU125/cTcvU/xjeAxJOnkx1p2BN5WULzUlEvVUr0kCizFvFZmCFOE5eOeKPjlRelHFKeyQUKCH6ssAb8QNWNC8KWSJb7h+L1RqzNXWZRPs8FkOro4mazeWtOzE3bKGUzlF410u4qZLuatcM4MI81A7mmmVEXP9xXNCNnWvcd3ffudb+2hTXvfHOCWsbu+gFvZwRUjy4ZHlyiuFYlG1XhaXTI6vGx4RTljM6eoqCkvLorRSDy4mrUiybTOeCXDDIsPvLn3dlE3gGyfP3t+B0/z9dICaQiy3d+ZMoX+uLh7CfGGqOLBHjTgAIqIOlytXg7KnBeu2iSuOpu3Tk7sXn6TlVtlOszWHWIpK75v57antmfj01uyyKQV5mpLbStlOTbbsuuFuGl82zyRkS3Ztm7Mpum+yvJtjznOfc8Nbms6g6YbsjVHWWjqNrPmYqnj7BEpCd7EWrxp2ZJWztdFrRSRH4/6OXvQ9n8Zja7C8zjdinZVlLmu7eeiUXJF17Yfp0/beEoX4lThP0T+qT/dvSCh4v69VK+KcqdGrWKkKG9WqK0Ftbnc7G76uGFcHM6E/d/uRsh0sWG8GM7QLzYtnX176aZNS5m3dNNdWBLMYEk4LNa+GKb3Z89MbqLNl7KUeLTsCz8KHMqIIPSnmwvN4DtpOwJqHwfNilIQMxTaOaP4KnFJUHUcrXhu4kIm/CFBHB7ftNQfEcjQ6UI2xLUOVWM8YvojQSpIp3urSg/CRTl8r39xpMd617IugQn5PN2MTjNCVz7Z2gs8LTmBhJh4tbe9+SFdKaji64I/ZlnvBusjYmMEENp3SrS/B+pPZxJ6YJfnoV5rp7vz7uiy2+ZhVr3zQ+QnN7N/uHCymNxwYUaQsF3nvj44ryEtE98txgcSYS7qCO2gEdFYEDQmqmUq5DW1S1iWBtRTRDYQ6bmgUtRlm9ybK4lDe+m9INcLmv3NjQsfvDwUWSmrISXbu6jf7SgspmAqFc8YWcf8wVyl9b1n7143t82gP144Xf6MZTRkrdiRjSfTpj1RdDARz4dtNZ3pW3d7u5In1LLt156GXzMDv+baTNAvGYM/K8S6k+JXQio9mkTEgKT6Oaat9/efoMfu/+7RV145eohX/s4/TvKR2S3XvPLYK+IPenT+vfOP0U94RoqIOF7kJAqC0pJQ7zrCU6JT6XTa9fekUvSgi1f/sJPGk/4shR9m0v7ewFae/0/A+XEbTlgBHC8EIHX4c6hlqRihrf5hAYoeTKX8PQDh0E7aJWD5u9Mp/NJuOz76gM+wU+1KifHMQE9HwpT5//5czOflTt68XPdCf4Njn/t166NvDFHwH+jN/yBM9oVPv+SQNTeDzpxP+BZ7hC+FbVBPRoUvDDE3KMLNr0uxh/zXqC8c3igMwY3IsR6Fxm8Ms7/yX/N/gNfrwPwviYzqUQPv2Tm4u+bgMgG3rbyNEH1YxmLbAC5jbgTIPv81cUAAhG72bwAknEYX4dxMGAeIhf8DGsCmVnicY2BkYGAAYn9tocJ4fpuvDNzML4AiDBfPfzkOoU9N///5fxaLAXMYkMvBwAQSBQBrbQ3jAAAAeJxjYGRgYA76n8UQxaLPwPD/OYshA1AEBegDAHAGBJh4nGN+wcDAvACII4G4AIqBYkw/IDSLPlReEChmDVUHFQfLvYBiqBo4PxKhFkwnAvFdhDhTE0QPmIap5UFSDzMPxN7KwMB4EYLBek9BMAC7ixnvAAAAAAAAADwAqADYAPYBWgGkAfYCJgLaA2IDwgaWBxIHegfQCIYI/gl+CZ4J5AoICnQK2AsyC4ALyAv4DDIMZAycDNoNFA1WDdIOJA5SDpQO/g82D54PyA/sEBAQYBCMELgAAQAAAC8B+AAGAAAAAAACAAAAEABzAAAAIgtwAAAAAHicdZHNSsNAFEa/aWvVFlQU3HpXUhHTH+hGEAqVutFNkW4ljWmSkmbKZFroa/gOPowv4bP4NZ2KtJiQzLln7ty5mQA4xzcUNleXz4YVjhhtuIRDPDgu0z86rpCfHR+gjlfHVfo3xzXcInJcxwU+WEFVjhlN8elY4UydOi7hRF05LtPfOa6QHxwf4FK9OK7SB45rGKnccR3X6quv5yuTRLGVRv9GOq12V8Yr0VRJ5qfiL2ysTS49mejMhmmqvUDPtjwMo0Xqm224HUehyROdSdtrbdVTmIXGt+H7unq+jDrWTmRi9EwGLkPmRk/DwHqxtfP7ZvPvfuhDY44VDBIeVQwLQYP2hmMHLbT5IwRjZggzN1kJMvhIaXwsuCIuZnLGPT4TRhltyIyU7CHge7bnh6SI61NWMXuzu/GItN4jKbywL4/d7WY9kbIi0y/s+2/vOZbcrUNruWrdpSm6Egx2agjPYz03pQnoveJULO09mrz/+b4f4GSETQB4nG1Q2XLbMAzU2pRky5GbtE2T3s3RO2y+iSIhiRVNqjyS0d9XjqfJS/Gwg51d7ADIFtmhquz/dZtlWGAJhhwFSqywRoUNjlBji2c4xgme4wVe4hSvcIZzvMYbvMU7vMcHfMQnXOASV7jGZ3zBV3zDd/zAT9yA4xdui0DCy74Q3rv7UIUoPKfdGKdVK+6c15FqYXRn+e8Uom6nMnoRelJL6bpcGpfUyrpIjXMDa5xRhY6zX+adcQ0V2iqykUmnqBy1jMlTbXSI3KZdQ57U5oE1yRiKrHU25jttU8g9jWbKQy88sSDuqGpnS5CeyC5pIpascmX4k2Y9FCNZqc0yio7td2HGyWG9B+5mqX7suDAxb/Z02+nYp4ZL7aWZjxloWrihcgM/ZKpc9iSHzQMe/nHUkxn/DZTKSW7pvjbUxkN2o7ut113/RNmObFo/OqonNcv+AhmPiuAAAHicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MjBoQWgOFHonAwMDJzKLmcFlowpjR2DEBoeOiI3MKS4b1UC8XRwNDIwsDh3JIREgJZFAsJGBR2sH4//WDSy9G5kYXAAH0yK4AAAA') format('woff'),\n       url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4oSSQAAADsAAAAVmNtYXDQPhm3AAABRAAAAUpjdnQgAAAAAAAAKmgAAAAKZnBnbYiQkFkAACp0AAALcGdhc3AAAAAQAAAqYAAAAAhnbHlmBB6RhgAAApAAACFwaGVhZAbiAwMAACQAAAAANmhoZWEH4AOwAAAkOAAAACRobXR4n2gAAAAAJFwAAAC8bG9jYdu85dYAACUYAAAAYG1heHAAyw16AAAleAAAACBuYW1lzJ0bHQAAJZgAAALNcG9zdIqG41QAAChoAAAB93ByZXDdawOFAAA15AAAAHsAAQNkAZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoLQNS/2oAWgNWAJYAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEQAAwABAAAAHAAEACgAAAAGAAQAAQACAADoLf//AAAAAOgA//8AABgBAAEAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv///2oDoQMNAAgAIQAItRkLBgMCLSsBNC4BBh4BPgEBFAYiLwEGIyIuAj4EHgIXFAcXFgKDlMyWBI7UjAEiLDoUv2R7UJJoQAI8bI6kjHA4A0W/FQGCZ5IClsqYBoz+mh0qFb9FPmqQoo5uOgRCZpZNe2S/FQAAAgAA/7EDWwMLACQARwAItT8oEAQCLSsBFBUOASMiJicHBiImPQE0NjsBMhYGDwEeATMyNjc2NzY7ATIWExUUBisBIiY2PwEmIyIGBwYHBisBIiY3NT4BMzIWFzc2MhYDSyTkmVGYPEgLHBYWDvoOFgIJTShkN0qCJwYXBQxrCAoOFBD6DhYCCU1ScEuCJwYXBQxvBwwBJOaZUZo8SAscGAEFAwGWuj45SAsWDvoOFhYcC00lKEo+CjgNDAG4+g4WFhwLTU1KPgo4DQwGBJa6PjlICxYAAAAAAgAA/6YDcAMWAAkAEwAItQ8LCAICLSsBBRMlBRMlIRsBBxcnNwcnBycXBwNw/vpe/vD+8F7++gFAeHh4lj6QsDg2sI4+AcbE/qTQ0AFcxAFQ/rDufLJyBMrKBHKyAAAAAQAA/6YDcAMWAAkABrMEAAEtKwETIQUTJQUTJSEBuHgBQP76Xv7w/vBe/voBQAMW/rDE/qTQ0AFcxAAABAAA//kD6AMLAA8AHwAvAD8ADUAKOzMrIxwUDAQELSslFRQGByEiJic1NDY3ITIWNxUUBgchIiYnNTQ2NyEyFjcVFAYjISImJzU0NhchMhY3FRQGJyEiJic1NDYzITIWA+gWDvxgDxQBFg4DoA8UARYO/GAPFAEWDgOgDxQBFg78YA8UARYOA6APFAEWDvxgDxQBFg4DoA8UZEcPFAEWDkcPFAEWyEcPFAEWDkcPFAEWyUgOFhYOSA4WARTHSA4WARQPSA4WFgADAAD/gAL4A0AACwAfACsACrcjIBsSBwADLSsTFiA3Aw4CIi4BJwEeAR0BFAYgJj0BNDY/ATY7ATIXBzMuASsBIg8BMzczMnoBoHo2AkKGlIREAgGyXoDg/sjggF4qFjBcNBIMVFwaEmYWCmpUQFIBykZG/hoOLCoqLA4DEhJKIgo6UlI6CiJKEjAaGqBuIBB+QgAAAgAA/2oD6ANSACcAMAAItS4qHgoCLSsRNTc2Nyc3FzY/ATMXFhc3FwcWHwEVBwYHFwcnBg8BIycmJwcnNyYnNxQWMjY0Jg4Blg4YYG11KS8QnBAtK3VtYBgOlpYOGGBtdSstEJwQLyl1bWAYDs1UelRUelQBEJwQLSt1bV8XDpaWDhdfbXUrLRCcEC8pdW1gGA6Wlg4YYG11KS9ePFRUeFYCUgAB////+QQwAwsAGwAGsw4DAS0rJRQGByEiJjc0NjcmNTQ2MzIWFzYzMhYVFAceAQQvfFr9oWeUAVBAAah2WI4iJzY7VBdIXs9ZfAGSaEp6Hg8JdqhkTiNUOyojEXQAAAAD//n/sQOpAwsAUQBhAHEACrdsZV1VNwYDLSsBFgcDDgEHISImJyY/ATY3NCY1Nj8BPgE3NiY2PwE+ATc2Jjc2PwE+ATc0Jj4BPwI+AT8BPgIXFTYzITIWBwMOAQchIgYXFjMhMjY3EzYnFgUGFhchMjY/ATYmJyEiBg8BBhYXITI2PwE2JgchIgYHA5MWDJoKQCX9/StQDw4NAQECBAEEEg0YBQIEBAcKDBYDAQQCAgoNChoDBAIIBgoJBQYGCwUUFBAVBwGpKSwMmBQoNP4bDwwFDkMCAxAeBKgEARX9ugIGCAFTCA4CDAIIB/6tBw4COgMIBwFTBw4DCwMIB/6tCAwEAkcgKP4HJDABPCwlIg8NBwUOBAYGGhU8FQYWCwkNFD4UBRgEBwoNDkIVBBQJDAcLEQoUChIICgIEAQVAKP4GQiYBEQ8nEg4CJg0TCBEHCgEMBiQHCgEMBrMHCgEMBiQHDAEKCAADAAD/sQMTAwoAFAAqAF0ACrdBKyEWCwEDLSslFjMyNTQnLgQjIgcVFAcVFBYDFjMyPgI3NC4CJyIHFBYHFRQHFAE3PgE3PgMmNzUQJy4EIyc2JDsBMh4DFRQOAwceAQcUDgMjIiYHIgcBNikl0hcPKCI4JiIoEAEEAxcmLkQ2HAEgOj4mHC0GAQH+0wEJThQEBgIGBAIMAhQeGhwDAjcBDklMJ0pGMiASGi4kHVZ0AShAWlw0GWIZO3ABErtAJRgiEgoCBlg7HlsVNAGWBA4kQC8nOiIOAQcccB0tHg4a/gM1Ag4IBxAWDhwFJAIkGAUGBgIELgEKDiIsSicdMh4iEA4UblQ3WjgmEAQBBgAAAAEAAP+xAjsDCwA7AAazMhABLSsVPwE2NzY/ATYSPQEuASInNxYfARY3MjY/AQYHDgEHBg8BDgEHBgIPAgYVFxYXBgciBiMiJiMmIyIGBwotKhQQByMiOg0iLAoKEzBUJB8bOCc3AggRUBQFAwUCBAIPRAkSCQQBCV4CBwYYBhBCD00mHGYRTjAMCwoTJaKeASIUDgcIAzoCAgQCAQIDBBYcBhQJCg0XCh4JUv7QLlMuFgoKAw8YHwIMAQoBAAAAAAP//f+xA1kDCwAMAbsB9wASvwHeAbwBMgCYAAYAAAADAC0rATIeARQOASIuAj4BAQ4BBzI+ATU+ATc2FyY+Aj8BBiY1FAc0JgY1LgQvASYiDgEVJiIUDgEiBzYnJgc2NCczLgInLgEGFB8BFgYeAQcGDwEGFhcWFAYiDwEGJicmJyYHJicmBzImBz4BIzY/ATYnFjc2PwE2MhYzFjQnMicmJyYHBhciDwEGLwEmJyIHNiYjNicmIg8BBh4BMhcWByIGIgYWBy4BJxYvASIGIicmNzQXJwYHMj8BNjU2FzcXJgcGBxYHJy4BJyIHBgceAhQ3FgcyFxYXFgcnJgYWMyIPAQYfAQYWNwYfAx4CFwYWByIGNR4CFBY3NicuAjUzMh8BBh4CMx4BBzIeBB8DFjI/ATYWFxY3Ih8BHgEVHgEXNjUGFjM2NQYvASY0JjYXMjYuAicGJicUBhUjNjQ/ATYvASYHIgcOAyYnLgE0PwE2JzY/ATY7ATI2Ji8BFjYXFjcnJjcWNx4CHwEWNjcWFx4BPgEmNSc1LgE2NzQ2PwE2JzI3JyYiNzYnPgEzFjc2Jz4BNxY2Jj4BFzc2IxY3Nic2Jic2MjU2JyYDNjcmIi8BNiYvASYvASYPASIPARUmJyIvASYGBwYPASY2JgYPAQY2BhUOARUuATceARcWBwYHBhcUBhYBrXTGcnLG6MhuBnq8ARIBCAMBAgQDERUTCgEMBAwDAQcGBAQKBQYEAQgBBgEEBAQCBAYBBgIICQUEBQUDAQgMAQUcBwICAQgBDgECBwkDBAQBBAIDAQcKAgQFDQQCFA4TBAgGAQIBAgUJAgETCQIEBgUGCgMIBAcFAwIGCQQGAQUJBAUDAwIFBAEOBwsPBBADAwEIBAgBCAMBCAQEBAMDBAIEEgUDDAwBAwMCDBkbAwMIBRMFAwsEDQsBBAIGBAgECQRRMgQFAgYFAwEYCgECBwUEAwQEBAECAQEBAgoHBxIEBwkEAwgEAg4BAQICDgIEAgIPCAMEAwIDBQEECgoBBAgEBQwHAgMIAwkHFgYGBQgIEAQUCgECBAIGAw4DBAEKBQgRCgICAgIBBQIEAQoCAwwDAggBAggDAQMCBwsEAQICCBQDCAoBAgEEAgMFAgECAQQBAgIEGAMJAwEBAQMNAg4EAgMBBAMFAgYIBAICAQgEBAcIBQcMBAQCAgIGAQUEAwIDBQcEAwISAQQCAgUMAgkCAgoIBQkCCAQCCgkNCWlyUQEMAQ0BBAMVAQMFAgMCAgEFDAgDBAUBCgEDAQEECAQKAQcGAgoCBAEMAQECAgQLDwECCQoBAwt0xOrEdHTE6sR0/t0BCAIGBgEECAMFCwEMAgIEDAEKBwIDBAIEAQIGDAUGAwoBBgQBAQICAgEDAwIBAwgEAgYCAwMEBQQGBwQGCAoHBAUGBQwDAQIEAgEDDAkOAwQFBwgFAxECAw4HBgwDAQMJAgcKAwYBDgQKBAECBQICBgoEBwcHAQkFCAcIAwIHAwIEAgYCBAUKAwMOAgUBAQIFBAcCAQoIDwEDAgIHBAMOAwIEAwcDBgQEAQEtTwQBCAQDBAYPCgIGBAUEBQ4JFAsCAQYaAgEXBQQGAwUUAwMQBQIBBAgFCAQBCxcOBQwCAgQEDAgOBA4BCgsUBwgBBQMNAgECARIDCgQECQUGAgMKAwIDBQwCEAkTAwMEBAYCBAoHDgEFAgQBBAICEAUPBQIFAwILAggEBAICBBgOCQ4FCQEEBgECAwEBAQQDBgcGBQIPCgEEAQIDAQIDCAUXBAIICAMEDwIKCgUBAgMECwkFAgICAgYCCgcGBQQEBAMBBAoEBgEHAgEHBgUDBAEBAQUEAv4NFVUCAgUEBgIPAQECAQIBAQMCCgMDBAECAwIGBwMOBgIBBQQCCAECCAMDAgIFHAgRCQ4JDAIEEAcABf////gD6QMLAA4AHgAuAD4ATgAPQAxLQjozKiIbEgoEBS0rExQPAQYiJjcRNDYyHwEWARUUBichIiY3NTQ2NyEyFicVFAYnISImNzU0NhchMhYnFRQGByEiJjc1NDYzITIWJxUUBiMhIiY3NTQ2NyEyFsQFoAUPDAEKEAWgBQMkCgj8PAcMAQoIA8QHDAEKCP2hBwwBCggCXwcMAQoI/aEHDAEKCAJfBwwBCgj8PAcMAQoIA8QHDAGCCAWhBQoIAUEICgWgBf7sawcMAQoIawcKAQzQawcMAQoIawcMAQrOawcKAQwGawgKCs9rCAoKCGsHCgEMAAADAAD/uQQWAroAFAAkADkACrc0JyAYCgIDLSslBwYiJwEmNDcBNjIfARYUDwEXFhQBAw4BLwEuATcTPgEfAR4BCQEGIi8BJjQ/AScmND8BNjIXARYUAVgcBQ4G/vwGBgEEBRAEHAYG29sGAUTQAg4GIggGAdADDAcjBwgBbP78Bg4GHAUF29sFBRwGDgYBBAVFHAUFAQQGDgYBBAYGHAUQBNzbBg4CTv0vBwgDCQMMCALQCAYBCgIO/o7+/AUFHAYOBtvcBQ4GHAYG/vwFEAAABP///7EELwMLAAgADwAfAC8ADUAKLCQfFw8KBgMELSsBFA4BJjQ2MhYBFSE1NxcBJSEiBgcRFBYzITI2JxE0JhcRFAYHISImNxE0NjchMhYBZT5aPj5aPgI8/O6yWgEdAR78gwcKAQwGA30HDAEKUTQl/IMkNgE0JQN9JTQCES0+AkJWQED+/vprs1kBHaEKCP1aCAoKCAKmBwwT/VolNAE2JAKmJTQBNgAAAAYAAP9qA+kDTQAfAD0ATQBdAG0AfQARQA55cmlhW09JQTIhGgMGLSsXFAYHIic3FjMyNjU0Byc2PwE2NzUiBicVIzUzFQceARMVIyY1ND4DNzQmByIHJz4BMzIWFRQOAgczNQUVFAYjISImPQE0NjMhMhYBFSM1MzU0NzUjBgcnNzMVBRUUBiMhIiY9ATQ2MyEyFgMVFAYHISImPQE0NjMhMhbVPiw8JB8cIBAYOw4EDhgKCgkkCTu6NRwiAcoEHCIoFgMSDRkULw02ICg4Ji4mAUcDTQoI/VoICgoIAqYHDPztuzwBAQUXKEw7A04KCP1aCAoKCAKmBwwBCgj9WggKCggCpgcMNi0yASUxGRAQIwQfBhIfDQgBAgEeVTFBBioBQlkUCh0uHhgYDQ4QASAhHCAuKBwuGh4PIrJrCAoKCGsICgwB8Dg4RC4VBwoUKkfh2GwHCgoHbAcKCgEWawcKAQwGawgKCgAAAAAGAAD/1APpAucACAARACEAKgA6AEoAEUAORj82LigkHhUPCwcCBi0rNxQGLgE0PgEWNRQGIiY0NjIWARUUBichIiY9ATQ2NyEyFgEUBiImNDYyFgEVFAYjISImPQE0NjMhMhYDFRQGByEiJj0BNDYzITIW1j5aPj5aPj5aPj5aPgMSCgj9WggKCggCpgcM/O0+Wj4+Wj4DEgoI/VoICgoIAqYHDAEKCP1aCAoKCAKmBwxALEACPFw8AkDyLT4+Wj4+/utrBwwBCghrBwoBDAIALT4+Wj4+/utsBwoKB2wHCgoBFmsHCgEMBmsICgoAAAACAAD/sQOhAwsABwBQAAi1EQgDAAItKwEHFxYzMjcmATc+BDcbATMXExYfAR4BFxYXHgEXFhUHBhciJgciBiM0PwI2PwE2PwE2JzQmLwIOARcUHgEfARY3FhUUByImIyIGJwYBlV9MOh8LFTD+NQENJBwcFgaEnEgGchMpPwkwEAsIC0wJBAEBASOOJCqcFQJJBwYDEQQCBQMCIhcY+w46ARAgCyAVAgECIIIgBRQCLQIa+wEBAY3+BiwEBgYKGBABWAGUDP70LGSZE3wgGQYJEAMWCgcFAwoBCBgTEAEBAQcCAgYEBAlaNjgBIZgPDBIKAgUDAQsVBQsMBgEIAAAAAQAAAAADEgHtAA8ABrMMAwEtKwEVFAYnISImJzU0NjchMhYDEiAW/VoXHgEgFgKmFiABt2sWIAEeF2sXHgEgAAEAAP+xA+gDLgArAAazIwcBLSslFA8CBgcGIiYnNDY3NjU0LgUrARUUBiInASY0NwE2MhYHFTMgFxYD6EcGBwMFBhIIAQIBAxQiOD5WVjd9FCAJ/uMLCwEdCxwYAn0Bjloe4V2fDREIBAoMCAUUAyYfOFpAMB4SBo8OFgsBHgoeCgEeChQPj+FLAAAAAQAA/4cD6AM1ABMABrMJAAEtKxU0PgUzNQkBESIOBTBIdmCUUEMBc/6NZmKaSmIyLHmP4pJmMhoE9f6M/o0BDAISIkRilgAABAAA/7EDWQMLAAMAIQAxAEUADUAKPTUtJQwEAgAELSsXITUhBTMRNCYvAS4BBxUUBiMhIiYnNSMRMzU0NjMhMhYHAzU0JisBIgYXFRQWOwEyNgURFAYjISImJxE0NjMhMhYfAR4B1gGt/lMB9EgMBZ0FHAgeF/6+Fh4BSEggFQHRFiAB1goIawcMAQoIawcMAWQeF/0SFx4BIBYCBRc2D5wQFgfW1gH0CBoHnAYMAegWICAW6P026BYgIBYBHrIICgoIsggKCgr9+hYgIBYC7hYgGA6dDzYAAQAA/7EDWgMLAEMABrMsCQEtKwEHFzc2Fh0BFAYrASInJj8BJwcXFgcGKwEiJic1NDYfATcnBwYjIicmPQE0NjsBMhYPARc3JyY2OwEyFgcVFAcGIyInAszGxlAQLRQQ+hcJChFRxsZQEQkKF/oPFAEsEVDGxlALDgcHFhYO+hcTEVDGxlERExf6DxYBFgcHDgsCJMbGUBITGPoOFhcVEVHGxlERFRcWDvoYExJQxsZQCwMJGPoOFi0QUcbGURAtFg76GAkDCwADAAD/+QPoAn0AEQAiADMACrcwJxsUDwIDLSsBJicWFRQGIiY1NDcGBx4BIDYBNCYHIgYVFB4BNjU0NjMyNgUUBwYEICQnJjQ3NiwBBBcWA6FVgCKS0JIigFVL4AEE4P65EAtGZBAWEEQwCxAB2QtO/vj+2v74TgsLTgEIASYBCE4LATqEQTpDaJKSaEM6QYRyiIgBSQsQAWRFDA4CEgowRBDMExOBmpqBEyYUgJoCnn4UAAABAAD/sQNZAwsAMQAGsywEAS0rARQOAiMiJicmND8BNhYXHgEzMj4DLgIiBgcXFgYrASImJzU0Nh8BPgEzMh4CA1lEcqBWYK48BAVMBhEEKXZDOmhQKgIuTGxvZChNERMX+g8UASwRSDyaUleedEIBXleedERSSQYOBE0FAQY1Oi5ManRqTC4oJU0QLRYO+hgTEkg5PkR0ngAAAAADAAD/4gNhAtoADwATACYACrciGhIQCAADLSsBIR4BFxEOAQchLgEnET4BBSERIQMjNSERMxUjLgEnET4BNyEeARcBNwHpHCQBASQc/hcbJQEBJQHh/l0Bo5Jl/l86XRwlAQElHAHoHCQBAfgBJRv+bBslAQElGwGUGyVd/qYCDyz+sF8BJRsBixwlAQElHAADAAD/cQPeA0wAAgAGABMACrcQCgYEAQADLSsVEwEDCQM+Ah8BFg4CLwEmRAESzQGmAQP+WwEPAjpQGoMZAjpQGoMbjwFX/u8BQgGm/vz+WgLyJzwCG4IaUDoCGYMbAAACAAD/vQNNAwsACAAdAAi1Fw0HAgItKxM0Jg4BHgI2ARQHAQYiJwEuAT0BNDY3MzIWFwEW+io6LAIoPiYCVRT+7hY7FP5xFR4qHekdSBUBjxQCWB4qAiZAJAYw/tkeFf7uFRUBjxVIHegdKgEeFf5xFQAAAAIAAP9qA1kDUgAGABgACLUXDwEAAi0rAREWHwEWFwUUFjchERQGByEiJicRNDY3IQI7DQjjCAj+sSAWAS8eF/0SFx4BIBYBvgI0AQgICOQHDRIWIAH9sxceASAWA3wXHgEAAAACAAD/+QKDAwsABwAfAAi1GAwEAAItKxMhNTQmDgEXBREUBgchIiYnETQ2FzM1NDYyFgcVMzIWswEdVHZUAQHQIBb96RceASAWEZTMlgISFx4BpWw7VAJQPaH+vhYeASAVAUIWIAFsZpSUZmweAAAAAQAA//kDoQMMACUABrMkFwEtKwEVFAYHIyImPQE0Jg4BBxUzMhYXERQGByEiJicRNDYXITU0PgEWA6EWDiQOFlJ4UgE1Fx4BIBb96RceASAWAXeS0JACEY8PFAEWDo87VAJQPWweF/6+Fh4BIBUBQhYgAWxnkgKWAAAAAAEAAP/5AoMDUwAjAAazEwcBLSsBMhYXERQGByEiJicRNDYXMzU0Nh4BBxQGByMiJjU0JiIGFxUCTRceASAW/ekXHgEgFhGUzJYCFA8kDhZUdlQBAaUeF/6+Fh4BIBUBQhYgAbNnlAKQaQ8UARYOO1RUO7MAA////7ADWQMQAAkAEgAjAAq3IBcMCgQCAy0rATQnARYzMj4CBQEmIyIOAQcUJRQOAi4DPgQeAgLcMP5bTFo+cFAy/dIBpUtcU4xQAQLcRHKgrKJwRgJCdJ6wnHZAAWBaSv5cMjJQcmkBpTJQkFBbW1igckYCQnactJp4PgZKbKYAAAAAAQAA/8QDXAMLAFIABrNPBAEtKwEUBgcGJj0BNCc+BCc0JzYnJgYPASYiBy4CBwYXBhUUHgMXBgcOASImJy4BLwEiBh4BHwEeAR8BHgIyPwEVFBcUBicuATU0PgEyHgEDWaSBDw4dIDI4IhoCLBUaDzwVFTRuNQgeQBAYFCwYIjgwIRYFDBomIg4LIAwLDAgCCAMEDBgGBQgiKCYMDQEQDoGkdMLuwHgBXozgKwMOCnY2GQMOHixIMEQvMz8FFg4NDw8GEhoGPzMvRC9ILhwQAhQmBQYYFxIWAwEECgYDAwYeDg0VGggCAzIcAgoOAyvgjHXEdHTEAAAC//r/cAMaA1YAGQAtAAi1Kh0YDAItKwEWBgcGJw8CBg8BBiYvASY3ASYnJjY3NhYHNi4BBgcOAR8BHgEfAR4CMj4BAwYUYGZCQHZGaA4gTAwUAhAIFAECGA4SjnJqsmweElRkHggIAwUCFgdDEA4YDhQQAmR0xhIMCsIMphwEDgQQDmIeGgGCMkZqohQUgrwsaj4ULAwWCRAHFAUxDAgQAg4AAAEAAAAAA6QCmAAVAAazEQQBLSsBFAcBBiInASY0PwE2Mh8BATYyHwEWA6QP/iAQLBD+6g8PTBAsEKQBbhAsEEwPAhYWEP4gDw8BFhAsEEwQEKUBbxAQTBAAAAAAAgAA/7EDWQMLABQAJAAItSEZEwYCLSslATY0LwEmIgcBJyYiDwEGFB8BFjIBERQGByEiJjURNDY3ITIWAX4BVwoKOQscC/77dgoeCjkKCsgLHAHmXkP96UNeXkMCF0NefQFXCh4KOQoK/vt2Cws5CxwLyAsB+P3oQl4BYEECGEJeAWAAAgAA//kDoAMLAC4AQwAItT4xDAMCLSsBFRQGIyEiJjURNDY3ITIXHgEPAQYjIicmIyEiBgcRFBYXITI2PQE0PwE2MzIXFhMBBiIvASY0PwE2Mh8BATYyHwEWFAMSXkP+MENeXkMB0CMeCQMHGwYHAgMNDP4wJTQBNiQB0CU0BSQGBwMEC4H+OQ0kDvAODj0OJA6TAWkNJA4+DQFLsUNeXkMB0EJeAQ4EEwYcBQEDNCX+MCU0ATYkjQgFIwYCBAEF/joODvANJA4+DQ2TAWkNDT0OJAACAAD/+QMTAwsADwAfAAi1HBMIAAItKwEhIgYHERQWFyEyNjURNCYXERQGIyEiJjURNDY3ITIWAnH+MCU0ATYkAdAlNDR8XkP+MENeXkMB0EJgAsM0Jf4wJTQBNiQB0CU0Wf4wQ15eQwHQQl4BYAAAA//9/7EDXwMLAA8ANwBEAAq3QTsrEwsDAy0rJTU0JisBIgYdARQWOwEyNhM0LgEjIgcGHwEWMzI3PgEyFhUUBgcOARcVFBY7ATI2NDY/AT4DFxQOASIuAj4BMh4BAfQKCGsICgoIawgKjz5cMYhHCQ1KBAYJBR4lOCoWGyM8AQoIawgKGBIcCh4UDNdyxujIbgZ6vPS6flJrCAoKCGsICgoBfzFULncOCjcEByYbHhIVGgwQQCYUCAoKEiILEAYaHChSdcR0dMTqxHR0xAAAAAIAAP9qA7UDUgAMABgACLUWEAIAAi0rFRE3IREjESEVIxEhFSc1MzUzFTMVIxUjNbwCB1b+j6YBooPRoM/PoJYDMrb+pAEEnP1kWM+iz8+iz88AAAH/9P+iAd4DHAANAAazCQMBLSsFFgcGJwEmNwE2FxYHAQHEGhoaFv54GBgBiBYaGhr+mhQaFhoaAYoYGgGKGhoWGv6MAAAAAAH/8/+iAd0DHAANAAazCwUBLSsXCQEmNzYXARYHAQYnJg0BZv6aGhoaFgGIGBj+eBYaGhQBcgF0GhYaGv52Ghj+dhoaFgAAAAMAAP/5A1oCxAAPAB8ALwAKtyskGxMMBAMtKyUVFAYHISImJzU0NjchMhYDFRQGJyEiJic1NDYXITIWAxUUBgchIiYnNTQ2FyEyFgNZFBD87w8UARYOAxEPFgEUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxZkRw8UARYORw8UARYBEEgOFgEUD0gOFgEUAQ5HDxQBFg5HDxYBFAAAAAABAAD/wAKYA0MAFAAGsw8HAS0rCQIWFA8BBiInASY0NwE2Mh8BFhQCjf7YASgLC1wLHAv+YgsLAZ4KHgpcCwKq/tj+1woeCl0KCgGfCh4KAZ4KCl0KHgAAAQAA/8ACdANDABQABrMPAgEtKwkBBiIvASY0NwkBJjQ/ATYyFwEWFAJq/mILHAxcCwsBKP7YCwtcCx4KAZ4KAWn+YQoKXQscCwEpASgLHAtdCgr+YgscAAEAAAABAABPKxJxXw889QALA+gAAAAA0c/0xwAAAADRz8qX//P/agQwA1YAAAAIAAIAAAAAAAAAAQAAA1L/agBaBC8AAP/nBDEAAQAAAAAAAAAAAAAAAAAAAC8D6AAAA6AAAANZAAADcAAAA3AAAAPoAAAC+AAAA+gAAAQvAAADoAAAAxEAAAI7AAADWQAAA+gAAAQvAAAELwAAA+gAAAPoAAADoAAAAxEAAAPoAAAD6AAAA1kAAANZAAAD6AAAA1kAAANhAAAD3QAAA1kAAANZAAACggAAA6AAAAKCAAADWQAAA1kAAAMMAAAD6AAAA1kAAAOgAAADEQAAA1kAAAO1AAAB0QAAAdEAAANZAAACygAAAsoAAAAAAAAAPACoANgA9gFaAaQB9gImAtoDYgPCBpYHEgd6B9AIhgj+CX4JngnkCggKdArYCzILgAvIC/gMMgxkDJwM2g0UDVYN0g4kDlIOlA7+DzYPng/ID+wQEBBgEIwQuAABAAAALwH4AAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE1IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA1ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALwAAAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8Gc2VhcmNoBmFycm93cwpzdGFyLWVtcHR5CGZhdm9yaXRlDWFsaWduLWp1c3RpZnkHdHJhc2hlZANjb2cFY2xvdWQIbm90ZWJvb2sEYm9sZAZpdGFsaWMFZ2xvYmUGaW5kZW50BGNvZGUHcGljdHVyZQ1saXN0LW51bWJlcmVkC2xpc3QtYnVsbGV0BGZvbnQFbWludXMFcmVwbHkFc2hhcmUEc2F2ZQpmdWxsc2NyZWVuA2V5ZQR1bmRvB3NxdWFyZXMGcGVuY2lsA3RhZwRub3RlBGxvY2sJbG9jay1vcGVuDWxvY2stb3Blbi1hbHQFYmxvY2sOZ2l0aHViLWNpcmNsZWQDa2V5Am9rCm9rLXNxdWFyZWQFY2hlY2sLY2hlY2stZW1wdHkMaGVscC1jaXJjbGVkB2RvYy1uZXcNbGVmdC1vcGVuLWJpZw5yaWdodC1vcGVuLWJpZwRtZW51CWxlZnQtb3BlbgpyaWdodC1vcGVuAAAAAAEAAf//AA8AAAAAAAAAAAAAAACwACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0VSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsQEKQ0VjsQEKQ7AAYEVjsAMqISCwBkMgiiCKsAErsTAFJbAEJlFYYFAbYVJZWCNZISCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAAI0JhsAJiZrABY7ABYLAFKi2wBywgIEUgsAtDY7gEAGIgsABQWLBAYFlmsAFjYESwAWAtsAgssgcLAENFQiohsgABAENgQi2wCSywAEMjRLIAAQBDYEItsAosICBFILABKyOwAEOwBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAssICBFILABKyOwAEOwBCVgIEWKI2EgZLAkUFiwABuwQFkjsABQWGVZsAMlI2FERLABYC2wDCwgsAAjQrILCgNFWCEbIyFZKiEtsA0ssQICRbBkYUQtsA4ssAFgICCwDENKsABQWCCwDCNCWbANQ0qwAFJYILANI0JZLbAPLCCwEGJmsAFjILgEAGOKI2GwDkNgIIpgILAOI0IjLbAQLEtUWLEEZERZJLANZSN4LbARLEtRWEtTWLEEZERZGyFZJLATZSN4LbASLLEAD0NVWLEPD0OwAWFCsA8rWbAAQ7ACJUKxDAIlQrENAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAOKiEjsAFhIIojYbAOKiEbsQEAQ2CwAiVCsAIlYbAOKiFZsAxDR7ANQ0dgsAJiILAAUFiwQGBZZrABYyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsQAAEyNEsAFDsAA+sgEBAUNgQi2wEywAsQACRVRYsA8jQiBFsAsjQrAKI7AAYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wFCyxABMrLbAVLLEBEystsBYssQITKy2wFyyxAxMrLbAYLLEEEystsBkssQUTKy2wGiyxBhMrLbAbLLEHEystsBwssQgTKy2wHSyxCRMrLbAeLACwDSuxAAJFVFiwDyNCIEWwCyNCsAojsABgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAfLLEAHistsCAssQEeKy2wISyxAh4rLbAiLLEDHistsCMssQQeKy2wJCyxBR4rLbAlLLEGHistsCYssQceKy2wJyyxCB4rLbAoLLEJHistsCksIDywAWAtsCosIGCwEGAgQyOwAWBDsAIlYbABYLApKiEtsCsssCorsCoqLbAsLCAgRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOCMgilVYIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgbIVktsC0sALEAAkVUWLABFrAsKrABFTAbIlktsC4sALANK7EAAkVUWLABFrAsKrABFTAbIlktsC8sIDWwAWAtsDAsALABRWO4BABiILAAUFiwQGBZZrABY7ABK7ALQ2O4BABiILAAUFiwQGBZZrABY7ABK7AAFrQAAAAAAEQ+IzixLwEVKi2wMSwgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhOC2wMiwuFzwtsDMsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYbABQ2M4LbA0LLECABYlIC4gR7AAI0KwAiVJiopHI0cjYSBYYhshWbABI0KyMwEBFRQqLbA1LLAAFrAEJbAEJUcjRyNhsAlDK2WKLiMgIDyKOC2wNiywABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyCwCEMgiiNHI0cjYSNGYLAEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsAJiILAAUFiwQGBZZrABY2AjILABKyOwBENgsAErsAUlYbAFJbACYiCwAFBYsEBgWWawAWOwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbA3LLAAFiAgILAFJiAuRyNHI2EjPDgtsDgssAAWILAII0IgICBGI0ewASsjYTgtsDkssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbkIAAgAY2MjIFhiGyFZY7gEAGIgsABQWLBAYFlmsAFjYCMuIyAgPIo4IyFZLbA6LLAAFiCwCEMgLkcjRyNhIGCwIGBmsAJiILAAUFiwQGBZZrABYyMgIDyKOC2wOywjIC5GsAIlRlJYIDxZLrErARQrLbA8LCMgLkawAiVGUFggPFkusSsBFCstsD0sIyAuRrACJUZSWCA8WSMgLkawAiVGUFggPFkusSsBFCstsD4ssDUrIyAuRrACJUZSWCA8WS6xKwEUKy2wPyywNiuKICA8sAQjQoo4IyAuRrACJUZSWCA8WS6xKwEUK7AEQy6wKystsEAssAAWsAQlsAQmIC5HI0cjYbAJQysjIDwgLiM4sSsBFCstsEEssQgEJUKwABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyBHsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhsAIlRmE4IyA8IzgbISAgRiNHsAErI2E4IVmxKwEUKy2wQiywNSsusSsBFCstsEMssDYrISMgIDywBCNCIzixKwEUK7AEQy6wKystsEQssAAVIEewACNCsgABARUUEy6wMSotsEUssAAVIEewACNCsgABARUUEy6wMSotsEYssQABFBOwMiotsEcssDQqLbBILLAAFkUjIC4gRoojYTixKwEUKy2wSSywCCNCsEgrLbBKLLIAAEErLbBLLLIAAUErLbBMLLIBAEErLbBNLLIBAUErLbBOLLIAAEIrLbBPLLIAAUIrLbBQLLIBAEIrLbBRLLIBAUIrLbBSLLIAAD4rLbBTLLIAAT4rLbBULLIBAD4rLbBVLLIBAT4rLbBWLLIAAEArLbBXLLIAAUArLbBYLLIBAEArLbBZLLIBAUArLbBaLLIAAEMrLbBbLLIAAUMrLbBcLLIBAEMrLbBdLLIBAUMrLbBeLLIAAD8rLbBfLLIAAT8rLbBgLLIBAD8rLbBhLLIBAT8rLbBiLLA3Ky6xKwEUKy2wYyywNyuwOystsGQssDcrsDwrLbBlLLAAFrA3K7A9Ky2wZiywOCsusSsBFCstsGcssDgrsDsrLbBoLLA4K7A8Ky2waSywOCuwPSstsGossDkrLrErARQrLbBrLLA5K7A7Ky2wbCywOSuwPCstsG0ssDkrsD0rLbBuLLA6Ky6xKwEUKy2wbyywOiuwOystsHAssDorsDwrLbBxLLA6K7A9Ky2wciyzCQQCA0VYIRsjIVlCK7AIZbADJFB4sAEVMC0AS7gAyFJYsQEBjlmwAbkIAAgAY3CxAAVCsQAAKrEABUKxAAgqsQAFQrEACCqxAAVCuQAAAAkqsQAFQrkAAAAJKrEDAESxJAGIUViwQIhYsQNkRLEmAYhRWLoIgAABBECIY1RYsQMARFlZWVmxAAwquAH/hbAEjbECAEQA') format('truetype');\n}\n/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */\n/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */\n/*\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n  @font-face {\n    font-family: 'fontello';\n    src: url('../font/fontello.svg?90323576#fontello') format('svg');\n  }\n}\n*/\n \n [class^=\"icon-\"]:before, [class*=\" icon-\"]:before {\n  font-family: \"fontello\";\n  font-style: normal;\n  font-weight: normal;\n  speak: none;\n \n  display: inline-block;\n  text-decoration: inherit;\n  width: 1em;\n  margin-right: .2em;\n  text-align: center;\n  /* opacity: .8; */\n \n  /* For safety - reset parent styles, that can break glyph codes*/\n  font-variant: normal;\n  text-transform: none;\n     \n  /* fix buttons height, for twitter bootstrap */\n  line-height: 1em;\n \n  /* Animation center compensation - margins should be symmetric */\n  /* remove if not needed */\n  margin-left: .2em;\n \n  /* you can be more comfortable with increased icons size */\n  /* font-size: 120%; */\n \n  /* Uncomment for 3D effect */\n  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */\n}\n.icon-search:before { content: '\\e800'; } /* '' */\n.icon-arrows:before { content: '\\e801'; } /* '' */\n.icon-star-empty:before { content: '\\e802'; } /* '' */\n.icon-favorite:before { content: '\\e803'; } /* '' */\n.icon-align-justify:before { content: '\\e804'; } /* '' */\n.icon-trashed:before { content: '\\e805'; } /* '' */\n.icon-cog:before { content: '\\e806'; } /* '' */\n.icon-cloud:before { content: '\\e807'; } /* '' */\n.icon-notebook:before { content: '\\e808'; } /* '' */\n.icon-bold:before { content: '\\e809'; } /* '' */\n.icon-italic:before { content: '\\e80a'; } /* '' */\n.icon-globe:before { content: '\\e80b'; } /* '' */\n.icon-indent:before { content: '\\e80c'; } /* '' */\n.icon-code:before { content: '\\e80d'; } /* '' */\n.icon-picture:before { content: '\\e80e'; } /* '' */\n.icon-list-numbered:before { content: '\\e80f'; } /* '' */\n.icon-list-bullet:before { content: '\\e810'; } /* '' */\n.icon-font:before { content: '\\e811'; } /* '' */\n.icon-minus:before { content: '\\e812'; } /* '' */\n.icon-reply:before { content: '\\e813'; } /* '' */\n.icon-share:before { content: '\\e814'; } /* '' */\n.icon-save:before { content: '\\e815'; } /* '' */\n.icon-fullscreen:before { content: '\\e816'; } /* '' */\n.icon-eye:before { content: '\\e817'; } /* '' */\n.icon-undo:before { content: '\\e818'; } /* '' */\n.icon-squares:before { content: '\\e819'; } /* '' */\n.icon-pencil:before { content: '\\e81a'; } /* '' */\n.icon-tag:before { content: '\\e81b'; } /* '' */\n.icon-note:before { content: '\\e81c'; } /* '' */\n.icon-lock:before { content: '\\e81d'; } /* '' */\n.icon-lock-open:before { content: '\\e81e'; } /* '' */\n.icon-lock-open-alt:before { content: '\\e81f'; } /* '' */\n.icon-block:before { content: '\\e820'; } /* '' */\n.icon-github-circled:before { content: '\\e821'; } /* '' */\n.icon-key:before { content: '\\e822'; } /* '' */\n.icon-ok:before { content: '\\e823'; } /* '' */\n.icon-ok-squared:before { content: '\\e824'; } /* '' */\n.icon-check:before { content: '\\e825'; } /* '' */\n.icon-check-empty:before { content: '\\e826'; } /* '' */\n.icon-help-circled:before { content: '\\e827'; } /* '' */\n.icon-doc-new:before { content: '\\e828'; } /* '' */\n.icon-left-open-big:before { content: '\\e829'; } /* '' */\n.icon-right-open-big:before { content: '\\e82a'; } /* '' */\n.icon-menu:before { content: '\\e82b'; } /* '' */\n.icon-left-open:before { content: '\\e82c'; } /* '' */\n.icon-right-open:before { content: '\\e82d'; } /* '' */"
  },
  {
    "path": "app/styles/core/fontello/css/fontello-ie7-codes.less",
    "content": "\n.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }\n.icon-arrows { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }\n.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }\n.icon-favorite { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }\n.icon-align-justify { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }\n.icon-trashed { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }\n.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }\n.icon-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }\n.icon-notebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }\n.icon-bold { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }\n.icon-italic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }\n.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }\n.icon-indent { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }\n.icon-code { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }\n.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }\n.icon-list-numbered { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }\n.icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }\n.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }\n.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }\n.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }\n.icon-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); }\n.icon-save { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); }\n.icon-fullscreen { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }\n.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }\n.icon-undo { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }\n.icon-squares { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }\n.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }\n.icon-tag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }\n.icon-note { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }\n.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81d;&nbsp;'); }\n.icon-lock-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81e;&nbsp;'); }\n.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81f;&nbsp;'); }\n.icon-block { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe820;&nbsp;'); }\n.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe821;&nbsp;'); }\n.icon-key { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe822;&nbsp;'); }\n.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe823;&nbsp;'); }\n.icon-ok-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe824;&nbsp;'); }\n.icon-check { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe825;&nbsp;'); }\n.icon-check-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe826;&nbsp;'); }\n.icon-help-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe827;&nbsp;'); }\n.icon-doc-new { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe828;&nbsp;'); }\n.icon-left-open-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe829;&nbsp;'); }\n.icon-right-open-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82a;&nbsp;'); }\n.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82b;&nbsp;'); }\n.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82c;&nbsp;'); }\n.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82d;&nbsp;'); }"
  },
  {
    "path": "app/styles/core/fontello/css/fontello-ie7.less",
    "content": "[class^=\"icon-\"], [class*=\" icon-\"] {\n  font-family: 'fontello';\n  font-style: normal;\n  font-weight: normal;\n \n  /* fix buttons height */\n  line-height: 1em;\n \n  /* you can be more comfortable with increased icons size */\n  /* font-size: 120%; */\n}\n \n.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }\n.icon-arrows { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }\n.icon-star-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }\n.icon-favorite { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }\n.icon-align-justify { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }\n.icon-trashed { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }\n.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }\n.icon-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }\n.icon-notebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }\n.icon-bold { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }\n.icon-italic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }\n.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }\n.icon-indent { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }\n.icon-code { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }\n.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }\n.icon-list-numbered { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }\n.icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }\n.icon-font { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }\n.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }\n.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }\n.icon-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); }\n.icon-save { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); }\n.icon-fullscreen { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }\n.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }\n.icon-undo { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }\n.icon-squares { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }\n.icon-pencil { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }\n.icon-tag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }\n.icon-note { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }\n.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81d;&nbsp;'); }\n.icon-lock-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81e;&nbsp;'); }\n.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81f;&nbsp;'); }\n.icon-block { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe820;&nbsp;'); }\n.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe821;&nbsp;'); }\n.icon-key { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe822;&nbsp;'); }\n.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe823;&nbsp;'); }\n.icon-ok-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe824;&nbsp;'); }\n.icon-check { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe825;&nbsp;'); }\n.icon-check-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe826;&nbsp;'); }\n.icon-help-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe827;&nbsp;'); }\n.icon-doc-new { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe828;&nbsp;'); }\n.icon-left-open-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe829;&nbsp;'); }\n.icon-right-open-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82a;&nbsp;'); }\n.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82b;&nbsp;'); }\n.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82c;&nbsp;'); }\n.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82d;&nbsp;'); }"
  },
  {
    "path": "app/styles/core/fontello/css/fontello.less",
    "content": "@font-face {\n  font-family: 'fontello';\n  src: url('core/fontello/font/fontello.eot?9108779');\n  src: url('core/fontello/font/fontello.eot?9108779#iefix') format('embedded-opentype'),\n       url('core/fontello/font/fontello.woff?9108779') format('woff'),\n       url('core/fontello/font/fontello.ttf?9108779') format('truetype'),\n       url('core/fontello/font/fontello.svg?9108779#fontello') format('svg');\n  font-weight: normal;\n  font-style: normal;\n}\n/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */\n/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */\n/*\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n  @font-face {\n    font-family: 'fontello';\n    src: url('../font/fontello.svg?9108779#fontello') format('svg');\n  }\n}\n*/\n\n [class^=\"icon-\"]:before, [class*=\" icon-\"]:before {\n  font-family: \"fontello\";\n  font-style: normal;\n  font-weight: normal;\n  speak: none;\n\n  display: inline-block;\n  text-decoration: inherit;\n  width: 1em;\n  margin-right: .2em;\n  text-align: center;\n  /* opacity: .8; */\n\n  /* For safety - reset parent styles, that can break glyph codes*/\n  font-variant: normal;\n  text-transform: none;\n\n  /* fix buttons height, for twitter bootstrap */\n  line-height: 1em;\n\n  /* Animation center compensation - margins should be symmetric */\n  /* remove if not needed */\n  margin-left: .2em;\n\n  /* you can be more comfortable with increased icons size */\n  /* font-size: 120%; */\n\n  /* Font smoothing. That was taken from TWBS */\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n\n  /* Uncomment for 3D effect */\n  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */\n}\n\n.icon-search:before { content: '\\e800'; } /* '' */\n.icon-arrows:before { content: '\\e801'; } /* '' */\n.icon-star-empty:before { content: '\\e802'; } /* '' */\n.icon-favorite:before { content: '\\e803'; } /* '' */\n.icon-align-justify:before { content: '\\e804'; } /* '' */\n.icon-trashed:before { content: '\\e805'; } /* '' */\n.icon-cog:before { content: '\\e806'; } /* '' */\n.icon-cloud:before { content: '\\e807'; } /* '' */\n.icon-notebook:before { content: '\\e808'; } /* '' */\n.icon-bold:before { content: '\\e809'; } /* '' */\n.icon-italic:before { content: '\\e80a'; } /* '' */\n.icon-globe:before { content: '\\e80b'; } /* '' */\n.icon-indent:before { content: '\\e80c'; } /* '' */\n.icon-code:before { content: '\\e80d'; } /* '' */\n.icon-picture:before { content: '\\e80e'; } /* '' */\n.icon-list-numbered:before { content: '\\e80f'; } /* '' */\n.icon-list-bullet:before { content: '\\e810'; } /* '' */\n.icon-font:before { content: '\\e811'; } /* '' */\n.icon-minus:before { content: '\\e812'; } /* '' */\n.icon-reply:before { content: '\\e813'; } /* '' */\n.icon-share:before { content: '\\e814'; } /* '' */\n.icon-save:before { content: '\\e815'; } /* '' */\n.icon-fullscreen:before { content: '\\e816'; } /* '' */\n.icon-eye:before { content: '\\e817'; } /* '' */\n.icon-undo:before { content: '\\e818'; } /* '' */\n.icon-squares:before { content: '\\e819'; } /* '' */\n.icon-pencil:before { content: '\\e81a'; } /* '' */\n.icon-tag:before { content: '\\e81b'; } /* '' */\n.icon-note:before { content: '\\e81c'; } /* '' */\n.icon-lock:before { content: '\\e81d'; } /* '' */\n.icon-lock-open:before { content: '\\e81e'; } /* '' */\n.icon-lock-open-alt:before { content: '\\e81f'; } /* '' */\n.icon-block:before { content: '\\e820'; } /* '' */\n.icon-github-circled:before { content: '\\e821'; } /* '' */\n.icon-key:before { content: '\\e822'; } /* '' */\n.icon-ok:before { content: '\\e823'; } /* '' */\n.icon-ok-squared:before { content: '\\e824'; } /* '' */\n.icon-check:before { content: '\\e825'; } /* '' */\n.icon-check-empty:before { content: '\\e826'; } /* '' */\n.icon-help-circled:before { content: '\\e827'; } /* '' */\n.icon-doc-new:before { content: '\\e828'; } /* '' */\n.icon-left-open-big:before { content: '\\e829'; } /* '' */\n.icon-right-open-big:before { content: '\\e82a'; } /* '' */\n.icon-menu:before { content: '\\e82b'; } /* '' */\n.icon-left-open:before { content: '\\e82c'; } /* '' */\n.icon-right-open:before { content: '\\e82d'; } /* '' */\n"
  },
  {
    "path": "app/styles/core/fontello/demo.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head><!--[if lt IE 9]><script language=\"javascript\" type=\"text/javascript\" src=\"//html5shim.googlecode.com/svn/trunk/html5.js\"></script><![endif]-->\n    <meta charset=\"UTF-8\"><style>/*\n * Bootstrap v2.2.1\n *\n * Copyright 2012 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n.clearfix {\n  *zoom: 1;\n}\n.clearfix:before,\n.clearfix:after {\n  display: table;\n  content: \"\";\n  line-height: 0;\n}\n.clearfix:after {\n  clear: both;\n}\nhtml {\n  font-size: 100%;\n  -webkit-text-size-adjust: 100%;\n  -ms-text-size-adjust: 100%;\n}\na:focus {\n  outline: thin dotted #333;\n  outline: 5px auto -webkit-focus-ring-color;\n  outline-offset: -2px;\n}\na:hover,\na:active {\n  outline: 0;\n}\nbutton,\ninput,\nselect,\ntextarea {\n  margin: 0;\n  font-size: 100%;\n  vertical-align: middle;\n}\nbutton,\ninput {\n  *overflow: visible;\n  line-height: normal;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n  padding: 0;\n  border: 0;\n}\nbody {\n  margin: 0;\n  font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  font-size: 14px;\n  line-height: 20px;\n  color: #333;\n  background-color: #fff;\n}\na {\n  color: #08c;\n  text-decoration: none;\n}\na:hover {\n  color: #005580;\n  text-decoration: underline;\n}\n.row {\n  margin-left: -20px;\n  *zoom: 1;\n}\n.row:before,\n.row:after {\n  display: table;\n  content: \"\";\n  line-height: 0;\n}\n.row:after {\n  clear: both;\n}\n[class*=\"span\"] {\n  float: left;\n  min-height: 1px;\n  margin-left: 20px;\n}\n.container,\n.navbar-static-top .container,\n.navbar-fixed-top .container,\n.navbar-fixed-bottom .container {\n  width: 940px;\n}\n.span12 {\n  width: 940px;\n}\n.span11 {\n  width: 860px;\n}\n.span10 {\n  width: 780px;\n}\n.span9 {\n  width: 700px;\n}\n.span8 {\n  width: 620px;\n}\n.span7 {\n  width: 540px;\n}\n.span6 {\n  width: 460px;\n}\n.span5 {\n  width: 380px;\n}\n.span4 {\n  width: 300px;\n}\n.span3 {\n  width: 220px;\n}\n.span2 {\n  width: 140px;\n}\n.span1 {\n  width: 60px;\n}\n[class*=\"span\"].pull-right,\n.row-fluid [class*=\"span\"].pull-right {\n  float: right;\n}\n.container {\n  margin-right: auto;\n  margin-left: auto;\n  *zoom: 1;\n}\n.container:before,\n.container:after {\n  display: table;\n  content: \"\";\n  line-height: 0;\n}\n.container:after {\n  clear: both;\n}\np {\n  margin: 0 0 10px;\n}\n.lead {\n  margin-bottom: 20px;\n  font-size: 21px;\n  font-weight: 200;\n  line-height: 30px;\n}\nsmall {\n  font-size: 85%;\n}\nh1 {\n  margin: 10px 0;\n  font-family: inherit;\n  font-weight: bold;\n  line-height: 20px;\n  color: inherit;\n  text-rendering: optimizelegibility;\n}\nh1 small {\n  font-weight: normal;\n  line-height: 1;\n  color: #999;\n}\nh1 {\n  line-height: 40px;\n}\nh1 {\n  font-size: 38.5px;\n}\nh1 small {\n  font-size: 24.5px;\n}\nbody {\n  margin-top: 90px;\n}\n.header {\n  position: fixed;\n  top: 0;\n  left: 50%;\n  margin-left: -480px;\n  background-color: #fff;\n  border-bottom: 1px solid #ddd;\n  padding-top: 10px;\n  z-index: 10;\n}\n.footer {\n  color: #ddd;\n  font-size: 12px;\n  text-align: center;\n  margin-top: 20px;\n}\n.footer a {\n  color: #ccc;\n  text-decoration: underline;\n}\n.the-icons {\n  font-size: 14px;\n  line-height: 24px;\n}\n.switch {\n  position: absolute;\n  right: 0;\n  bottom: 10px;\n  color: #666;\n}\n.switch input {\n  margin-right: 0.3em;\n}\n.codesOn .i-name {\n  display: none;\n}\n.codesOn .i-code {\n  display: inline;\n}\n.i-code {\n  display: none;\n}\n@font-face {\n      font-family: 'fontello';\n      src: url('./font/fontello.eot?7834516');\n      src: url('./font/fontello.eot?7834516#iefix') format('embedded-opentype'),\n           url('./font/fontello.woff?7834516') format('woff'),\n           url('./font/fontello.ttf?7834516') format('truetype'),\n           url('./font/fontello.svg?7834516#fontello') format('svg');\n      font-weight: normal;\n      font-style: normal;\n    }\n     \n     \n    .demo-icon\n    {\n      font-family: \"fontello\";\n      font-style: normal;\n      font-weight: normal;\n      speak: none;\n     \n      display: inline-block;\n      text-decoration: inherit;\n      width: 1em;\n      margin-right: .2em;\n      text-align: center;\n      /* opacity: .8; */\n     \n      /* For safety - reset parent styles, that can break glyph codes*/\n      font-variant: normal;\n      text-transform: none;\n     \n      /* fix buttons height, for twitter bootstrap */\n      line-height: 1em;\n     \n      /* Animation center compensation - margins should be symmetric */\n      /* remove if not needed */\n      margin-left: .2em;\n     \n      /* You can be more comfortable with increased icons size */\n      /* font-size: 120%; */\n     \n      /* Font smoothing. That was taken from TWBS */\n      -webkit-font-smoothing: antialiased;\n      -moz-osx-font-smoothing: grayscale;\n     \n      /* Uncomment for 3D effect */\n      /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */\n    }\n     </style>\n    <link rel=\"stylesheet\" href=\"css/animation.css\"><!--[if IE 7]><link rel=\"stylesheet\" href=\"css/fontello-ie7.css\"><![endif]-->\n    <script>\n      function toggleCodes(on) {\n        var obj = document.getElementById('icons');\n        \n        if (on) {\n          obj.className += ' codesOn';\n        } else {\n          obj.className = obj.className.replace(' codesOn', '');\n        }\n      }\n      \n    </script>\n  </head>\n  <body>\n    <div class=\"container header\">\n      <h1>\n        fontello\n         <small>font demo</small>\n      </h1>\n      <label class=\"switch\">\n        <input type=\"checkbox\" onclick=\"toggleCodes(this.checked)\">show codes\n      </label>\n    </div>\n    <div id=\"icons\" class=\"container\">\n      <div class=\"row\">\n        <div title=\"Code: 0xe800\" class=\"the-icons span3\"><i class=\"demo-icon icon-search\">&#xe800;</i> <span class=\"i-name\">icon-search</span><span class=\"i-code\">0xe800</span></div>\n        <div title=\"Code: 0xe801\" class=\"the-icons span3\"><i class=\"demo-icon icon-arrows\">&#xe801;</i> <span class=\"i-name\">icon-arrows</span><span class=\"i-code\">0xe801</span></div>\n        <div title=\"Code: 0xe802\" class=\"the-icons span3\"><i class=\"demo-icon icon-star-empty\">&#xe802;</i> <span class=\"i-name\">icon-star-empty</span><span class=\"i-code\">0xe802</span></div>\n        <div title=\"Code: 0xe803\" class=\"the-icons span3\"><i class=\"demo-icon icon-favorite\">&#xe803;</i> <span class=\"i-name\">icon-favorite</span><span class=\"i-code\">0xe803</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe804\" class=\"the-icons span3\"><i class=\"demo-icon icon-align-justify\">&#xe804;</i> <span class=\"i-name\">icon-align-justify</span><span class=\"i-code\">0xe804</span></div>\n        <div title=\"Code: 0xe805\" class=\"the-icons span3\"><i class=\"demo-icon icon-trashed\">&#xe805;</i> <span class=\"i-name\">icon-trashed</span><span class=\"i-code\">0xe805</span></div>\n        <div title=\"Code: 0xe806\" class=\"the-icons span3\"><i class=\"demo-icon icon-cog\">&#xe806;</i> <span class=\"i-name\">icon-cog</span><span class=\"i-code\">0xe806</span></div>\n        <div title=\"Code: 0xe807\" class=\"the-icons span3\"><i class=\"demo-icon icon-cloud\">&#xe807;</i> <span class=\"i-name\">icon-cloud</span><span class=\"i-code\">0xe807</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe808\" class=\"the-icons span3\"><i class=\"demo-icon icon-notebook\">&#xe808;</i> <span class=\"i-name\">icon-notebook</span><span class=\"i-code\">0xe808</span></div>\n        <div title=\"Code: 0xe809\" class=\"the-icons span3\"><i class=\"demo-icon icon-bold\">&#xe809;</i> <span class=\"i-name\">icon-bold</span><span class=\"i-code\">0xe809</span></div>\n        <div title=\"Code: 0xe80a\" class=\"the-icons span3\"><i class=\"demo-icon icon-italic\">&#xe80a;</i> <span class=\"i-name\">icon-italic</span><span class=\"i-code\">0xe80a</span></div>\n        <div title=\"Code: 0xe80b\" class=\"the-icons span3\"><i class=\"demo-icon icon-globe\">&#xe80b;</i> <span class=\"i-name\">icon-globe</span><span class=\"i-code\">0xe80b</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe80c\" class=\"the-icons span3\"><i class=\"demo-icon icon-indent\">&#xe80c;</i> <span class=\"i-name\">icon-indent</span><span class=\"i-code\">0xe80c</span></div>\n        <div title=\"Code: 0xe80d\" class=\"the-icons span3\"><i class=\"demo-icon icon-code\">&#xe80d;</i> <span class=\"i-name\">icon-code</span><span class=\"i-code\">0xe80d</span></div>\n        <div title=\"Code: 0xe80e\" class=\"the-icons span3\"><i class=\"demo-icon icon-picture\">&#xe80e;</i> <span class=\"i-name\">icon-picture</span><span class=\"i-code\">0xe80e</span></div>\n        <div title=\"Code: 0xe80f\" class=\"the-icons span3\"><i class=\"demo-icon icon-list-numbered\">&#xe80f;</i> <span class=\"i-name\">icon-list-numbered</span><span class=\"i-code\">0xe80f</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe810\" class=\"the-icons span3\"><i class=\"demo-icon icon-list-bullet\">&#xe810;</i> <span class=\"i-name\">icon-list-bullet</span><span class=\"i-code\">0xe810</span></div>\n        <div title=\"Code: 0xe811\" class=\"the-icons span3\"><i class=\"demo-icon icon-font\">&#xe811;</i> <span class=\"i-name\">icon-font</span><span class=\"i-code\">0xe811</span></div>\n        <div title=\"Code: 0xe812\" class=\"the-icons span3\"><i class=\"demo-icon icon-minus\">&#xe812;</i> <span class=\"i-name\">icon-minus</span><span class=\"i-code\">0xe812</span></div>\n        <div title=\"Code: 0xe813\" class=\"the-icons span3\"><i class=\"demo-icon icon-reply\">&#xe813;</i> <span class=\"i-name\">icon-reply</span><span class=\"i-code\">0xe813</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe814\" class=\"the-icons span3\"><i class=\"demo-icon icon-share\">&#xe814;</i> <span class=\"i-name\">icon-share</span><span class=\"i-code\">0xe814</span></div>\n        <div title=\"Code: 0xe815\" class=\"the-icons span3\"><i class=\"demo-icon icon-save\">&#xe815;</i> <span class=\"i-name\">icon-save</span><span class=\"i-code\">0xe815</span></div>\n        <div title=\"Code: 0xe816\" class=\"the-icons span3\"><i class=\"demo-icon icon-fullscreen\">&#xe816;</i> <span class=\"i-name\">icon-fullscreen</span><span class=\"i-code\">0xe816</span></div>\n        <div title=\"Code: 0xe817\" class=\"the-icons span3\"><i class=\"demo-icon icon-eye\">&#xe817;</i> <span class=\"i-name\">icon-eye</span><span class=\"i-code\">0xe817</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe818\" class=\"the-icons span3\"><i class=\"demo-icon icon-undo\">&#xe818;</i> <span class=\"i-name\">icon-undo</span><span class=\"i-code\">0xe818</span></div>\n        <div title=\"Code: 0xe819\" class=\"the-icons span3\"><i class=\"demo-icon icon-squares\">&#xe819;</i> <span class=\"i-name\">icon-squares</span><span class=\"i-code\">0xe819</span></div>\n        <div title=\"Code: 0xe81a\" class=\"the-icons span3\"><i class=\"demo-icon icon-pencil\">&#xe81a;</i> <span class=\"i-name\">icon-pencil</span><span class=\"i-code\">0xe81a</span></div>\n        <div title=\"Code: 0xe81b\" class=\"the-icons span3\"><i class=\"demo-icon icon-tag\">&#xe81b;</i> <span class=\"i-name\">icon-tag</span><span class=\"i-code\">0xe81b</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe81c\" class=\"the-icons span3\"><i class=\"demo-icon icon-note\">&#xe81c;</i> <span class=\"i-name\">icon-note</span><span class=\"i-code\">0xe81c</span></div>\n        <div title=\"Code: 0xe81d\" class=\"the-icons span3\"><i class=\"demo-icon icon-lock\">&#xe81d;</i> <span class=\"i-name\">icon-lock</span><span class=\"i-code\">0xe81d</span></div>\n        <div title=\"Code: 0xe81e\" class=\"the-icons span3\"><i class=\"demo-icon icon-lock-open\">&#xe81e;</i> <span class=\"i-name\">icon-lock-open</span><span class=\"i-code\">0xe81e</span></div>\n        <div title=\"Code: 0xe81f\" class=\"the-icons span3\"><i class=\"demo-icon icon-lock-open-alt\">&#xe81f;</i> <span class=\"i-name\">icon-lock-open-alt</span><span class=\"i-code\">0xe81f</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe820\" class=\"the-icons span3\"><i class=\"demo-icon icon-block\">&#xe820;</i> <span class=\"i-name\">icon-block</span><span class=\"i-code\">0xe820</span></div>\n        <div title=\"Code: 0xe821\" class=\"the-icons span3\"><i class=\"demo-icon icon-github-circled\">&#xe821;</i> <span class=\"i-name\">icon-github-circled</span><span class=\"i-code\">0xe821</span></div>\n        <div title=\"Code: 0xe822\" class=\"the-icons span3\"><i class=\"demo-icon icon-key\">&#xe822;</i> <span class=\"i-name\">icon-key</span><span class=\"i-code\">0xe822</span></div>\n        <div title=\"Code: 0xe823\" class=\"the-icons span3\"><i class=\"demo-icon icon-ok\">&#xe823;</i> <span class=\"i-name\">icon-ok</span><span class=\"i-code\">0xe823</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe824\" class=\"the-icons span3\"><i class=\"demo-icon icon-ok-squared\">&#xe824;</i> <span class=\"i-name\">icon-ok-squared</span><span class=\"i-code\">0xe824</span></div>\n        <div title=\"Code: 0xe825\" class=\"the-icons span3\"><i class=\"demo-icon icon-check\">&#xe825;</i> <span class=\"i-name\">icon-check</span><span class=\"i-code\">0xe825</span></div>\n        <div title=\"Code: 0xe826\" class=\"the-icons span3\"><i class=\"demo-icon icon-check-empty\">&#xe826;</i> <span class=\"i-name\">icon-check-empty</span><span class=\"i-code\">0xe826</span></div>\n        <div title=\"Code: 0xe827\" class=\"the-icons span3\"><i class=\"demo-icon icon-help-circled\">&#xe827;</i> <span class=\"i-name\">icon-help-circled</span><span class=\"i-code\">0xe827</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe828\" class=\"the-icons span3\"><i class=\"demo-icon icon-doc-new\">&#xe828;</i> <span class=\"i-name\">icon-doc-new</span><span class=\"i-code\">0xe828</span></div>\n        <div title=\"Code: 0xe829\" class=\"the-icons span3\"><i class=\"demo-icon icon-left-open-big\">&#xe829;</i> <span class=\"i-name\">icon-left-open-big</span><span class=\"i-code\">0xe829</span></div>\n        <div title=\"Code: 0xe82a\" class=\"the-icons span3\"><i class=\"demo-icon icon-right-open-big\">&#xe82a;</i> <span class=\"i-name\">icon-right-open-big</span><span class=\"i-code\">0xe82a</span></div>\n        <div title=\"Code: 0xe82b\" class=\"the-icons span3\"><i class=\"demo-icon icon-menu\">&#xe82b;</i> <span class=\"i-name\">icon-menu</span><span class=\"i-code\">0xe82b</span></div>\n      </div>\n      <div class=\"row\">\n        <div title=\"Code: 0xe82c\" class=\"the-icons span3\"><i class=\"demo-icon icon-left-open\">&#xe82c;</i> <span class=\"i-name\">icon-left-open</span><span class=\"i-code\">0xe82c</span></div>\n        <div title=\"Code: 0xe82d\" class=\"the-icons span3\"><i class=\"demo-icon icon-right-open\">&#xe82d;</i> <span class=\"i-name\">icon-right-open</span><span class=\"i-code\">0xe82d</span></div>\n      </div>\n    </div>\n    <div class=\"container footer\">Generated by <a href=\"http://fontello.com\">fontello.com</a></div>\n  </body>\n</html>"
  },
  {
    "path": "app/styles/core/fontello.less",
    "content": "/**\n * Fontello\n */\n@import 'fontello/css/fontello.less';\n@import 'fontello/css/fontello-codes.less';\n// @import 'fontello/css/fontello-ie7-codes.less';\n@import 'fontello/css/fontello-embedded.less';\n// @import 'fontello/css/fontello-ie7.less';\n@import 'fontello/css/animation.less';\n\n.icons {\n    font-family: \"fontello\";\n}\n.icon--block {\n    display: block;\n}\n"
  },
  {
    "path": "app/styles/core/fuzzy.less",
    "content": "/**\n * Fuzzy search\n */\n// Hide other content in sidebar while Fuzzy search is active\n.-fuzzy #sidebar--content {\n    display: none;\n}\n"
  },
  {
    "path": "app/styles/core/header.less",
    "content": "/**\n * Navbar styles\n */\n.header {\n}\n\n/**\n * \"Grid\" system for elements in the navbar\n */\n.header--col--left {\n    max-width: 70%;\n\n    .header--col--left--drawer {\n      max-width: 85%;\n    }\n\n    .header--col--left--search {\n      max-width: 15%;\n    }\n}\n\n/**\n * Fix margins and paddings of .navbar-right or .navbar-left\n */\n.header--right {\n    margin-right : 2px;\n    margin-left  : auto;\n}\n.header--left {\n    padding-left: 0;\n}\n\n/**\n * Button styles\n */\n.header--btn {\n    &:extend(.navbar-btn);\n    margin-left: 5px;\n}\n// Search button\n.header--sbtn {\n    background-color: transparent;\n}\n\n/**\n * Search form\n */\n.header--search {\n    &:extend(.navbar-form);\n    display : none;\n    width   : 100%;\n}\n.header--search--input {\n}\n\n/**\n * Hide everything else in the navbar and\n * show the search form.\n */\n.header.-search {\n    .navbar-right,\n    .navbar-left {\n        display: none;\n    }\n\n    .header--search {\n        display: block;\n    }\n}\n\n/**\n * Settings navbar\n */\n.header--settings {\n    .container-fluid  {\n        padding-left: 0;\n    }\n}\n\n.header--container {\n    display: flex;\n}\n\n/**\n * Navbar title\n */\n.header--title {\n    font-size: 18px;\n    margin-left: 0;\n    width: 100%;\n}\n\n#header--title {\n    margin-left: 15px;\n}\n"
  },
  {
    "path": "app/styles/core/layout.less",
    "content": "/**\n * Layout module\n */\n\n/**\n * Grids\n */\n.make-cols(@lg, @md, @sm, @xs) {\n    .make-lg-column(@lg);\n    .make-md-column(@md);\n    .make-sm-column(@sm);\n    .make-xs-column(@xs);\n    transition: left 0.3s ease;\n}\n.layout--sidebar {\n    .make-cols(3, 4, 4.2, 12);\n}\n.layout--content {\n    .make-cols(9, 8, 7.8, 12);\n}\n\n.layout {\n}\n\n.layout--row {\n    overflow : hidden;\n    position : absolute;\n    top      : 0;\n    bottom   : 0;\n    padding  : 0;\n    height   : 100%;\n}\n\n// Sidebar\n.layout--sidebar {\n    &:extend(.layout--row);\n    left: 0;\n    border-right: 1px solid darken(#FFF, 10%);\n}\n\n// Content\n.layout--content {\n    &:extend(.layout--row);\n    right  : 0;\n    height : 100%;\n}\n\n.layout--navbar {\n}\n\n.layout--body {\n    height     : 100%;\n    width      : 100%;\n    position   : relative;\n\n    &.-scroll {\n        height   : auto;\n        position : absolute;\n        bottom   : 0;\n        top      : @navbar-height;\n    }\n}\n\n// Brand layout\n.layout--brand {\n    &:extend(.layout--row);\n    overflow-y : auto;\n    z-index    : @zindex-navbar-fixed;\n    width      : 100%;\n    display    : none;\n}\n\n// Layout Backdrop\n.layout--backdrop {\n    display    : none;\n    background : rgba(0,0,0,0.4);\n\n    // Position and z-index\n    position : absolute;\n    top      : 0;\n    bottom   : 0;\n    left     : 0;\n    right    : 0;\n    z-index  : @zindex--backdrop;\n\n    // Transition\n    opacity    : 0;\n    transition : opacity 0.3s ease;\n\n    &.-show {\n        display: block;\n        opacity: 1;\n    }\n}\n\n// Loading\n.layout--brand.-loading {\n    display         : flex;\n    align-items     : center;\n    text-align      : center;\n    justify-content : center;\n    z-index         : @zindex-navbar-fixed + 3;\n}\n\n.container.-backup {\n    max-width      : 400px;\n    padding-top    : 10%;\n    padding-bottom : 20px;\n}\n\n// Notes\n.note--date {\n    display: inline-block;\n}\n"
  },
  {
    "path": "app/styles/core/list.less",
    "content": "/**\n * List module\n */\n.list {\n}\n\n.list--group {\n    margin-bottom: 0;\n    position: relative;\n\n    &:last-child .list--item {\n        border-bottom-width: 1px;\n    }\n}\n\n.list--item {\n    border-right-width  : 0;\n    border-bottom-width : 0;\n}\n\n.list--item.-note {\n    min-height: 60px;\n    max-height: 60px;\n}\n\n.list--item--title.-note {\n    overflow    : hidden;\n    white-space : nowrap;\n    width       : 90%;\n    &:extend(.pull-left);\n}\n\n.list--item--favorite {\n    width: 7%;\n    &:extend(.pull-right);\n}\n\n.list--item--text {\n    max-width: 80%;\n    &:extend(.-text-elipsis);\n}\n\n// Button groups\n.list--buttons {\n    position : absolute;\n    top      : 0;\n    right    : 4px;\n    padding  : 8px 4px 8px 0;\n}\n\n// Navbars\n.list--navbar {\n    z-index       : inherit;\n    margin-bottom : 0;\n    padding-top   : 20px;\n}\n.list--nav {\n    margin-right: 2px;\n}\n\n// Pagination bar\n.list--pager {\n    margin-top   : 15px;\n    margin-left  : 10px;\n    margin-right : 10px;\n}\n"
  },
  {
    "path": "app/styles/core/main.less",
    "content": "/**\n * Core styles.\n */\n\n// Fontello\n@import 'fontello.less';\n\n// Import Bootstrap\n@import 'bootstrap.less';\n@import 'variables.less';\n\n// Navbars\n@import 'header.less';\n@import 'sidemenu.less';\n\n// Sidebar\n@import 'list.less';\n@import 'fuzzy.less';\n\n// Editor\n@import 'codemirror.less';\n@import 'editor.less';\n\n// Core\n@import 'layout.less';\n@import 'utils.less';\n@import 'responsive.less';\n\n// RemoteStorage styles\n#remotestorage-widget {\n    top    : auto !important;\n    right  : 15px !important;\n    bottom : 80px !important;\n\n    &.remotestorage-state-connected {\n        bottom: 40px !important;\n    }\n}\n"
  },
  {
    "path": "app/styles/core/responsive.less",
    "content": ".make-visible(@resolution) {\n    @media (max-width: @resolution) {\n        .responsive-visibility();\n    }\n}\n\n.visible-500,\n.visible-400 {\n    .responsive-invisibility();\n}\n\n.visible-500 {\n    .make-visible(500px);\n}\n.visible-400 {\n    .make-visible(400px);\n}\n\n"
  },
  {
    "path": "app/styles/core/sidemenu.less",
    "content": "/**\n * Side menu\n */\n.sidemenu {\n    .make-xs-column(8);\n    .make-md-column(3);\n    .make-sm-column(3);\n    .make-lg-column(3);\n}\n.sidemenu {\n    background-color: #fff;\n\n    // Position and z-index\n    position : fixed;\n    top      : 0;\n    bottom   : 0;\n    z-index  : @zindex--sidemenu;\n\n    // Size\n    height  : 100%;\n    padding : 0;\n\n    // Show scrollbars\n    overflow-y : auto;\n    overflow-x : hidden;\n\n    // Slide open from left\n    visibility : hidden;\n    transition : all 0.3s ease;\n    .make-xs-column-push(-8);\n    .make-md-column-push(-3);\n    .make-sm-column-push(-3);\n    .make-lg-column-push(-3);\n\n    &.-show {\n        visibility: visible;\n        left: 0;\n    }\n}\n"
  },
  {
    "path": "app/styles/core/utils.less",
    "content": "/**\n * Utility classes\n */\n// Show scrollbars\n.-noscroll {\n    overflow: hidden;\n}\n.-scroll {\n    overflow: hidden;\n    overflow-y: scroll;\n}\n.-scroll-x {\n    overflow-x: scroll;\n}\n\n// Hide text that doesn't fit\n.-text-elipsis {\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n}\n\n// Default view for all tables inside of notes\n.layout--body.-note,\n.layout--body.-form {\n    table {\n        &:extend(.table all);\n        &:extend(.table-bordered all);\n        &:extend(.table-striped all);\n        &:extend(.table-hover all);\n        width: auto;\n    }\n    img {\n        &:extend(.img-responsive);\n    }\n}\n"
  },
  {
    "path": "app/styles/core/variables.less",
    "content": "/**\n */\n// Basic variables\n@grid-float-breakpoint: 0;\n@border-radius-base: 3px;\n\n// List group\n@list-group-border-radius: 0;\n\n// Z-indexes\n@zindex--backdrop: @zindex-navbar-fixed + 1;\n@zindex--sidemenu: @zindex-navbar-fixed + 2;\n"
  },
  {
    "path": "app/styles/theme-default/buttons.less",
    "content": "/**\n * Buttons\n */\n.btn-default {\n    border-color: @btn--border--color;\n}\n\n// Success button\n.btn.btn-success {\n    background-color : @btn--success;\n    border-color     : @btn--success--border;\n    &:hover,\n    &:focus {\n        background-color: @btn--success--hover;\n    }\n}\n\n/**\n * Change button styles in note view\n */\n.btn.-note {\n    background   : @btn--note--bg;\n    border       : none;\n    box-shadow   : none;\n    padding      : 7px 15px;\n    margin-right : 5px;\n    &:hover {\n        background: @btn--note--hover;\n    }\n}\n\n/**\n * Favourite button\n */\n.btn--favourite {\n    color     : @btn--favourite;\n    font-size : 1em;\n    padding   : 0;\n    &:hover,\n    &:focus {\n        outline : none;\n        color   : @btn--favourite--hover;\n    }\n}\n\n/**\n * WYSIWYG buttons\n */\n.btn--wysiwyg {\n    border-radius : 0;\n    transition    : background .3s ease-in-out;\n\n    &:hover {\n        background : @pagedown--btn--hover--bg;\n    }\n}\n"
  },
  {
    "path": "app/styles/theme-default/checkbox.less",
    "content": "/**\n * Checkbox module\n */\n.task--checkbox {\n    position : relative;\n    cursor   : pointer;\n    &:hover {\n        color: lighten(@text-color, 15%);\n    }\n}\n\n// Text\n.checkbox--text {\n    margin-left: 10px;\n    font-weight: 300;\n\n    // Create checkbox border block\n    &::before {\n        content : ' ';\n        width   : @check--size;\n        height  : @check--size;\n\n        // Borders\n        border        : @check--border;\n        border-radius : @check--border-radius;\n\n        // Position\n        position : absolute;\n        left     : 0;\n        top      : 3px;\n    }\n}\n\n.checkbox--svg {\n    width    : @check--icon--size;\n    height   : @check--icon--size;\n    position : absolute;\n    top      : 6px;\n    left     : 3px;\n}\n\n.checkbox--path {\n    fill           : none;\n    stroke         : @check--color;\n    stroke-width   : 20px;\n    stroke-linecap : round;\n\n    stroke-dasharray  : 126.366, 126.366;\n    stroke-dashoffset : 126.6;\n    transition        : stroke-dashoffset 0.2s ease-in-out 0s;\n    stroke-linejoin   : round;\n}\n\n.checkbox--input {\n    opacity: 0;\n\n    &:checked {\n        & ~ .checkbox--text {\n            text-decoration: line-through;\n        }\n        & + svg path {\n            stroke-dashoffset: 0;\n        }\n    }\n}\n"
  },
  {
    "path": "app/styles/theme-default/codemirror.less",
    "content": ".CodeMirror {\n    background  : white;\n}\n\n.cm-link {\n    color: @gray;\n}\n.cm-url {\n    color: @gray-light;\n}\n\n.cm-comment {\n    background-color: lighten(@gray-lighter, 2);\n}\n\n.cm-quote {\n    color: @gray-light;\n}\n"
  },
  {
    "path": "app/styles/theme-default/dropzone.less",
    "content": "/**\n * Dropzone styles.\n */\n.dropzone--container {\n    margin: 20px auto;\n    border: 10px dashed lighten(#ccc, 8%);\n}\n\n.dropzone {\n    min-height : 100px;\n    cursor     : pointer;\n    padding    : 20px;\n\n    .dz-default.dz-message {\n        margin-bottom : 16px;\n        text-align    : center;\n    }\n\n    .dz-details {\n        text-align: center;\n    }\n\n    .dz-preview {\n        margin-right  : 5px;\n        margin-bottom : 5px;\n        padding       : 4px;\n    }\n\n    .dz-image img {\n        border-radius : 5px;\n        cursor        : auto;\n    }\n\n    .dz-filename {\n        word-break: break-all;\n    }\n}\n"
  },
  {
    "path": "app/styles/theme-default/editor.less",
    "content": "/**\n * Editor module\n */\n#editor--input--title {\n    height    : auto;\n    padding   : 0;\n    font-size : 36px;\n}\n\n// Leave some space from the top\n.editor--container {\n    &:first-child {\n        margin-top: 30px;\n    }\n}\n\n// Remove borders from Pagedown bar\n.editor--bar {\n    border-right-width : 0;\n    border-left-width  : 0;\n\n    // Hide some buttons on small screens\n    max-height : @navbar-height - 6px;\n    height     : @navbar-height - 6px;\n    overflow   : hidden;\n}\n.editor--btns {\n    margin-top: 2px;\n    margin-bottom: 2px;\n}\n\n.editor--fullscreen .layout--body.-form {\n    top: @navbar-height + 40px;\n}\n\n.editor--fullscreen.-preview .layout--content {\n    background: @layout--form--bg;\n}\n\n/**\n * Editor WYSIWG bar\n */\n.editor--bar,\n.editor--fullscreen .editor--bar {\n    // Colors\n    background : @pagedown--bar--bg;\n    color      : @pagedown--bar--color;\n\n    border-top-width    : 1px;\n    border-bottom-width : 1px;\n    border-style        : solid;\n    border-color        : @pagedown--bar--border;\n    border-radius       : 0;\n\n    // Sizing\n    padding-top    : 2px;\n    padding-bottom : 2px;\n    min-height     : 20px;\n    height         : auto;\n}\n\n// Fullscree mode\n.editor--fullscreen {\n    // Reduce font size of the title\n    #editor--input--title {\n        font-size: 1.8em;\n    }\n\n    // Remove space from the top\n    .editor--container {\n        margin-top: 0;\n    }\n\n    // Remove shadows from the main navbar\n    .header.-form {\n        box-shadow: none;\n    }\n}\n\n// Preview mode\n.editor--fullscreen.-preview {\n    #editor--input--title {\n        padding: 0 12px;\n    }\n}\n"
  },
  {
    "path": "app/styles/theme-default/forms.less",
    "content": "/**\n * Form styles\n */\n.form-control {\n    &:focus {\n        box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset, 0px 0px 8px rgba(189, 189, 189, 0.6);\n        border-color: #BDBDBD;\n        outline: 0px none;\n    }\n}\n\ninput.-borderless {\n    // Borders\n    border-radius : 0;\n    box-shadow    : none;\n    border        : none;\n\n    // Font\n    font-weight : 700;\n    font-size   : 1.8em;\n\n    &:hover,\n    &:focus {\n        box-shadow: none;\n    }\n}\n\n/**\n * Brand forms\n */\n.input--brand {\n    background    : transparent;\n    box-shadow    : none;\n    border        : none;\n    border-bottom : 2px solid #FFF;\n    color         : #FFF;\n    border-radius : 0;\n    text-align    : center;\n    .placeholder(darken(#FFF, 10));\n\n    &:hover,\n    &:focus,\n    &.focus {\n        box-shadow   : none;\n        border-color : #FFF;\n    }\n}\n\n.btn--brand {\n    margin-top     : 33px;\n    padding-top    : 17px;\n    padding-bottom : 17px;\n\n    border         : none;\n    border-radius  : 9px;\n    border         : 2px solid #fff;\n\n    background     : transparent;\n    box-shadow     : 0;\n    color          : #fff;\n\n    font-variant   : small-caps;\n    font-size      : 1.5em;\n\n    &:hover, &:focus, &:active {\n        background : #fff;\n        color      : #303030;\n        outline    : none;\n    }\n}\n\n#module--electronSearch {\n    position: absolute;\n    top: 0;\n    left: 0;\n    z-index: 9999;\n\n    .search--close {\n        font-size: 21px;\n        line-height: 1;\n        font-weight: bold;\n    }\n\n    .electron--search {\n        border-radius: 0 0 8px 0;\n\n        border: 1px solid #ccc;\n        border-top-width: 0;\n        border-left-width: 0;\n        background: #fff;\n        padding: 8px;\n    }\n}\n\n"
  },
  {
    "path": "app/styles/theme-default/header.less",
    "content": "/**\n * Header module\n */\n.header--bg(@bg, @shadow, @border, @color) {\n    background : @bg;\n    box-shadow : @shadow;\n    border     : @border;\n    color      : @color;\n\n    .header--title {\n        &:hover,\n        &:focus {\n            background-color: darken(@bg, 5%);\n        }\n    }\n\n    .navbar-nav > li > a,\n    .navbar-btn,\n    .navbar-link,\n    .navbar-text,\n    .btn-link {\n        color : @color;\n        &:hover,\n        &:focus {\n            color : @color;\n        }\n    }\n}\n\n/**\n * Left Navbar\n */\n.header.-left {\n    .header--bg(@header--left--bg, @header--left--shadow, @header--left--border, @header--left--color);\n}\n.header--container.-left {\n    padding-left: 0;\n}\n.header--search {\n    padding-right: 0;\n}\n\n/**\n * Right Navbar\n */\n.header.-right,\n.header.-note {\n    .header--bg(@header--right--bg, @header--right--shadow, @header--right--border, @header--right--color);\n}\n\n/**\n * The main navbar in note form, pagedown\n */\n.header.-form {\n    .header--bg(@header--right--bg, @header--right--shadow, @header--right--border, @header--right--color);\n}\n"
  },
  {
    "path": "app/styles/theme-default/layout.less",
    "content": "/**\n * Layout module\n */\n\n// Note layout styles\n.layout--body.-note {\n    // Colors\n    background : @layout--note--bg;\n    color      : @layout--note--color;\n}\n\n.container-fluid.-note {\n    padding-left   : 30px;\n    padding-right  : 30px;\n    padding-bottom : 50px;\n}\n\n.page-header.-note {\n    margin-top    : 30px;\n    border-bottom : none;\n}\n\n/**\n * Brand layout\n */\n.layout--brand {\n    background : @layout--brand--bg;\n    color      : @layout--brand--color;\n}\n\n.form--icon {\n    position: relative;\n    .icon {\n        z-index   : @zindex-navbar-fixed + 1;\n        position  : absolute;\n        top       : 10px;\n        left      : 6px;\n        font-size : 20px;\n        color     : #CCC;\n    }\n    .input--icon {\n        padding-left: 30px;\n    }\n}\n\n.header--brand {\n    font-size     : @navbar-height;\n    font-variant  : small-caps;\n\n    // Margins\n    margin-top    : 0;\n    margin-bottom : 10px;\n\n    &:last-child {\n        margin-bottom: 40px;\n    }\n}\n\n.container.-auth {\n    max-width      : 400px;\n    padding-top    : 10%;\n    padding-bottom : 20px;\n}\n\n// Loading\n.layout--brand.-loading {\n    &:after {\n        &:extend(.header--brand);\n    }\n}\n\n#loading--circle {\n\tposition: absolute;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\ttop: 0;\n\tmargin: auto;\n}\n"
  },
  {
    "path": "app/styles/theme-default/list.less",
    "content": "/**\n * List module\n */\n\n.list--item {\n    border-color: @list--border--color;\n\n    &.active,\n    &.active:hover,\n    &.active:focus {\n        background-color : @list--active--bg;\n        border-color     : @list--active--border;\n        box-shadow       : none;\n    }\n}\n\n// List of notes\n.list--item.-note {\n    padding-top    : 25px;\n    padding-bottom : 15px;\n    max-height     : 80px;\n    min-height     : 80px;\n}\n\n// Pagination buttons\n.list--pager--btn {\n    padding    : 15px;\n    background : transparent;\n}\n\n// List of notebooks\n.list--nested {\n    padding-left: 30px;\n\n    .list--item {\n        border-left-color: transparent;\n        margin-left: -30px * 4;\n        padding-left: 30px * 4;\n        &:last-child {\n            border-bottom-color: transparent;\n        }\n    }\n}\n\n// Navbars\n.list--navbar {\n    color         : @list--navbar--color;\n    background    : @list--navbar--bg;\n}\n.list--brand {\n    font-variant : small-caps;\n    color        : @list--navbar--brand;\n}\n"
  },
  {
    "path": "app/styles/theme-default/loading-animation.less",
    "content": "/*!\n * Load Awesome v1.1.0 (http://github.danielcardoso.net/load-awesome/)\n * Copyright 2015 Daniel Cardoso <@DanielCardoso>\n * Licensed under MIT\n */\n.loading--circle {\n    display: block;\n    font-size: 0;\n    color: #fff;\n}\n.loading--circle > div {\n    display: inline-block;\n    float: none;\n    background-color: currentColor;\n    border: 0 solid currentColor;\n}\n.loading--circle {\n    width: 96px;\n    height: 96px;\n}\n.loading--circle > div {\n    width: 96px;\n    height: 96px;\n    background: transparent;\n    border-width: 6px;\n    border-bottom-color: transparent;\n    border-radius: 100%;\n    -webkit-animation: ball-clip-rotate .75s linear infinite;\n       -moz-animation: ball-clip-rotate .75s linear infinite;\n         -o-animation: ball-clip-rotate .75s linear infinite;\n            animation: ball-clip-rotate .75s linear infinite;\n}\n/*\n * Animation\n */\n@-webkit-keyframes ball-clip-rotate {\n    0% {\n        -webkit-transform: rotate(0deg);\n                transform: rotate(0deg);\n    }\n    50% {\n        -webkit-transform: rotate(180deg);\n                transform: rotate(180deg);\n    }\n    100% {\n        -webkit-transform: rotate(360deg);\n                transform: rotate(360deg);\n    }\n}\n@-moz-keyframes ball-clip-rotate {\n    0% {\n        -moz-transform: rotate(0deg);\n             transform: rotate(0deg);\n    }\n    50% {\n        -moz-transform: rotate(180deg);\n             transform: rotate(180deg);\n    }\n    100% {\n        -moz-transform: rotate(360deg);\n             transform: rotate(360deg);\n    }\n}\n@-o-keyframes ball-clip-rotate {\n    0% {\n        -o-transform: rotate(0deg);\n           transform: rotate(0deg);\n    }\n    50% {\n        -o-transform: rotate(180deg);\n           transform: rotate(180deg);\n    }\n    100% {\n        -o-transform: rotate(360deg);\n           transform: rotate(360deg);\n    }\n}\n@keyframes ball-clip-rotate {\n    0% {\n        -webkit-transform: rotate(0deg);\n           -moz-transform: rotate(0deg);\n             -o-transform: rotate(0deg);\n                transform: rotate(0deg);\n    }\n    50% {\n        -webkit-transform: rotate(180deg);\n           -moz-transform: rotate(180deg);\n             -o-transform: rotate(180deg);\n                transform: rotate(180deg);\n    }\n    100% {\n        -webkit-transform: rotate(360deg);\n           -moz-transform: rotate(360deg);\n             -o-transform: rotate(360deg);\n                transform: rotate(360deg);\n    }\n}\n"
  },
  {
    "path": "app/styles/theme-default/main.less",
    "content": "/**\n * Default theme\n */\n@import \"bootstrap/less/variables.less\";\n@import \"bootstrap/less/mixins.less\";\n\n// Variables\n@import 'variables.less';\n\n// Core\n@import 'layout.less';\n@import 'utils.less';\n@import 'forms.less';\n@import 'modal.less';\n@import 'buttons.less';\n@import 'checkbox.less';\n\n// Navbars\n@import 'header.less';\n@import 'sidemenu.less';\n\n// List of notes, notebooks, etc.\n@import 'list.less';\n\n// Settings\n@import 'settings.less';\n\n// Codemirror editor\n@import 'editor.less';\n@import 'codemirror.less';\n@import 'dropzone.less';\n\n@import 'prism.less';\n\n// Loading animation\n@import 'loading-animation.less';\n"
  },
  {
    "path": "app/styles/theme-default/modal.less",
    "content": "/**\n * Modal windows\n */\n.modal--body.modal-body {\n    padding: 0 15px;\n}\n\n.modal--group.form-group {\n    border-bottom: 1px solid #e5e5e5;\n    overflow: hidden;\n    margin-left    : -30px;\n    margin-right   : -30px;\n    margin-bottom  : 0;\n    padding-top    : 5px;\n    padding-bottom : 5px;\n    &:last-child {\n        border-bottom-width: 0;\n    }\n}\n\n.modal--input {\n    border-width: 0;\n\n    &, &:hover, &:focus {\n        box-shadow: none;\n    }\n}\n"
  },
  {
    "path": "app/styles/theme-default/prism.less",
    "content": "/**\n * prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML\n * Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics);\n * @author Tim  Shedor\n */\n\ncode,\npre[class*=\"language-\"] {\n    color: black;\n    background-color: #fdfdfd;\n    font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;\n    direction: ltr;\n    text-align: left;\n    white-space: pre;\n    word-spacing: normal;\n    word-break: normal;\n    word-wrap: normal;\n    line-height: 1.5;\n\n    -moz-tab-size: 4;\n    -o-tab-size: 4;\n    tab-size: 4;\n\n    -webkit-hyphens: none;\n    -moz-hyphens: none;\n    -ms-hyphens: none;\n    hyphens: none;\n}\n\n/* Code blocks */\n.-note,\n.editor--preview {\n    pre {\n        display: block;\n        position: relative;\n        margin: .5em 0;\n        -webkit-box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;\n        -moz-box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;\n        box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;\n        border-left: 10px solid lighten(@color--info, 4%);\n        background-image: -webkit-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);\n        background-image: -moz-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);\n        background-image: -ms-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);\n        background-image: -o-linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);\n        background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);\n        background-size: 3em 3em;\n        background-origin: content-box;\n        overflow: auto;\n        max-height: inherit;\n        height: 100%;\n        padding: 0 1em;\n\n        /* Margin bottom to accomodate shadow */\n        -webkit-box-sizing: border-box;\n        -moz-box-sizing: border-box;\n        box-sizing: border-box;\n        margin-bottom: 1em;\n    }\n}\n\n.-note {\n    pre {\n        max-height: 50rem;\n    }\n}\n\n/* Inline code */\n:not(pre) > code {\n    position: relative;\n    padding: .2em;\n    -webkit-border-radius: 0.3em;\n    -moz-border-radius: 0.3em;\n    -ms-border-radius: 0.3em;\n    -o-border-radius: 0.3em;\n    border-radius: 0.3em;\n    color: #c92c2c;\n    border: 1px solid rgba(0, 0, 0, 0.1);\n    display: inline;\n}\n\n[class*=\"language-\"]:before,\n[class*=\"language-\"]:after {\n    content: '';\n    z-index: -2;\n    display: block;\n    position: absolute;\n    bottom: 0.75em;\n    left: 0.18em;\n    width: 40%;\n    height: 20%;\n    -webkit-box-shadow: 0px 13px 8px #979797;\n    -moz-box-shadow: 0px 13px 8px #979797;\n    box-shadow: 0px 13px 8px #979797;\n    -webkit-transform: rotate(-2deg);\n    -moz-transform: rotate(-2deg);\n    -ms-transform: rotate(-2deg);\n    -o-transform: rotate(-2deg);\n    transform: rotate(-2deg);\n}\n\n:not(pre) > code:after,\n[class*=\"language-\"]:after {\n    right: 0.75em;\n    left: auto;\n    -webkit-transform: rotate(2deg);\n    -moz-transform: rotate(2deg);\n    -ms-transform: rotate(2deg);\n    -o-transform: rotate(2deg);\n    transform: rotate(2deg);\n}\n\n.token.comment,\n.token.block-comment,\n.token.prolog,\n.token.doctype,\n.token.cdata {\n    color: #7D8B99;\n}\n\n.token.punctuation {\n    color: #5F6364;\n}\n\n.token.property,\n.token.tag,\n.token.boolean,\n.token.number,\n.token.function-name,\n.token.constant,\n.token.symbol,\n.token.deleted {\n    color: #c92c2c;\n}\n\n.token.selector,\n.token.attr-name,\n.token.string,\n.token.char,\n.token.function,\n.token.builtin,\n.token.inserted {\n    color: #2f9c0a;\n}\n\n.token.operator,\n.token.entity,\n.token.url,\n.token.variable {\n    color: #a67f59;\n    background: rgba(255, 255, 255, 0.5);\n}\n\n.token.atrule,\n.token.attr-value,\n.token.keyword,\n.token.class-name {\n    color: #1990b8;\n}\n\n.token.regex,\n.token.important {\n    color: #e90;\n}\n\n.language-css .token.string,\n.style .token.string {\n    color: #a67f59;\n    background: rgba(255, 255, 255, 0.5);\n}\n\n.token.important {\n    font-weight: normal;\n}\n\n.token.bold {\n    font-weight: bold;\n}\n.token.italic {\n    font-style: italic;\n}\n\n.token.entity {\n    cursor: help;\n}\n\n.namespace {\n    opacity: .7;\n}\n\n@media screen and (max-width: 767px) {\n    [class*=\"language-\"]:before,\n    [class*=\"language-\"]:after {\n        bottom: 14px;\n        -webkit-box-shadow: none;\n        -moz-box-shadow: none;\n        box-shadow: none;\n    }\n\n}\n\n/* Plugin styles */\n.token.tab:not(:empty):before,\n.token.cr:before,\n.token.lf:before {\n    color: #e0d7d1;\n}\n\n/* Plugin styles: Line Numbers */\n[class*=\"language-\"].line-numbers {\n    padding-left: 0;\n}\n\n[class*=\"language-\"].line-numbers code {\n    padding-left: 3.8em;\n}\n\n[class*=\"language-\"].line-numbers .line-numbers-rows {\n    left: 0;\n}\n\n/* Plugin styles: Line Highlight */\n[class*=\"language-\"][data-line] {\n    padding-top: 0;\n    padding-bottom: 0;\n    padding-left: 0;\n}\n[data-line] code {\n    position: relative;\n    padding-left: 4em;\n}\npre .line-highlight {\n    margin-top: 0;\n}\n"
  },
  {
    "path": "app/styles/theme-default/settings.less",
    "content": "/**\n * Settings\n */\n// List of settings\n.list--settings {\n    &:extend(.sidemenu--item all);\n\n    &.list-group-item.active {\n        background : lighten(@sidemenu--item--hover, 2%);\n    }\n}\n\n// Back button\n.header--settings--back:hover {\n    background: #f8f8f8;\n}\n\n// Content\n.container-fluid.-settings {\n    padding: 40px 30px;\n}\n"
  },
  {
    "path": "app/styles/theme-default/sidemenu.less",
    "content": "/**\n * Sidemenu\n */\n.sidemenu {\n    background: @sidemenu--bg;\n}\n\n.sidemenu--item.list-group-item {\n    // Sizing\n    min-height   : 50px;\n    max-height   : 50px;\n    padding-left : 20px;\n    padding-top  : 15px;\n\n    // Colors\n    background : @sidemenu--item;\n    color      : @sidemenu--item--color;\n    border     : @sidemenu--item--border;\n\n    &.active,\n    &:hover {\n        background : @sidemenu--item--hover;\n        color      : @sidemenu--item--hover--color;\n    }\n}\n\n.sidemenu--item.-disabled,\n.sidemenu--item.-disabled:hover {\n    background : @sidemenu--disabled;\n    color      : @sidemenu--disabled--color;\n    font-size  : 1.3em;\n}\n\n.sidemenu--close {\n    cursor: pointer;\n}\n"
  },
  {
    "path": "app/styles/theme-default/utils.less",
    "content": "/**\n * Utils\n */\na,\n.btn-link {\n    color: darken(@color--brand, 5%);\n    &:hover {\n        color: lighten(@color--brand, 5%);\n    }\n}\n\n.-text-big {\n    font-size   : 1.3em;\n    line-height : 2em;\n}\n\n// Progress bars\n.progress-bar-success {\n    background-color: @color--brand;\n}\n.progress-bar-info {\n    background-color: @color--info;\n}\n"
  },
  {
    "path": "app/styles/theme-default/variables.less",
    "content": "/**\n * Variables\n */\n@color--brand: #00a693;\n@color--info : #1693A7;\n\n\n/**\n * Layout\n */\n@layout--note--bg    : #fff;\n@layout--note--color : #2c3130;\n@layout--form--bg    : #EDECE4;\n@layout--brand--bg   : #007164;\n@layout--brand--color: #fff;\n\n/**\n * Header\n */\n@header--bg     : #fff;\n@header--border : none;\n@header--color  : #333333;\n@header--shadow : 1px 1px 10px -6px #939393;\n\n// Left header\n@header--left--bg     : @header--bg;\n@header--left--border : @header--border;\n@header--left--color  : @header--color;\n@header--left--shadow : @header--shadow;\n\n// Right header\n@header--right--bg     : @header--bg;\n@header--right--border : @header--border;\n@header--right--color  : @header--color;\n@header--right--shadow : 0px -5px 10px 1px #cccccc;\n\n/**\n * Buttons\n */\n@btn--border--color   : #f2f2f2;\n\n// Success button\n@btn--success         : @color--brand;\n@btn--success--border : lighten(@color--brand, 1%);\n@btn--success--hover  : darken(@color--brand, 5%);\n\n// Buttons in note view\n@btn--note--bg         : #f8f8f8;\n@btn--note--hover      : #f3f3f3;\n\n// Favourite button\n@btn--favourite        : #515151;\n@btn--favourite--hover : #6a6a6a;\n\n/**\n * Checkbox module\n */\n@check--border        : 2px solid @color--brand;\n@check--size          : @font-size-base + 4px;\n@check--icon--size    : @font-size-base - 2px;\n@check--border-radius : 2px;\n@check--border-radius : 2px;\n@check--color         : @color--brand;\n\n/**\n * List module\n */\n@list--border--color  : #f2f2f2;\n@list--active--bg     : @color--brand;\n@list--active--border : @color--brand;\n\n// List navbar\n@list--navbar--color  : #1f1f1f;\n@list--navbar--bg     : transparent;\n@list--navbar--brand  : #1f1f1f;\n\n/**\n * Sidemenu\n */\n@sidemenu--bg                 : #fff;\n@sidemenu--item               : #fff;\n@sidemenu--item--color        : #333333;\n@sidemenu--item--border       : none;\n@sidemenu--item--hover        : @color--brand;\n@sidemenu--item--hover--color : #fff;\n@sidemenu--disabled           : @sidemenu--item;\n@sidemenu--disabled--color    : #838383;\n\n/**\n * Pagedown module\n */\n// Pagedown bar\n@pagedown--bar--bg    : #f5f5f5;\n@pagedown--bar--border: #e6e6e6;\n@pagedown--bar--color : #000;\n\n// Buttons\n@pagedown--btn--hover--bg    : #ccccce;\n@pagedown--btn--hover--color : #07111a;\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"laverna\",\n  \"version\": \"0.7.51\",\n  \"license\": \"MPL-2.0\",\n  \"dependencies\": {\n    \"backbone\": \"~1.3.3\",\n    \"backbone.babysitter\": \"~0.1.12\",\n    \"backbone.marionette\": \"marionette#~2.4.3\",\n    \"backbone.radio\": \"~1.0.5\",\n    \"Blob\": \"git://github.com/eligrey/Blob.js\",\n    \"bluebird\": \"~3.4.0\",\n    \"blueimp-canvas-to-blob\": \"git://github.com/blueimp/JavaScript-Canvas-to-Blob#~2.1.1\",\n    \"bootstrap\": \"~3.3.6\",\n    \"device.js\": \"git://github.com/matthewhudson/device.js#0.2.7\",\n    \"dropzone\": \"~4.3.0\",\n    \"enquire\": \"~2.1.2\",\n    \"FileSaver\": \"*\",\n    \"fuse\": \"~2.3.0\",\n    \"hammerjs\": \"~2.0.8\",\n    \"i18next\": \"~3.4.1\",\n    \"jquery\": \"~3.1.0\",\n    \"localforage\": \"~1.4.2\",\n    \"MathJax\": \"components/MathJax#~2.6.0\",\n    \"modernizr\": \"~2.8.3\",\n    \"mousetrap\": \"~1.6.0\",\n    \"q\": \"v1.4.1\",\n    \"remotestorage.js\": \"git://github.com/remotestorage/remotestorage.js#0.12.1\",\n    \"requirejs\": \"~2.2.0\",\n    \"requirejs-text\": \"~2.0.15\",\n    \"sjcl\": \"~1.0.3\",\n    \"to-markdown\": \"#jquery\",\n    \"tv4\": \"~1.2.7\",\n    \"underscore\": \"~1.8.3\",\n    \"xregexp\": \"~3.1.1\",\n    \"jszip\": \"Stuk/jszip#~2.5.0\",\n    \"markdown-it-imsize\": \"~2.0.1\",\n    \"markdown-it-math\": \"~4.0.0\",\n    \"markdown-it\": \"~7.0.0\",\n    \"codemirror\": \"~5.17.0\",\n    \"prism\": \"~1.5.1\",\n    \"markdown-it-sanitizer\": \"~0.4.1\",\n    \"markdown-it-hashtag\": \"~0.4.0\",\n    \"xss\": \"^0.2.13\",\n    \"i18next-xhr-backend\": \"^1.0.1\",\n    \"fastclick\": \"^1.0.6\",\n    \"jquery-hammerjs\": \"jquery.hammer.js#^2.0.0\"\n  },\n  \"devDependencies\": {},\n  \"resolutions\": {\n    \"backbone\": \"~1.3.3\",\n    \"underscore\": \"~1.8.3\",\n    \"jquery\": \"~3.1.0\"\n  }\n}\n"
  },
  {
    "path": "config.xml",
    "content": "<?xml version='1.0' encoding='utf-8'?>\n<widget id=\"<%= id %>\" version=\"<%= version %>\" xmlns=\"http://www.w3.org/ns/widgets\" xmlns:gap=\"http://phonegap.com/ns/1.0\">\n    <name>Laverna</name>\n    <description>\n        Open source note taking application\n    </description>\n    <author email=\"lavernaproject@gmail.com\" href=\"https://github.com/Laverna/laverna\">\n        Laverna project\n    </author>\n    <feature name=\"http://api.phonegap.com/1.0/device\" />\n    <preference name=\"permissions\" value=\"none\" />\n    <preference name=\"orientation\" value=\"default\" />\n    <preference name=\"target-device\" value=\"universal\" />\n    <preference name=\"fullscreen\" value=\"true\" />\n    <preference name=\"webviewbounce\" value=\"true\" />\n    <preference name=\"prerendered-icon\" value=\"true\" />\n    <preference name=\"stay-in-webview\" value=\"false\" />\n    <preference name=\"ios-statusbarstyle\" value=\"black-opaque\" />\n    <preference name=\"detect-data-types\" value=\"true\" />\n    <preference name=\"exit-on-suspend\" value=\"false\" />\n    <preference name=\"show-splash-screen-spinner\" value=\"true\" />\n    <preference name=\"auto-hide-splash-screen\" value=\"true\" />\n    <preference name=\"disable-cursor\" value=\"false\" />\n    <preference name=\"android-minSdkVersion\" value=\"14\" />\n    <preference name=\"android-installLocation\" value=\"auto\" />\n    <access origin=\"http://127.0.0.1*\" />\n</widget>\n"
  },
  {
    "path": "electron.js",
    "content": "'use strict';\n\nconst electron        = require('electron'),\n    windowStateKeeper = require('electron-window-state'),\n    path              = require('path'),\n    openUrl           = require('open');\n\nconst {app, BrowserWindow, Menu, Tray, protocol} = electron;\n\nlet argv        = require('minimist')(process.argv.slice(1)),\n    contextMenu = require('electron-context-menu')({}),\n    win         = null,\n    appHelper;\n\n// Show command line help\nif (argv.help || argv.h) {\n    console.log('Usage: laverna [options]\\r\\n');\n    console.log('--help', 'Show this help');\n    console.log('--dev ', 'Show developer tools automatically on start');\n    console.log('--tray', 'Hide to tray on start');\n    console.log(\n        '--data-dir',\n        'Directory where data is stored.',\n        'Example: laverna --data-dir=../data'\n    );\n\n    return app.quit();\n}\n\n// Allow to change the directory where the data is stored\nif (argv['data-dir'] && argv['data-dir'].trim()) {\n    let dataDir = argv['data-dir'].trim();\n\n    // The same or parent directory\n    if (dataDir.search(/^\\./) > - 1) {\n\n        // Always store data in a separate directory\n        dataDir += (dataDir.search(/^\\.{1,2}$/) > -1 ? '/laverna-data' : '');\n\n        dataDir = path.join(__dirname, dataDir);\n    }\n\n    app.setPath('userData', path.normalize(dataDir));\n}\n\nappHelper = {\n\n    // The page which will be loaded in the window\n    page: 'http://localhost:9000/',\n\n    // Icon of the app\n    icon: path.join(__dirname, '/dist/images/icon/',\n      process.platform === 'darwin' ? 'IconMenubarTemplate.png' : 'icon-120x120.png'\n    ),\n\n    // The main menu\n    menu: [\n        {\n            label   : '&File',\n            submenu : [\n                {\n                    label       : 'Quit',\n                    accelerator : 'CmdOrCtrl+Q',\n                    click       : function() {\n                        app.quit();\n                    }\n                }\n            ]\n        },\n        {\n            label: '&Edit',\n            submenu: [\n                {\n                    label       : 'Undo',\n                    accelerator : 'CmdOrCtrl+Z',\n                    role        : 'undo'\n                },\n                {\n                    label       : 'Redo',\n                    accelerator : 'Shift+CmdOrCtrl+Z',\n                    role        : 'redo'\n                },\n                {type: 'separator'},\n                {\n                    label       : 'Cut',\n                    accelerator : 'CmdOrCtrl+X',\n                    role        : 'cut'\n                },\n                {\n                    label       : 'Copy',\n                    accelerator : 'CmdOrCtrl+C',\n                    role        : 'copy'\n                },\n                {\n                    label       : 'Paste',\n                    accelerator : 'CmdOrCtrl+V',\n                    role        : 'paste'\n                },\n                {type: 'separator'},\n                {\n                    label       : 'Select All',\n                    accelerator : 'CmdOrCtrl+A',\n                    role        : 'selectall'\n                },\n            ]\n        },\n        {\n            label: '&View',\n            submenu: [\n                {\n                    label       : 'Reload',\n                    accelerator : 'CmdOrCtrl+R',\n                    click       : function(item, focusedWindow) {\n                        if (focusedWindow) { focusedWindow.reload(); }\n                    }\n                },\n                {\n                    label       : 'Toggle Developer Tools',\n                    accelerator : process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',\n                    click       : function(item, focusedWindow) {\n                        if (focusedWindow) {\n                            focusedWindow.webContents.toggleDevTools();\n                        }\n                    }\n                }\n            ]\n        },\n    ],\n\n    // Tray menu (shown on right click)\n    menuTray: [\n        {\n            label: 'Quit',\n            click: function() {\n                app.quit();\n            }\n        }\n    ],\n\n    /**\n     * Electron is ready.\n     */\n    onReady: function() {\n        this\n        .startServer()\n        // .interceptProtocol()\n        .createWindow()\n        .createMenu()\n        .createTray()\n        .registerEvents();\n    },\n\n    startServer() {\n        require('./server')(9000);\n        return this;\n    },\n\n    /**\n     * Override HTTP protocol. The reason:\n     * In order to make oAuth authentifications to Dropbox/RemoteStorage,\n     * we need to serve the app from https? protocol and have relative paths.\n     */\n    interceptProtocol: function() {\n        protocol.interceptHttpProtocol('http', function(req, callback) {\n\n            if (req.url.search(appHelper.page) > - 1) {\n\n                // Remove the domain, path, and hash location\n                req.url = req.url.replace(appHelper.page, '');\n                req.url = req.url.split('#')[0] || 'index.html';\n\n                // Serve the resource from the file system\n                req.url = 'file:///' + path.normalize(__dirname + '/dist/' + req.url);\n            }\n\n            callback(req);\n\n        });\n\n        return this;\n    },\n\n    /**\n     * Create the main window.\n     */\n    createWindow: function() {\n\n        // Recover window state (width, height, and x&y position)\n        this.state = windowStateKeeper('main', {\n            width  : 1000,\n            height : 600\n        });\n\n        // Create new browser window\n        win = new BrowserWindow({\n            width  : this.state.width,\n            height : this.state.height,\n            x      : this.state.x,\n            y      : this.state.y,\n\n            title           : 'Laverna',\n            icon            : this.icon,\n            autoHideMenuBar : true,\n            backgroundColor : '#00a693',\n\n            webPreferences      : {\n                nodeIntegration : true,\n                preload         : path.resolve(path.join(__dirname, 'preload.js')),\n            },\n        });\n\n        if (this.state.isMaximized) {\n            win.maximize();\n        }\n\n        // Load the app\n        win.loadURL(this.page);\n\n        // Show development tools\n        if (process.env.NODE_ENV === 'dev' || argv.dev) {\n            win.webContents.openDevTools();\n        }\n\n        return this;\n    },\n\n    /**\n     * Create the main menu.\n     */\n    createMenu: function() {\n\n        // Slightly different menu on OS X\n        if (process.platform === 'darwin') {\n            this.menu[0] = {\n                label   : 'Laverna',\n                submenu : this.menu[0].submenu\n            };\n        }\n\n        let menu = Menu.buildFromTemplate(this.menu);\n        Menu.setApplicationMenu(menu);\n\n        return this;\n    },\n\n    /**\n     * Create tray icon.\n     */\n    createTray: function() {\n        let icon = new Tray(this.icon),\n            menu = Menu.buildFromTemplate(this.menuTray);\n\n        icon.setToolTip('Laverna');\n        icon.setContextMenu(menu);\n\n        // Auto hide to tray on start\n        if (argv.tray) {\n            win.hide();\n        }\n\n        // Hide the window into tray on click\n        icon.on('click', function() {\n            if (win.isVisible()) {\n                return win.hide();\n            }\n            win.show();\n        });\n\n        return this;\n    },\n\n    /**\n     * Listen to window events.\n     */\n    registerEvents: function() {\n\n        // Save window state (width, height, and x&y position)\n        win.on('close', function() {\n            this.state.saveState(win);\n        }.bind(appHelper));\n\n        win.on('closed', function() {\n            win = null;\n        });\n\n        win.webContents.on('will-navigate', appHelper.onNavigate.bind(appHelper));\n        win.webContents.on('new-window', appHelper.onNavigate.bind(appHelper));\n\n        // Disable nodeIntegration\n        // win.webContents.on('new-window', appHelper.onNewWindow.bind(appHelper));\n    },\n\n    /**\n     * Open URLs in an external browser.\n     */\n    onNavigate: function(e, url) {\n        if (url.search(this.page) === -1 &&\n            url.search(/(oauth|sign_in)/) === -1 &&\n            url.search(/^blob:/) === -1) {\n\n            e.preventDefault();\n            return openUrl(url);\n        }\n    },\n\n    /**\n     * Disable nodeIntegration on all pages except for app's.\n     */\n    onNewWindow: function(e, url) {\n        if (url.search(this.page) === -1 &&\n            url.search(/^blob:/) === -1) {\n\n            e.preventDefault();\n\n            let extWin = new BrowserWindow({\n                width  : 600,\n                height : 600,\n                autoHideMenuBar     : true,\n                webPreferences      : {\n                    nodeIntegration : false,\n                },\n            });\n\n            // Load the app\n            extWin.loadURL(url);\n            extWin.on('closed', function() { extWin = null; });\n        }\n    },\n\n};\n\n// Create browser window when Electron is ready\napp.on('ready', appHelper.onReady.bind(appHelper));\n\n// Quit when all windows are closed.\napp.on('window-all-closed', function() {\n    /*\n     * On OS X it is common for applications and their menu bar\n     * to stay active until the user quits explicitly with Cmd + Q\n     */\n    if (process.platform !== 'darwin') {\n        app.quit();\n    }\n});\n\napp.on('activate', function() {\n    /*\n     * On OS X it's common to re-create a window in the app when the\n     * dock icon is clicked and there are no other windows open.\n     */\n    if (win === null) {\n        appHelper.onReady();\n    }\n});\n"
  },
  {
    "path": "gulpfile.js",
    "content": "'use strict';\nvar gulp    = require('gulp'),\n    pkg     = require('./package.json'),\n    plugins = require('gulp-load-plugins')();\n\nplugins.del         = require('del');\nplugins.browserSync = require('browser-sync').create();\n\nfunction getTask(task) {\n    return require('./gulps/' + task)(gulp, plugins, pkg);\n}\n\n// Add Gulp tasks\n[\n    'jshint' , 'jsonlint'   , 'mocha'       , 'nightwatch',\n    'less'   , 'prism'      , 'require'     , 'electron'  ,\n    'htmlmin', 'cssmin'     , 'htmlManifest', 'mobile'    ,\n    'copy'   , 'copyRelease', 'copyDist'    ,\n    'serve'  , 'clean'      , 'npm'\n]\n.forEach(function(task) {\n    var taskFun = getTask(task);\n\n    // It has several tasks\n    if (typeof taskFun === 'function') {\n        gulp.task(task, taskFun);\n    }\n});\n\ngulp.task('release:after', function() {\n    return gulp.src('./release')\n    .pipe(plugins.shell([\n        'cd ./release && zip -r ../release/webapp.zip ./laverna',\n    ]));\n});\n\n/**\n * Unit tests.\n */\ngulp.task('test', ['jsonlint', 'jshint', 'mocha']);\n\n/**\n * Build the app.\n * ``gulp build --dev`` to build without minifying.\n */\ngulp.task('build', plugins.sequence(\n    // 'test',\n    'clean:dist',\n    ['prism', 'less'],\n    ['copy', 'require', 'htmlmin', 'cssmin'],\n    'htmlManifest'\n));\n\n/**\n * Prepare the release files.\n */\ngulp.task('release', plugins.sequence(\n    'build',\n    'clean:release',\n    ['copyDist', 'copyRelease'],\n    'npm:install',\n    'electron',\n    'release:after'\n));\n\n/**\n * Gulp server.\n * ``gulp --root dist`` to serve dist folder.\n */\ngulp.task('default', plugins.sequence(\n    ['less', 'prism'],\n    ['serve:start', 'serve:watch']\n));\n"
  },
  {
    "path": "gulps/clean.js",
    "content": "'use strict';\n\n/**\n * Tasks for cleaning up.\n */\nmodule.exports = function(gulp, plugins) {\n\n    gulp.task('clean:dist', function() {\n        return plugins.del(['./dist']);\n    });\n\n    gulp.task('clean:release', function() {\n        return plugins.del(['./release/*']);\n    });\n\n};\n"
  },
  {
    "path": "gulps/copy.js",
    "content": "'use strict';\nvar merge = require('merge-stream');\n\n/**\n * Copy additional dependencies into dist directory.\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        var options = {base: './app/bower_components/'};\n\n        return merge.apply(merge, [\n            gulp.src([\n                './LICENSE'\n            ], {base: './'})\n            .pipe(gulp.dest('./dist')),\n\n            // Copy\n            gulp.src([\n                './app/scripts/modules/fs/**/*.*',\n                './app/scripts/modules/electronSearch/**/*.*',\n                './app/scripts/modules/fuzzySearch/**/*.*',\n                './app/scripts/modules/mathjax/**/*.*',\n\n                './app/scripts/modules/modules.json',\n\n                './app/bower_components/prism/themes/prism.css',\n                './app/images/**/*.+(png|jpg|gif|ico|icns)',\n                './app/docs/**',\n                './app/locales/**',\n                './app/.htaccess',\n                './app/*.+(xml|ico|txt|webapp)',\n                './app/styles/**/*.+(eot|svg|ttf|woff)',\n            ], {base: './app'})\n            .pipe(gulp.dest('./dist')),\n\n            // Copy and minify excluded modules\n            gulp.src([\n                './app/scripts/modules/markdown/libs/markdown-it-file.js',\n                './app/scripts/modules/markdown/libs/markdown-it-task.js',\n                './app/scripts/modules/markdown/libs/markdown-it.js',\n                './app/scripts/modules/markdown/libs/markdown.js',\n                './app/scripts/modules/markdown/workers/markdown.js',\n\n                './app/scripts/workers/sjcl.js',\n                './app/scripts/classes/sjcl.js',\n                './app/scripts/classes/sjcl.worker.js',\n\n                './app/scripts/helpers/Dropbox-sdk.min.js',\n                './app/scripts/helpers/db.js',\n                './app/scripts/helpers/migrate.js',\n                './app/scripts/migrate.js',\n                './app/scripts/workers/localForage.js',\n            ], {base: './app'})\n            .pipe(plugins.util.env.dev ? plugins.util.noop() : plugins.uglify({\n                preserveComments: 'license'\n            }))\n            .pipe(gulp.dest('./dist')),\n\n            // Copy and minify Bower components\n            gulp.src([\n                './app/bower_components/markdown-it*/dist/*.min.js',\n\n                './app/bower_components/backbone/backbone.js',\n                './app/bower_components/jquery/dist/jquery.js',\n                './app/bower_components/backbone.radio/build/backbone.radio.min.js',\n                './app/bower_components/prism/bundle.js',\n                './app/bower_components/requirejs/require.js',\n                './app/bower_components/modernizr/modernizr.js',\n                './app/bower_components/q/q.js',\n                './app/bower_components/underscore/underscore.js',\n                './app/bower_components/localforage/dist/localforage.js',\n                './app/bower_components/dropbox/dropbox.js',\n                './app/bower_components/sjcl/sjcl.js',\n\n                // Remotestorage\n                './app/bower_components/remotestorage.js/release/stable/remotestorage.js',\n                './app/bower_components/tv4/tv4.js',\n                './app/bower_components/bluebird/js/browser/bluebird.min.js',\n            ], options)\n            .pipe(plugins.util.env.dev ? plugins.util.noop() : plugins.uglify({\n                preserveComments: 'license'\n            }))\n            .pipe(gulp.dest('./dist/bower_components/')),\n\n            // Copy MathJax files\n            gulp.src([\n                './app/bower_components/MathJax/*.js',\n                './app/bower_components/MathJax/config/TeX-AMS-MML_HTMLorMML.js',\n                './app/bower_components/MathJax/images/**',\n                './app/bower_components/MathJax/extensions/**',\n                './app/bower_components/MathJax/fonts/HTML-CSS/TeX/woff/**',\n                './app/bower_components/MathJax/jax/element/**',\n                './app/bower_components/MathJax/jax/input/+(MathML|TeX)/**',\n                './app/bower_components/MathJax/jax/output/+(HTML-CSS|NativeMML)/**',\n                './app/bower_components/MathJax/localization/en/**'\n            ], options)\n            .pipe(gulp.dest('./dist/bower_components/')),\n        ]);\n    };\n};\n"
  },
  {
    "path": "gulps/copyDist.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src('./dist')\n        .pipe(plugins.shell([\n            'mkdir -p ./release/laverna',\n            'cp -R ./dist ./release/laverna/dist',\n        ]));\n    };\n};\n"
  },
  {
    "path": "gulps/copyRelease.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp) {\n    return function() {\n        return gulp.src([\n            './preload.js',\n            './server.js',\n            './package.json'\n        ], {base: './'})\n        .pipe(gulp.dest('./release/laverna'));\n    };\n};\n"
  },
  {
    "path": "gulps/cssmin.js",
    "content": "'use strict';\n\n/**\n * Minify CSS files.\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src('./app/styles/*.css')\n        .pipe(plugins.cleanCss({\n            compatibility: 'ie8'\n        }))\n        .pipe(gulp.dest('./dist/styles'));\n    };\n};\n"
  },
  {
    "path": "gulps/electron.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp, plugins, pkg) {\n    return function() {\n        var platforms = [\n            'darwin-x64',\n            // 'linux-arm',\n            'linux-ia32',\n            'linux-x64',\n            'win32-ia32',\n            'win32-x64'\n        ];\n\n        if (plugins.util.env.platform) {\n            platforms = [plugins.util.env.platform];\n        }\n\n        return gulp.src('./electron.js')\n        // .pipe(replace('__dirname + \\'/dist\\'', '__dirname'))\n        .pipe(gulp.dest('./release/laverna'))\n        .pipe(plugins.electron({\n            src         : './release/laverna',\n            packageJson : pkg,\n            release     : './release',\n            cache       : './.tmp',\n            version     : 'v1.6.5',\n            packaging   : true,\n            // rebuild     : true,\n            platforms   : platforms,\n            platformResources: {\n                darwin: {\n                    CFBundleDisplayName : pkg.name,\n                    CFBundleIdentifier  : pkg.name,\n                    CFBundleName        : pkg.name,\n                    CFBundleVersion     : pkg.version,\n                    icon                : './app/images/icon/icon-512x512.icns'\n                },\n                win: {\n                    'version-string'    : pkg.version,\n                    'file-version'      : pkg.version,\n                    'product-version'   : pkg.version,\n                    'icon'              : './resources/app/images/icon/icon-120x120.png'\n                }\n            }\n        }));\n    };\n};\n"
  },
  {
    "path": "gulps/htmlManifest.js",
    "content": "'use strict';\n\n/**\n * Generate HTML5 cache manifest file.\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src([\n            'dist/**',\n            '!dist/bower_components/MathJax/**'\n        ], {base: './dist'})\n        .pipe(plugins.manifest({\n            hash         : true,\n            preferOnline : true,\n            network      : ['*'],\n            filename     : 'app.appcache',\n            exclude      : [\n                'app.appcache',\n                'dropbox.html'\n            ],\n            timestamp    : true,\n            master       : ['index.html'],\n            fallback     : ['/ 404.html']\n        }))\n        .pipe(gulp.dest('dist'));\n    };\n};\n"
  },
  {
    "path": "gulps/htmlmin.js",
    "content": "'use strict';\n\n/**\n * Minify HTML files.\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src('./app/*.html')\n        .pipe(plugins.replace('<html class=\"no-js\">', '<html manifest=\"app.appcache\" class=\"no-js\">'))\n        .pipe(plugins.htmlmin({\n            collapseWhitespace : true,\n            quoteCharacter     : '\\''\n        }))\n        .pipe(gulp.dest('./dist'));\n    };\n};\n"
  },
  {
    "path": "gulps/jshint.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src([\n            './app/scripts/**/*.js',\n            './app/scripts/*.js'\n        ])\n        .pipe(plugins.jshint())\n        .pipe(plugins.jshint.reporter('jshint-stylish'))\n        .pipe(plugins.jshint.reporter('fail'));\n    };\n};\n"
  },
  {
    "path": "gulps/jsonlint.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src([\n            'app/manifest.webapp',\n            'bower.json',\n            'package.json',\n            'app/**/*.json'\n        ])\n        .pipe(plugins.jsonlint())\n        .pipe(plugins.jsonlint.failAfterError())\n        .pipe(plugins.jsonlint.reporter());\n    };\n};\n"
  },
  {
    "path": "gulps/less.js",
    "content": "'use strict';\nvar path = require('path');\n\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src('./app/styles/*/main.less')\n        .pipe(plugins.less({\n            paths: [\n                path.join(__dirname, 'less', 'includes'),\n                './app/bower_components/'\n            ]\n        }))\n        // Unique names for theme files\n        .pipe(plugins.rename(function(path) {\n            path.basename = path.dirname;\n            path.dirname  = '';\n            return path;\n        }))\n        // Autoprefixer\n        .pipe(plugins.autoprefixer({\n            browsers : ['> 5%'],\n            cascade  : false,\n            remove   : false,\n            add      : true\n        }))\n        .pipe(gulp.dest('./app/styles/'))\n        .pipe(plugins.browserSync.stream());\n    };\n};\n"
  },
  {
    "path": "gulps/mobile.js",
    "content": "'use strict';\n\nvar cordova = require('cordova-lib').cordova.raw,\n    fs      = require('fs'),\n    devip   = require('dev-ip');\n\nmodule.exports = function(gulp, plug, pkg) {\n\n    /**\n     * Use livereload server when debugging.\n     */\n    function useServer() {\n        return plug.replace(\n            '<content src=\"index.html',\n            '<content src=\"http://' + devip()[0] + ':' + (plug.util.env.port || 9000)\n        );\n    }\n\n    gulp.task('mobile:clean', function() {\n        return plug.del(['./cordova']);\n    });\n\n    gulp.task('mobile:copy', function() {\n        return gulp.src(['./dist/**/*'], {base: 'dist'})\n        .pipe(gulp.dest('./cordova/www'));\n    });\n\n    gulp.task('mobile:config', function() {\n        return gulp.src(['./app/config.xml'])\n        .pipe(plug.replace('{{version}}', pkg.version))\n        .pipe(!plug.util.env.dev ?  plug.util.noop() : useServer())\n        .pipe(gulp.dest('./cordova'));\n    });\n\n    /**\n     * Copy build.json in which one can store keystore configs.\n     */\n    gulp.task('mobile:buildConfig', function() {\n        try {\n            fs.statSync('./build.json');\n            return gulp.src(['./build.json'])\n            .pipe(gulp.dest('./cordova'));\n        } catch (e) {\n            return plug.util.noop();\n        }\n    });\n\n    gulp.task('mobile:replace', function() {\n        return gulp.src('./cordova/www/index.html')\n        .pipe(plug.replace('<!-- {{cordova}} -->', '<script src=\"cordova.js\"></script>'))\n        .pipe(plug.replace(' manifest=\\'app.appcache\\'', ''))\n\n        // Use different name for debugging\n        .pipe(\n            plug.util.env.dev ?\n                plug.replace('<name>Laverna', '<name>Laverna dev') :\n                plug.util.noop()\n        )\n        .pipe(gulp.dest('./cordova/www'));\n    });\n\n    gulp.task('mobile:cordova', function() {\n        process.chdir('./cordova');\n\n        return cordova.platform('add', ['android'])\n        .then(function() {\n            return cordova.plugins('add', [\n                'cordova-plugin-crosswalk-webview',\n                'cordova-plugin-inappbrowser',\n                'cordova-plugin-file',\n            ]);\n        });\n    });\n\n    gulp.task('mobile:create', plug.sequence(\n        'mobile:clean',\n        'build',\n        ['mobile:copy', 'mobile:config', 'mobile:buildConfig'],\n        'mobile:replace',\n        'mobile:cordova'\n    ));\n\n    gulp.task('mobile:build', ['mobile:create'], function() {\n        return cordova.build({\n            platforms: ['android'],\n            options  : {\n                release: !plug.util.env.dev\n            }\n        });\n    });\n\n};\n"
  },
  {
    "path": "gulps/mocha.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src('./test/index.html')\n        .pipe(plugins.mochaPhantomjs())\n        .once('error', function(err) {\n            console.log('Error', err.toString());\n            process.exit(1);\n        });\n    };\n};\n"
  },
  {
    "path": "gulps/nightwatch.js",
    "content": "'use strict';\n\n/**\n * Run UI tests.\n * Example of running tests:\n * gulp nightwatch --env [test_settings profile]\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        gulp.task('nightwatch', function() {\n            return gulp.src('./test/spec-ui/test.js', {read: false})\n            .pipe(plugins.nightwatch({\n                configFile : './test/nightwatch.json',\n                cliArgs    : [\n                    // '--test ' + './test/spec-ui/tests/apps/notes/show.js',\n                    '--env ' + (plugins.util.env.env || 'default')\n                ]\n            }))\n            .once('error', function(err) {\n                console.log('Error', err.toString());\n                process.exit(1);\n            });\n        });\n    };\n};\n"
  },
  {
    "path": "gulps/npm.js",
    "content": "'use strict';\n\nmodule.exports = function(gulp, plugins) {\n\n    gulp.task('npm:install', function() {\n        return gulp.src('./release/laverna/package.json')\n        .pipe(plugins.shell(\n            'cd ./release/laverna && npm install --production && cd ../../'\n        ));\n    });\n\n};\n"
  },
  {
    "path": "gulps/prism.js",
    "content": "'use strict';\n\n/**\n * Concatenate all Prism.js dependencies/languages into one file.\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+abap+actionscript+apacheconf+apl+applescript+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+groovy+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+julia+keyman+kotlin+latex+less+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+puppet+pure+python+q+qore+r+jsx+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+verilog+vhdl+vim+wiki+yaml */\n        var components = 'markup+css+clike+javascript+abap+actionscript+apacheconf+apl+applescript+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+groovy+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+julia+keyman+kotlin+latex+less+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+puppet+pure+python+q+qore+r+jsx+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+verilog+vhdl+vim+wiki+yaml',\n            files      = [];\n\n        components = components.split('+');\n        components.unshift('core');\n\n        components.forEach(function(item) {\n            files.push(\n                './app/bower_components/prism/components/prism-' + item + '.js'\n            );\n        });\n\n        return gulp.src(files)\n        .pipe(plugins.concat('bundle.js'))\n        .pipe(gulp.dest('./app/bower_components/prism'));\n    };\n};\n"
  },
  {
    "path": "gulps/require.js",
    "content": "'use strict';\n\n/**\n * Minify JS files.\n * Require.js config example:\n * https://github.com/jrburke/r.js/blob/master/build/example.build.js\n */\nmodule.exports = function(gulp, plugins) {\n    return function() {\n        return gulp.src('./app/scripts/main.js')\n        // Require.js optimizer\n        .pipe(plugins.requirejsOptimize({\n            name           : 'main',\n            baseUrl        : './app/scripts',\n            mainConfigFile : './app/scripts/main.js',\n            optimize       : 'none',\n            paths: {\n                'workers': 'empty:'\n            },\n            exclude        : [\n                'markdown-it',\n                'markdown-it-san',\n                'markdown-it-hash',\n                'markdown-it-math',\n                'modules/markdown/libs/markdown-it-file',\n                'modules/markdown/libs/markdown-it-task',\n                'modules/markdown/libs/markdown-it',\n                'modules/markdown/libs/markdown',\n\n                'migrate',\n                'helpers/db',\n                'helpers/migrate',\n\n                'backbone',\n                'backbone.radio',\n                'jquery',\n                'prism',\n                'q',\n                'underscore',\n                'localforage',\n                'dropbox',\n                'classes/sjcl',\n                'sjcl',\n                'remotestorage',\n                'tv4',\n                'bluebird',\n                'mathjax',\n            ],\n            include        : [\n                // Because settings views are loaded dynamically\n                'apps/settings/show/views/encryption',\n                'apps/settings/show/views/general',\n                'apps/settings/show/views/importExport',\n                'apps/settings/show/views/keybindings',\n                'apps/settings/show/views/profiles',\n                'apps/settings/show/views/showView',\n                'apps/settings/show/views/sync',\n                'apps/settings/show/views/modules',\n\n                'apps/settings/module/app',\n                'apps/settings/module/controller',\n                'views/loader',\n\n                'backbone.sync',\n                'backbone.noworker.sync',\n                'modules/remotestorage/module',\n                'modules/dropbox/module'\n            ],\n            findNestedDependencies : true,\n            generateSourceMaps     : true,\n            useStrict              : true,\n            wrapShim               : true,\n            preserveLicenseComments: true,\n            wrap                   : true\n        }))\n        .pipe(plugins.util.env.dev ? plugins.util.noop() : plugins.uglify({\n            preserveComments: 'license'\n        }))\n        .pipe(gulp.dest('./dist/scripts'));\n    };\n};\n"
  },
  {
    "path": "gulps/serve.js",
    "content": "'use strict';\n\n/**\n * Live reload server.\n */\nmodule.exports = function(gulp, plugins) {\n\n    gulp.task('serve:watch', function() {\n        // Compile LESS files\n        gulp.watch('app/styles/**/*.less', ['less']);\n\n        gulp.watch([\n            'app/**/*.html',\n            'app/scripts/**/*.js',\n            'app/locales/**/*.json',\n        ]).on('change', plugins.browserSync.reload);\n    });\n\n    gulp.task('serve:start', function() {\n        return plugins.browserSync.init({\n            server : (plugins.util.env.root || 'app'),\n            port   : (plugins.util.env.port || 9000),\n        });\n    });\n\n};\n"
  },
  {
    "path": "karma.conf.js",
    "content": "// Karma configuration\n// Generated on Fri May 23 2016 17:23:01 GMT+0600 (+06)\n\nmodule.exports = function(config) {\n    'use strict';\n\n    config.set({\n\n        // base path that will be used to resolve all patterns (eg. files, exclude)\n        basePath: './',\n\n\n        // frameworks to use\n        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n        frameworks: ['mocha', 'requirejs'],\n\n\n        // list of files / patterns to load in the browser\n        files: [\n            'test/spec/test.js',\n            {pattern: 'app/**/*.js', included: false, served: true},\n            {pattern: 'app/scripts/modules/modules.json', included: false, served: true},\n            {pattern: 'app/locales/**/*.json', included: false, served: true},\n            {pattern: 'test/spec/**/*.js', included: false, served: true},\n            {pattern: 'test/bower_components/**/*.js', included: false, served: true}\n        ],\n\n\n        // list of files to exclude\n        exclude: [\n        ],\n\n\n        // preprocess matching files before serving them to the browser\n        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n        preprocessors: {\n            'app/scripts/**/*.js': ['coverage']\n        },\n\n        coverageReporter: {\n            reporters: [\n                {type: 'lcov', dir: 'coverage/', subdir: '.'},\n            ]\n        },\n\n\n        // test results reporter to use\n        // possible values: 'dots', 'progress'\n        // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n        reporters: ['progress', 'coverage'],\n\n\n        // web server port\n        port: 9876,\n\n\n        // enable / disable colors in the output (reporters and logs)\n        colors: true,\n\n\n        // level of logging\n        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n        logLevel: config.LOG_INFO,\n\n\n        // enable / disable watching file and executing tests whenever any file changes\n        autoWatch: true,\n\n\n        // start these browsers\n        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher\n        browsers: ['PhantomJS'],\n\n\n        // Continuous Integration mode\n        // if true, Karma captures browsers, runs the tests and exits\n        singleRun: false,\n\n        // Concurrency level\n        // how many browser should be started simultaneous\n        concurrency: Infinity\n    });\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"laverna\",\n  \"version\": \"0.7.51\",\n  \"license\": \"MPL-2.0\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Laverna/laverna\"\n  },\n  \"scripts\": {\n    \"start\": \"node ./server.js\",\n    \"electron\": \"NODE_ENV=dev electron .\",\n    \"karma\": \"./node_modules/.bin/karma start\",\n    \"test:karma\": \"./node_modules/.bin/karma start --single-run\"\n  },\n  \"main\": \"electron.js\",\n  \"dependencies\": {\n    \"chokidar\": \"^1.6.0\",\n    \"electron-context-menu\": \"^0.6.0\",\n    \"electron-window-state\": \"^3.0.3\",\n    \"finalhandler\": \"^0.5.0\",\n    \"glob\": \"^7.0.5\",\n    \"graceful-fs\": \"^4.1.5\",\n    \"minimist\": \"^1.2.0\",\n    \"open\": \"0.0.5\",\n    \"serve-static\": \"^1.11.1\"\n  },\n  \"devDependencies\": {\n    \"browser-sync\": \"^2.14.0\",\n    \"chai\": \"^3.5.0\",\n    \"cordova-lib\": \"^6.3.0\",\n    \"del\": \"^2.2.1\",\n    \"dev-ip\": \"^1.0.1\",\n    \"gulp\": \"^3.9.1\",\n    \"gulp-autoprefixer\": \"^3.1.0\",\n    \"gulp-clean-css\": \"^2.0.12\",\n    \"gulp-concat\": \"^2.6.0\",\n    \"gulp-electron\": \"0.1.3\",\n    \"gulp-htmlmin\": \"^2.0.0\",\n    \"gulp-jshint\": \"^2.0.1\",\n    \"gulp-jsonlint\": \"^1.1.2\",\n    \"gulp-less\": \"^3.1.0\",\n    \"gulp-load-plugins\": \"^1.2.4\",\n    \"gulp-manifest\": \"^0.1.1\",\n    \"gulp-mocha-phantomjs\": \"^0.11.0\",\n    \"gulp-mocha-selenium\": \"^1.0.0\",\n    \"gulp-nightwatch\": \"^0.3.0\",\n    \"gulp-rename\": \"^1.2.2\",\n    \"gulp-replace\": \"^0.5.4\",\n    \"gulp-requirejs-optimize\": \"^1.2.0\",\n    \"gulp-sequence\": \"^0.4.5\",\n    \"gulp-shell\": \"^0.5.2\",\n    \"gulp-uglify\": \"^1.5.4\",\n    \"gulp-util\": \"^3.0.7\",\n    \"jshint\": \"^2.9.2\",\n    \"jshint-stylish\": \"^2.2.0\",\n    \"karma\": \"^1.1.2\",\n    \"karma-coverage\": \"^1.1.1\",\n    \"karma-mocha\": \"^1.1.1\",\n    \"karma-phantomjs-launcher\": \"^1.0.1\",\n    \"karma-requirejs\": \"^1.0.0\",\n    \"merge-stream\": \"^1.0.0\",\n    \"nightwatch\": \"^0.9.6\"\n  },\n  \"engines\": {\n    \"node\": \">=0.8.0\"\n  },\n  \"optionalDependencies\": {},\n  \"resolutions\": {\n    \"underscore\": \"~1.7.0\"\n  }\n}\n"
  },
  {
    "path": "preload.js",
    "content": "// Requirejs compatibility in Electron app\nconsole.log('Preloading...');\nif (window.require) {\n    window.requireNode = window.require;\n    window.moduleNode  = window.module;\n\n    window.currentDir = __dirname;\n    window.nodeDir    = __dirname + '/node_modules/';\n\n    window.require = undefined;\n    window.module  = undefined;\n}\n"
  },
  {
    "path": "server.js",
    "content": "'use strict';\n\nvar finalhandler = require('finalhandler'),\n    http         = require('http'),\n    serveStatic  = require('serve-static'),\n    serve,\n    server;\n\nserve  = serveStatic(__dirname + '/dist', {index: ['index.html']});\n\nserver = http.createServer(function(req, res) {\n    var done = finalhandler(req, res);\n    serve(req, res, done);\n});\n\nmodule.exports = function(port) {\n    console.log('Server is running on port: ' + port);\n    return server.listen(port);\n};\n"
  },
  {
    "path": "test/.bowerrc",
    "content": "{\n    \"directory\": \"bower_components\"\n}\n"
  },
  {
    "path": "test/bower.json",
    "content": "{\n  \"name\": \"laverna\",\n  \"private\": true,\n  \"dependencies\": {\n    \"chai\": \"~3.5.0\",\n    \"mocha\": \"~2.5.3\",\n    \"chai-jquery\": \"~2.0.1\",\n    \"chai-as-promised\": \"^5.3.0\",\n    \"sinon-chai\": \"^2.8.0\",\n    \"sinonjs\": \"^1.17.1\"\n  },\n  \"devDependencies\": {}\n}\n"
  },
  {
    "path": "test/index.html",
    "content": "<!doctype html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n    <title>Mocha Spec Runner</title>\n    <link rel=\"stylesheet\" href=\"bower_components/mocha/mocha.css\">\n</head>\n<body>\n    <div id=\"mocha\" class=\"-scroll\"></div>\n\n    <script src=\"bower_components/mocha/mocha.js\"></script>\n    <script src=\"bower_components/chai/chai.js\"></script>\n    <script data-main=\"spec/test\" src=\"../app/bower_components/requirejs/require.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "test/nightwatch.json",
    "content": "{\n    \"src_folders\": [\"test/spec-ui/tests\"],\n    \"output_folder\"          : \".reports\",\n    \"custom_commands_path\"   : \"test/spec-ui/commands\",\n    \"custom_assertions_path\" : \"\",\n    \"page_objects_path\"      : \"\",\n    \"globals_path\"           : \"\",\n\n    \"selenium\" : {\n        \"start_process\" : false,\n        \"server_path\"   : \"\",\n        \"log_path\"      : \"\",\n        \"host\"          : \"127.0.0.1\",\n        \"port\"          : 4444,\n        \"cli_args\"      : {\n            \"webdriver.chrome.driver\" : \"\",\n            \"webdriver.ie.driver\"     : \"\"\n        }\n    },\n\n    \"test_settings\" : {\n        \"ci\": {\n            \"launch_url\"            :  \"http://localhost:9000\",\n\n            \"selenium_port\"         :  80,\n            \"selenium_host\"         :  \"ondemand.saucelabs.com\",\n            \"username\"              :  \"${SAUCE_USERNAME}\",\n            \"access_key\"            :  \"${SAUCE_ACCESS_KEY}\",\n\n            \"end_session_on_fail\"   :  true,\n            \"skip_testcases_on_fail\":  true,\n            \"silent\"                :  true,\n            \"desiredCapabilities\"   :  {\n                \"name\"              :  \"CI\",\n                \"videoUploadOnPass\" :  false,\n                \"browserName\"       :  \"firefox\",\n                \"javascriptEnabled\" :  true,\n                \"acceptSslCerts\"    :  true,\n                \"tunnel-identifier\" :  \"${TRAVIS_JOB_NUMBER}\",\n                \"build\"             :  \"${TRAVIS_BUILD_NUMBER}\"\n            }\n        },\n\n        \"default\" : {\n            \"launch_url\"            :  \"http://localhost:9000\",\n            \"selenium_port\"         :  4444,\n            \"selenium_host\"         :  \"localhost\",\n            \"silent\"                :  true,\n            \"end_session_on_fail\"   :  true,\n            \"skip_testcases_on_fail\":  true,\n            \"screenshots\"           :  {\n                \"enabled\"           :  false,\n                \"path\"              :  \"\"\n            },\n            \"desiredCapabilities\"   :  {\n                \"browserName\"       :  \"firefox\",\n                \"javascriptEnabled\" :  true,\n                \"acceptSslCerts\"    :  true\n            }\n        },\n\n        \"chrome\" : {\n            \"desiredCapabilities\": {\n                \"browserName\"       : \"chrome\",\n                \"javascriptEnabled\" : true,\n                \"acceptSslCerts\"    : true\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test/spec/app.js",
    "content": "/* global define, describe, it, expect, before, after */\ndefine([\n    'underscore',\n    'i18next',\n    'backbone',\n    'backbone.radio',\n    'app',\n    'marionette'\n], function(_, i18n, Backbone, Radio, App) {\n    'use strict';\n\n    describe('app', function() {\n\n        before(function() {\n            Backbone.history.stop();\n        });\n\n        after(function() {\n            Backbone.history.stop();\n        });\n\n        describe('Object', function() {\n\n            it('is an object', function() {\n                expect(App).to.be.an('object');\n            });\n\n            it('is instance of Marionette.Application', function() {\n                expect(App).to.be.instanceof(Backbone.Marionette.Application);\n            });\n\n            it('has \"startSubApp\" method', function() {\n                expect(App.startSubApp).to.be.a('function');\n            });\n\n        });\n\n        describe('.startSubApp()', function() {\n            var apps;\n\n            before(function() {\n                apps = {};\n                apps.test = App.module('AppTest', {startWithParent: false});\n                apps.test2 = App.module('AppTest2', {startWithParent: false});\n            });\n\n            it('can start a sub app', function(done) {\n                apps.test.once('before:start', function(args) {\n                    expect(args.myArg).to.be.equal(true);\n                    done();\n                });\n                App.startSubApp('AppTest', {myArg: true});\n            });\n\n            it('currentApp changes', function() {\n                expect(App.currentApp).to.be.equal(apps.test);\n            });\n\n            it('it will not start the same subApp again', function() {\n                expect(App.currentApp).to.be.equal(apps.test);\n                expect(App.startSubApp('AppTest')).not.to.be.equal(true);\n            });\n\n            it('stops previous app if current app is not modal', function(done) {\n                expect(App.currentApp).to.be.equal(apps.test);\n                expect(App.currentApp.options.modal).not.to.be.equal(true);\n\n                apps.test.once('stop', done);\n                expect(App.startSubApp('AppTest2')).to.be.equal(true);\n            });\n\n        });\n\n        describe('overwrites Marionette renderrer', function() {\n            var render;\n\n            before(function(done) {\n                render = Backbone.Marionette.Renderer.render;\n                $.t = i18n.t.bind(i18n);\n                i18n.init({}, function() { done(); });\n            });\n\n            it('has i18n method', function() {\n                expect(render(_.template('{{i18n(\"key\")}}')))\n                    .to.be.equal('key');\n            });\n\n            it('has cleanXSS method', function() {\n                expect(render(_.template('{{cleanXSS(\"Hello\")}}')))\n                    .to.be.equal('Hello');\n            });\n\n            it('has stripTags method', function() {\n                expect(render(_.template('{{stripTags(\"<b>Hello</b>\")}}')))\n                    .to.be.equal('Hello');\n            });\n\n        });\n\n        describe('Replies', function() {\n\n            it('`app:current` returns current subApp', function() {\n                var sApp = App.module('AppTesting', {startWithParent: false});\n                App.startSubApp('AppTesting');\n                expect(Radio.request('global', 'app:current')).to.be.equal(sApp);\n            });\n\n            it('`device` executes Device method', function() {\n                expect(Radio.request('global', 'device', 'mobile'))\n                    .to.be.equal(false);\n            });\n\n            it('`platform`', function() {\n                expect(Radio.request('global', 'platform')).to.be.a('string');\n            });\n\n            it('`use:webworkers`', function() {\n                expect(typeof Radio.request('global', 'use:webworkers'))\n                    .to.be.equal('boolean');\n            });\n\n        });\n\n        describe('Events', function() {\n\n            it('triggers `app:init` on `before:start`', function(done) {\n                Radio.once('global', 'app:init', done);\n                App.trigger('before:start');\n            });\n\n            it('triggers `app:start` on `start`', function(done) {\n                Radio.once('global', 'app:start', done);\n                App.trigger('start');\n            });\n\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/confirm/show/view.js",
    "content": "/* global define, chai, describe, before, it */\ndefine([\n    'underscore',\n    'apps/confirm/show/view'\n], function(_, View) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Confirm view', function() {\n        var view;\n\n        function checkEvent(needEv, done) {\n            view.once('click', function(ev) {\n                expect(ev).to.be.equal(needEv);\n                done();\n            });\n        }\n\n        before(function() {\n            view = new View({\n                el: $('<div>'),\n                content: 'A **markdown** content.'\n            });\n            view.render();\n        });\n\n        describe('triggers events', function() {\n\n            it('cancel if cancel button is clicked', function(done) {\n                checkEvent('cancel', done);\n                view.$('[data-event=\"cancel\"]').click();\n            });\n\n            it('confirm if confirm button is clicked', function(done) {\n                checkEvent('confirm', done);\n                view.$('[data-event=\"confirm\"]').click();\n            });\n\n            it('cancel on `hidden.modal`', function(done) {\n                checkEvent('cancel', done);\n                view.trigger('hidden.modal');\n            });\n\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/confirm/test.js",
    "content": "/* global define */\ndefine([\n    'spec/apps/confirm/show/view'\n], function() {\n    'use strict';\n\n});\n"
  },
  {
    "path": "test/spec/apps/encryption/encrypt/controller.js",
    "content": "/* global define */\ndefine([\n    'apps/encryption/encrypt/controller',\n    'collections/notes',\n    'spec/apps/encryption/encrypt/view'\n], function(Controller) {\n    'use strict';\n\n    console.log('Controller', Controller);\n\n});\n"
  },
  {
    "path": "test/spec/apps/encryption/encrypt/view.js",
    "content": "/* global define, chai, describe, it, before, beforeEach */\ndefine([\n    'jquery',\n    'underscore',\n    'backbone.radio',\n    'apps/encryption/encrypt/view'\n], function($, _, Radio, View) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Encryption view', function() {\n        var view,\n            configs;\n\n        before(function() {\n            configs = {\n                encrypt           : 1,\n                encryptPass       : '1',\n                encryptSalt       : '',\n                encryptIter       : '1000',\n                encryptTag        : '64',\n                encryptKeySize    : '128'\n            };\n            configs.encryptBackup = _.clone(configs);\n\n            view = new View({\n                el      : $('<div>'),\n                configs : _.clone(configs)\n            });\n\n            try {\n                view.render();\n            } catch (e) {\n                console.log(e);\n            }\n        });\n\n        it('is ok', function() {\n            expect(typeof view).to.be.equal('object');\n            expect(view instanceof View).to.be.equal(true);\n            expect(typeof view.options.configs).to.be.equal('object');\n        });\n\n        describe('Old password', function() {\n\n            beforeEach(function() {\n                view.options.configs = _.clone(configs);\n            });\n\n            it('shows it', function() {\n                expect(view.$el).to.have('input[name=oldpass]');\n            });\n\n            it('shows it if encryption was disabled', function(done) {\n                view.options.configs.encrypt = 0;\n                view.options.configs.encryptBackup.encrypt = 1;\n\n                view.once('render', function() {\n                    expect(view.$el).to.have('input[name=oldpass]');\n                    done();\n                });\n                view.render();\n            });\n\n            it('hides it if password wasn\\'t changed', function(done) {\n                delete view.options.configs.encryptBackup.encryptPass;\n\n                view.once('render', function() {\n                    expect(view.$el).not.to.have('input[name=oldpass]');\n                    done();\n                });\n                view.render();\n            });\n\n            it('hides it if backup password is empty', function(done) {\n                view.options.configs.encryptBackup.encryptPass = '';\n\n                view.once('render', function() {\n                    expect(view.$el).not.to.have('input[name=oldpass]');\n                    done();\n                });\n                view.render();\n            });\n\n            it('hides it if encryption wasn\\'t used before', function(done) {\n                view.options.configs.encryptBackup.encrypt = 0;\n\n                view.once('render', function() {\n                    expect(view.$el).not.to.have('input[name=oldpass]');\n                    done();\n                });\n                view.render();\n            });\n\n            it('shows it if encryption password was changed', function(done) {\n                view.options.configs.encryptBackup.encrypt = 1;\n                view.options.configs.encryptBackup.encryptPass = '2';\n\n                view.once('render', function() {\n                    expect(view.$el).to.have('input[name=oldpass]');\n                    done();\n                });\n                view.render();\n            });\n\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/encryption/test.js",
    "content": "/* global define */\ndefine([\n    'spec/apps/encryption/encrypt/controller'\n], function() {\n    'use strict';\n\n});\n"
  },
  {
    "path": "test/spec/apps/help/about/view.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'underscore',\n    'apps/help/about/view'\n], function(_, View) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('About view', function() {\n        var view;\n\n        before(function() {\n            view = new View({\n                el: $('<div>'),\n                appVersion : 1\n            });\n\n            view.render();\n        });\n\n        describe('render()', function() {\n            it('ok', function() {\n                expect(view.options.appVersion).to.be.equal(1);\n                expect(typeof view.behaviors).to.be.equal('object');\n                expect(view.behaviors.hasOwnProperty('ModalBehavior')).to.be.equal(true);\n            });\n        });\n\n        describe('events', function() {\n            it('triggers view:redirect event when it\\'s closed', function(done) {\n                view.on('redirect', function() {\n                    done();\n                });\n                view.trigger('hidden.modal');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/help/show/view.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'underscore',\n    'collections/configs',\n    'apps/help/show/view'\n], function(_, Configs, View) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Shortcuts help view', function() {\n        var view,\n            configs;\n\n        before(function() {\n            configs = new Configs();\n            configs.resetFromJSON(configs.configNames);\n            configs.reset(configs.shortcuts());\n\n            view = new View({\n                el: $('<div>'),\n                collection: configs\n            });\n\n            view.render();\n        });\n\n        describe('render()', function() {\n            it('ok', function() {\n                expect(typeof view.collection).to.be.equal('object');\n                expect(typeof view.behaviors).to.be.equal('object');\n                expect(view.behaviors.hasOwnProperty('ModalBehavior')).to.be.equal(true);\n            });\n\n            it('shows all shortcuts', function() {\n                expect(view.$el).to.have('td');\n                expect($('tbody tr', view.$el).length).to.be.equal(configs.length);\n            });\n        });\n\n        describe('events', function() {\n            it('triggers view:redirect event when it\\'s closed', function(done) {\n                view.on('redirect', function() {\n                    done();\n                });\n                view.trigger('hidden.modal');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/help/test.js",
    "content": "/* global define */\ndefine([\n    'spec/apps/help/about/view',\n    'spec/apps/help/show/view'\n], function() {\n    'use strict';\n\n});\n"
  },
  {
    "path": "test/spec/apps/navbar/show/view.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'underscore',\n    'jquery',\n    'backbone.radio',\n    'collections/configs',\n    'collections/notebooks',\n    'apps/navbar/show/view'\n], function(_, $, Radio, Configs, Notebooks, NavbarView) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Navbar view', function() {\n        var view,\n            configs;\n\n        before(function() {\n            configs = new Configs();\n            configs.resetFromJSON(_.extend({}, configs.configNames, {cloudStorage: 'dropbox'}));\n\n            view = new NavbarView({\n                el         : $('<div>'),\n                collection : configs,\n                profiles   : configs.get('appProfiles'),\n                notebooks  : new Notebooks(),\n                args       : {},\n            });\n\n            view.render();\n        });\n\n        describe('instantiated', function() {\n            it('should exist', function() {\n                expect(view instanceof NavbarView).to.be.equal(true);\n            });\n        });\n\n        describe('Triggers events', function() {\n\n            it('`sync`:`start` event', function(done) {\n                Radio.replyOnce('sync', 'start', function() {\n                    done();\n                });\n                view.$('#header--sync').trigger('click');\n            });\n\n            it('`search:hidden` after search form is submitted', function(done) {\n                Radio.once('global', 'search:hidden', function() {\n                    done();\n                });\n                view.$('#header--search').trigger('submit');\n            });\n\n            it('`search:submit` after search form is submitted and text is not empty', function(done) {\n                view.once('search:submit', function() {\n                    done();\n                });\n                view.ui.search.val('Test');\n                view.$('#header--search').trigger('submit');\n            });\n\n        });\n\n        describe('Shows sync status', function() {\n\n            it('`sync`:`start` event', function(done) {\n                Radio.once('sync', 'start', function() {\n                    expect(view.ui.sync).to.have.class('animate-spin');\n                    done();\n                });\n                Radio.trigger('sync', 'start');\n            });\n\n            it('`sync`:`stop` event', function(done) {\n                Radio.once('sync', 'stop', function() {\n                    expect(view.ui.sync).not.to.have.class('animate-spin');\n                    done();\n                });\n                Radio.trigger('sync', 'stop');\n            });\n        });\n\n        describe('Search form', function() {\n\n            it('is invisible', function() {\n                expect(view.$('#sidebar--nav').hasClass('-search')).to.equal(false);\n            });\n\n            it('will appear if the search button is clicked', function(done) {\n                Radio.once('global', 'search:shown', function() {\n                    expect(view.$('#sidebar--nav').hasClass('-search')).to.equal(true);\n                    done();\n                });\n\n                view.$('#header--sbtn').click();\n            });\n\n        });\n    });\n\n});\n"
  },
  {
    "path": "test/spec/apps/navbar/test.js",
    "content": "/* global define */\ndefine([\n    'spec/apps/navbar/show/view'\n], function() {\n    'use strict';\n\n});\n"
  },
  {
    "path": "test/spec/apps/notebooks/list/layout.js",
    "content": "/* global chai, define, describe, before, it, Mousetrap */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'collections/configs',\n    'apps/notebooks/list/views/layout',\n    'apps/notebooks/list/views/notebooksComposite',\n    'apps/notebooks/list/views/tagsComposite',\n    'collections/notebooks',\n    'collections/tags',\n    'mousetrap'\n], function(_, Radio, Configs, Layout, NotebooksComp, TagsComp, Notebooks, Tags) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('NotebookLayout view', function() {\n        var layout,\n            notebookView,\n            tagsView,\n            notebooks,\n            tags;\n\n        before(function() {\n\n            notebooks = new Notebooks([{id: '1', name: 'Test'}]);\n            tags = new Tags([{id: '1', name: 'Test'}]);\n\n            // Show layout\n            layout = new Layout({\n                configs   : Configs.prototype.configNames,\n                notebooks : notebooks,\n                tags      : tags\n            });\n            layout.render();\n\n            // Show notebooks list\n            notebookView = new NotebooksComp({\n                collection: notebooks\n            });\n\n            // Show tags list\n            tagsView = new TagsComp({\n                collection: tags\n            });\n\n            // Render lists in layout\n            layout.notebooks.show(notebookView);\n            layout.tags.show(tagsView);\n        });\n\n        describe('Keybindings', function() {\n\n            it('triggers \"navigate:next\" event on \"j\" key', function(done) {\n                layout[layout.activeRegion].currentView\n                    .once('navigate:next', done);\n\n                Mousetrap.trigger('j');\n            });\n\n            it('triggers \"navigate:previous\" event on \"k\" key', function(done) {\n                layout[layout.activeRegion].currentView\n                    .once('navigate:previous', done);\n\n                Mousetrap.trigger('k');\n            });\n\n            it('\"o\", opens active element', function(done) {\n                Radio.replyOnce('uri', 'navigate', function(url) {\n                    var $a = layout.$('.list-group-item.active');\n                    expect($a.length !== 0).to.be.equal(true);\n                    expect(url).to.be.equal($a.attr('href'));\n                    done();\n                });\n\n                layout.$('.list-group-item:first').addClass('active');\n                Mousetrap.trigger('o');\n            });\n\n            it('\"e\" redirects to edit page', function(done) {\n                Radio.replyOnce('uri', 'navigate', function(url) {\n                    var $a = layout.$('.list-group-item.active').parent().find('.edit-link:first');\n\n                    expect($a.length !== 0).to.be.equal(true);\n\n                    expect(url).to.be.equal($a.attr('href'));\n                    done();\n                });\n\n                layout.$('.list-group-item:first').addClass('active');\n                Mousetrap.trigger('e');\n            });\n\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notebooks/list/views/notebookList.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'marionette',\n    'models/notebook',\n    'collections/notebooks',\n    'apps/notebooks/list/views/notebooksItem',\n    'apps/notebooks/list/views/notebooksComposite'\n], function(Marionette, Notebook, Notebooks, ItemView, ListView) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    // Fragments don't work properly in PhantomJS\n    if (!document.createDocumentFragment().getElementById) {\n        ListView.prototype.showCollection = function() {\n            var frag = document.createDocumentFragment(),\n                fake = {\n                    appendChild: function(el) {\n                        return frag.appendChild(el);\n                    },\n                    getElementById: function(ar) {\n                        return frag.querySelector('#' + ar);\n                    },\n                };\n\n            Marionette.CompositeView.prototype.showCollection.apply(this, arguments);\n            this.children.each(function(view) {\n                this.attachFragment(this, view, fake);\n            }, this);\n            this.$(this.childViewContainer).append(frag);\n        };\n    }\n\n    describe('NotebooksItem view', function() {\n        var notebook,\n            view;\n\n        before(function() {\n            notebook = new Notebook({\n                id  : '1',\n                name: 'This is notebook name'\n            });\n\n            view = new ItemView({\n                el: $('<div>'),\n                model: notebook\n            });\n\n            view.render();\n        });\n\n        describe('instantiated', function() {\n            it('should exist', function() {\n                expect(view instanceof ItemView).to.be.equal(true);\n            });\n\n            it('model was passed', function() {\n                expect(view.model).to.be.equal(notebook);\n            });\n        });\n\n        describe('render()', function() {\n            it('is not empty', function() {\n                expect(view.$el.html() !== '').to.be.equal(true);\n            });\n\n            it('model', function() {\n                var regx = new RegExp(notebook.get('name'), 'gi');\n                expect(regx.test(view.$el.html())).to.be.equal(true);\n            });\n\n            it('makes itself active after `focus` event', function(done) {\n                notebook.once('focus', function() {\n                    var $item = view.$('.list-group-item[data-id=' + notebook.get('id') + ']');\n                    expect($item.hasClass('active')).to.be.equal(true);\n                    done();\n                });\n                notebook.trigger('focus');\n            });\n        });\n\n    });\n\n    describe('NotebooksComposite view', function() {\n        var collection,\n            view,\n            models = [];\n\n        before(function() {\n\n            for (var i = 1; i <= 2; i++) {\n                models.push({\n                    id: 'notebook-' + i.toString(),\n                    name: 'Notebook #' + i.toString(),\n                    parentId: 'notebook-' + (i.toString() - 1)\n                });\n            }\n\n            collection = new Notebooks(models);\n\n            // Instantiate a CompositeView\n            view = new ListView({\n                el: $('<div>'),\n                collection: collection\n            });\n            view.render();\n        });\n\n        describe('instantiated', function() {\n            it('collection is not empty', function() {\n                expect(view.collection.length).to.be.equal(models.length);\n            });\n\n            it('is was rendered', function() {\n                expect(view.isRendered).to.be.equal(true);\n                expect(view.$el.html() !== '').to.be.equal(true);\n            });\n        });\n\n        describe('nested view', function() {\n\n            it('items are nested', function() {\n                var div,\n                    id;\n\n                for (var i = 0; i < collection.length; i++) {\n                    id = collection.at(i).get('id');\n                    div = view.$el.find('.list--nested[data-id=' + id + '] .list-group-item');\n                    expect(div.length).to.be.equal(collection.length - 1 - i);\n                }\n            });\n\n        });\n\n        describe('behaviors', function() {\n            it('has a behavior', function() {\n                expect(view.behaviors.hasOwnProperty('CompositeBehavior')).to.be.equal(true);\n            });\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notebooks/list/views/tagList.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'require',\n    'jquery',\n    'models/tag',\n    'collections/tags',\n    'apps/notebooks/list/views/tagsItem',\n    'apps/notebooks/list/views/tagsComposite'\n], function(require, $, Tag, Tags, ItemView, ListView) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('TagsItem view', function() {\n        var tag,\n            view;\n\n        before(function() {\n            tag = new Tag({\n                id  : '1',\n                name: 'This is tag name'\n            });\n\n            view = new ItemView({\n                el: $('<div>'),\n                model: tag\n            });\n\n            view.render();\n        });\n\n        describe('instantiated', function() {\n            it('should exist', function() {\n                expect(view instanceof ItemView).to.be.equal(true);\n            });\n\n            it('model was passed', function() {\n                expect(view.model).to.be.equal(tag);\n            });\n        });\n\n        describe('render()', function() {\n            it('is not empty', function() {\n                expect(view.$el.html() !== '').to.be.equal(true);\n            });\n\n            it('model', function() {\n                var regx = new RegExp(tag.get('name'), 'gi');\n                expect(regx.test(view.$el.html())).to.be.equal(true);\n            });\n\n            it('makes itself active after `focus` event', function(done) {\n                tag.once('focus', function() {\n                    var $item = view.$('.list-group-item[data-id=' + tag.get('id') + ']');\n                    expect($item.hasClass('active')).to.be.equal(true);\n                    done();\n                });\n                tag.trigger('focus');\n            });\n        });\n    });\n\n    describe('TagsComposite view', function() {\n        var collection,\n            view,\n            models = [];\n\n        before(function() {\n\n            for (var i = 1; i <= 10; i++) {\n                models.push({\n                    id: i.toString(),\n                    name: 'tag #' + i.toString()\n                });\n            }\n\n            collection = new Tags(models);\n\n            // Instantiate a CompositeView\n            view = new ListView({\n                el: $('<div>'),\n                collection: collection\n            });\n            view.render();\n        });\n\n        describe('instantiated', function() {\n            it('collection is not empty', function() {\n                expect(view.collection.length).to.be.equal(models.length);\n            });\n\n            it('is was rendered', function() {\n                expect(view.isRendered).to.be.equal(true);\n                expect(view.$el.html() !== '').to.be.equal(true);\n            });\n        });\n\n        describe('behaviors', function() {\n\n            it('has a behavior', function() {\n                expect(view.behaviors.hasOwnProperty('CompositeBehavior')).to.be.equal(true);\n            });\n\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notebooks/notebooksForm/formView.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'require',\n    'underscore',\n    'jquery',\n    'models/notebook',\n    'collections/notebooks',\n    'apps/notebooks/form/notebook/formView'\n], function(require, _, $, Notebook, Notebooks, FormView) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Notebook\\'s form view', function() {\n        var notebook,\n            notebooks,\n            models = [],\n            view;\n\n        before(function() {\n            notebook = new Notebook({\n                id  : 1,\n                name: 'Notebook'\n            });\n\n            for (var i = 0; i < 10; i++) {\n                models.push(_.extend(\n                    {},\n                    notebook.toJSON(),\n                    { id: i + 1, parentId: i}\n                ));\n            }\n\n            notebooks = new Notebooks(models, {\n                comparator: 'name'\n            });\n            notebook = notebooks.get(2);\n\n            view = new FormView({\n                el: $('<div>'),\n                collection: notebooks,\n                model: notebook,\n            });\n\n            view.render();\n        });\n\n        describe('View is rendered', function() {\n            it('is rendered', function() {\n                expect(view instanceof FormView).to.be.equal(true);\n                expect(view.$el.length).not.to.be.equal(0);\n            });\n\n            it('model was passed', function() {\n                expect(view.model).to.be.equal(notebook);\n            });\n\n            it('collection was passed', function() {\n                expect(view.collection).to.be.equal(notebooks);\n            });\n\n            it('Shows notebook\\'s name', function() {\n                expect(view.ui.name).to.have.value(notebook.get('name'));\n            });\n\n            it('Shows notebook\\'s parent', function() {\n                var par = $(':selected', view.ui.parentId);\n                expect(view.ui.parentId).to.have(':selected');\n                expect(par.val()).to.be.equal(notebook.get('parentId'));\n            });\n\n            it('Shows validation errors', function(done) {\n                notebook.once('invalid', function(model, errors) {\n                    _.forEach(errors, function(err) {\n                        expect(view.ui[err].parent()).to.have.class('has-error');\n                        if (errors[errors.length - 1] === err) {\n                            done();\n                        }\n                    });\n                });\n                notebook.save({\n                    'name': '',\n                    'parentId': notebook.get('id')\n                });\n            });\n        });\n\n        describe('Triggers events', function() {\n            it('view:save when user submits the form', function(done) {\n                view.once('save', function() {\n                    done();\n                });\n                $('.form-horizontal', view.$el).submit();\n            });\n\n            it('view:save when user OK button', function(done) {\n                view.once('save', function() {\n                    done();\n                });\n                $('.ok', view.$el).click();\n            });\n\n            it('view:redirect', function(done) {\n                view.once('redirect', function() {\n                    done();\n                });\n                view.trigger('hidden.modal');\n            });\n\n            it('view:close when user hits cancel', function(done) {\n                view.once('close', function() {\n                    done();\n                });\n                $('.cancelBtn', view.$el).click();\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notebooks/tagsForm/tagForm.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'require',\n    'underscore',\n    'jquery',\n    'models/tag',\n    'apps/notebooks/form/tag/formView'\n], function(require, _, $, Tag, FormView) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Tag form view', function() {\n        var tag,\n            view;\n\n        before(function() {\n            tag = new Tag({\n                id  : 1,\n                name: 'Tag name'\n            });\n\n            view = new FormView({\n                el: $('<div>'),\n                model: tag,\n                data: tag.toJSON()\n            });\n\n            view.render();\n        });\n\n        describe('View is rendered', function() {\n            it('is rendered', function() {\n                expect(view instanceof FormView).to.be.equal(true);\n                expect(view.$el.length).not.to.be.equal(0);\n            });\n\n            it('model was passed', function() {\n                expect(view.model).to.be.equal(tag);\n            });\n\n            it('Shows tags name', function() {\n                expect(view.ui.name).to.have.value(tag.get('name'));\n            });\n\n            it('Shows validation errors', function(done) {\n                tag.once('invalid', function(model, errors) {\n                    _.forEach(errors, function(err) {\n                        expect(view.ui[err].parent()).to.have.class('has-error');\n                        if (errors[errors.length - 1] === err) {\n                            done();\n                        }\n                    });\n                });\n                tag.save({\n                    'name': ''\n                });\n            });\n        });\n\n        describe('Triggers events', function() {\n            it('view:save when user submits the form', function(done) {\n                view.once('save', function() {\n                    done();\n                });\n                $('.form-horizontal', view.$el).submit();\n            });\n\n            it('view:save when user hits OK button', function(done) {\n                view.once('save', function() {\n                    done();\n                });\n                $('.ok', view.$el).click();\n            });\n\n            it('view:redirect', function(done) {\n                view.once('redirect', function() {\n                    done();\n                });\n                view.trigger('hidden.modal');\n            });\n\n            it('view:close when user hits cancel', function(done) {\n                view.once('close', function() {\n                    done();\n                });\n                $('.cancelBtn', view.$el).click();\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notebooks/test.js",
    "content": "/* global define */\ndefine([\n    'spec/apps/notebooks/list/layout',\n    'spec/apps/notebooks/list/views/notebookList',\n    'spec/apps/notebooks/list/views/tagList',\n    'spec/apps/notebooks/notebooksForm/formView',\n    'spec/apps/notebooks/tagsForm/tagForm'\n], function() {\n    'use strict';\n\n});\n"
  },
  {
    "path": "test/spec/apps/notes/list/app.js",
    "content": "/* global define, describe, before, after, it, chai */\ndefine([\n    'backbone',\n    'backbone.radio',\n    'apps/notes/list/listApp',\n    'spec/apps/notes/list/controller'\n], function(Backbone, Radio, ListApp) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Notes list module', function() {\n\n        before(function() {\n            ListApp.start();\n        });\n\n        after(function() {\n            ListApp.controller.view = new Backbone.View();\n            ListApp.stop();\n        });\n\n        it('is an object', function() {\n            expect(typeof ListApp).to.be.equal('object');\n        });\n\n        it('instantiates a controller on start', function() {\n            expect(typeof ListApp.controller).to.be.equal('object');\n        });\n\n        it('complies to `filter` command', function(done) {\n            Radio.replyOnce('notes', 'filter', function() {\n                done();\n                return [];\n            });\n\n            Radio.request('appNote', 'filter', {filter: 'favorite'});\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notes/list/controller.js",
    "content": "/* global define, describe, before, after, it, chai */\ndefine([\n    'backbone.radio',\n    'apps/notes/list/controller',\n    'spec/apps/notes/list/views/noteSidebar'\n], function(Radio, Controller) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    function overwriteMethod(name) {\n        var fnc = Controller.prototype[name];\n        Controller.prototype[name] = function() {\n            if (typeof arguments[0] === 'function') {\n                return arguments[0]();\n            }\n            fnc.apply(this, arguments);\n        };\n    }\n\n    describe('Notes list controller', function() {\n        var controller;\n\n        before(function() {\n            overwriteMethod('onModelActive');\n            overwriteMethod('navigate');\n\n            controller = new Controller({});\n        });\n\n        after(function() {\n            if (controller.view) {\n                controller.destroy();\n            }\n        });\n\n        it('is an object', function() {\n            expect(typeof controller).to.be.equal('object');\n        });\n\n        it('is an instance of Controller', function() {\n            expect(controller instanceof Controller).to.be.equal(true);\n        });\n\n        describe('triggers', function() {\n\n            it('requests notes on `notes` channel', function(done) {\n                Radio.replyOnce('notes', 'filter', function() {\n                    done();\n                    return [];\n                });\n                controller.filter({filter: 'favourite'});\n            });\n\n            it('event `navigate` on `global` channel', function(done) {\n                Radio.once('global', 'navigate', function() {\n                    done();\n                });\n                controller.navigate();\n            });\n\n        });\n\n        describe('Listens to events', function() {\n\n            it('on `appNote` channel to `model:active` event', function(done) {\n                Radio.trigger('appNote', 'model:active', done);\n            });\n\n            it('on `notes` channel to `model:navigate` event', function(done) {\n                Radio.trigger('notes', 'model:navigate', done);\n            });\n\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notes/list/views/noteSidebar.js",
    "content": "/* global define, describe, before, after, it, chai */\ndefine([\n    'backbone.radio',\n    'Mousetrap',\n    'apps/notes/list/views/noteSidebar',\n    'collections/notes',\n    'spec/apps/notes/list/views/noteSidebarItem'\n], function(Radio, Mousetrap, View, Notes) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    function overwriteMethod(name) {\n        var fnc = View.prototype[name];\n        View.prototype[name] = function() {\n            if (typeof arguments[0] === 'function') {\n                return arguments[0]();\n            }\n            fnc.apply(this, arguments);\n        };\n    }\n\n    describe('Notes list composite view', function() {\n        var view;\n\n        before(function() {\n            overwriteMethod('modelFocus');\n\n            Radio.replyOnce('global', 'configs', function() {\n                return {\n                    navigateBottom : 'j',\n                    navigateTop    : 'k',\n                };\n            });\n\n            view = new View({\n                el         : $('<div/>'),\n                args       : {},\n                collection : new Notes([\n                    {title : 'Title'},\n                    {title : 'Title2'}\n                ])\n            });\n        });\n\n        after(function() {\n            view.trigger('destroy');\n        });\n\n        it('is an object', function() {\n            expect(typeof view).to.be.equal('object');\n        });\n\n        it('is an instance of View', function() {\n            expect(view instanceof View).to.be.equal(true);\n        });\n\n        describe('Triggers', function() {\n            it('request `route:args` on `appNote` channel', function(done) {\n                Radio.replyOnce('appNote', 'route:args', function() {\n                    done();\n                });\n                view.onBeforeRender();\n            });\n\n            it('event `navigate:link` on `global` channel', function(done) {\n                Radio.once('global', 'navigate:link', function() {\n                    done();\n                });\n                view.navigatePage(1);\n            });\n        });\n\n        describe('Listens to', function(done) {\n            it('event `model:navigate` on `notes` channel', function(done) {\n                Radio.trigger('notes', 'model:navigate', done);\n            });\n\n            it('keyboard event \"j\"', function(done) {\n                Radio.once('notes', 'model:navigate', function() {\n                    done();\n                });\n                Mousetrap.trigger(view.configs.navigateBottom);\n            });\n\n            it('keyboard event \"k\"', function(done) {\n                Radio.once('notes', 'model:navigate', function() {\n                    done();\n                });\n                Mousetrap.trigger(view.configs.navigateTop);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notes/list/views/noteSidebarItem.js",
    "content": "/* global define, describe, before, after, it, chai */\ndefine([\n    'backbone',\n    'backbone.radio',\n    'apps/notes/list/views/noteSidebarItem',\n    'collections/notes'\n], function(Backbone, Radio, View, Notes) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Notes list item view', function() {\n        var view,\n            notes;\n\n        before(function() {\n            notes = new Notes([\n                {id: 1, title: 'Title', content: 'Content'}\n            ]);\n\n            Radio.reply('editor', 'content:html', function(text) {\n                return text;\n            });\n\n            view = new View({\n                el   : $('<div/>'),\n                model: notes.get(1),\n                args : {}\n            });\n\n            view.render();\n        });\n\n        after(function() {\n            Radio.stopReplying('editor', 'content:html');\n            view.trigger('destroy');\n        });\n\n        it('is an object', function() {\n            expect(typeof view).to.be.equal('object');\n        });\n\n        it('instance of View', function() {\n            expect(view instanceof View).to.be.equal(true);\n        });\n\n        describe('Listens to model events', function() {\n            it('change', function(done) {\n                view.once('render', function() {\n                    done();\n                });\n                view.model.trigger('change');\n            });\n\n            it('focus', function(done) {\n                var $item = view.$('.list-group-item');\n                view.model.once('focus', function() {\n                    expect($item).to.have.class('active');\n                    done();\n                });\n                view.model.trigger('focus');\n            });\n\n        });\n\n        describe('Triggers', function() {\n            it('event \"scroll:top\" to itself', function(done) {\n                view.once('scroll:top', function() {\n                    done();\n                });\n                view.model.trigger('focus');\n            });\n\n            it('command \"save\" to \"notes\" channel when a user clicks on favorite button', function(done) {\n                Radio.complyOnce('notes', 'save', function(model) {\n                    expect(model).to.be.equal(view.model);\n                    done();\n                });\n                view.ui.favorite.trigger('click');\n            });\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/notes/test.js",
    "content": "/* global define, describe, before, after, it, chai */\ndefine([\n    'app',\n    'apps/notes/appNote',\n    'spec/apps/notes/list/app'\n], function(App, AppNote) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('AppNote module', function() {\n        before(function() {\n        });\n\n        after(function() {\n        });\n\n        it('is an object', function() {\n            expect(typeof AppNote).to.be.equal('object');\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/settings/show/formBehavior.js",
    "content": "/* global define */\ndefine([\n    'jquery'\n], function($) {\n    'use strict';\n\n    return function testChangeTrigger (view, done) {\n        var inputs = $('input, select, textarea', view.$el),\n            triggered = 0;\n\n        view.collection.on('new:value', function() {\n            if (triggered === (inputs.length - 1)) {\n                done();\n                return;\n            }\n            triggered++;\n        });\n\n        inputs.trigger('change');\n    };\n});\n"
  },
  {
    "path": "test/spec/apps/settings/show/views/basic.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'underscore',\n    'jquery',\n    'spec/apps/settings/show/formBehavior',\n    'apps/settings/show/views/general',\n    'collections/configs',\n    'text!locales/locales.json'\n], function(_, $, settingsBehavior, Basic, Configs, locales) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Basic settings view', function() {\n        var view,\n            configs;\n\n        before(function(done) {\n            configs = new Configs();\n            configs.resetFromJSON(Configs.prototype.configNames);\n\n            locales = _.keys(JSON.parse(locales));\n            view = new Basic({\n                el: $('<div>'),\n                collection: configs,\n                useDefault: configs.get('useDefaultConfigs')\n            });\n\n            view.render();\n            done();\n        });\n\n        describe('Instantiated', function() {\n            it('should exist', function() {\n                expect(view instanceof Basic).to.be.equal(true);\n                expect(view.collection instanceof Configs).to.be.equal(true);\n            });\n\n            it('has behaviors', function() {\n                expect(typeof view.behaviors).to.be.equal('object');\n                expect(view.behaviors.hasOwnProperty('FormBehavior')).to.be.equal(true);\n            });\n\n            it('shows language options', function() {\n                expect(locales.length > 0).to.be.equal(true);\n                expect($('#appLang option', view.$el).length).to.be.equal(locales.length);\n            });\n        });\n\n        describe('Triggers events', function() {\n            it('collection:new:value when something has changed', function(done) {\n                settingsBehavior(view, done);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/settings/show/views/importExport.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'underscore',\n    'backbone.radio',\n    'apps/settings/show/views/importExport',\n    'collections/configs'\n], function(_, Radio, Import, Configs) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Import and export tab view', function() {\n        var view;\n\n        before(function() {\n            view = new Import({\n                el: $('<div>'),\n                collection: new Configs()\n            });\n            view.render();\n        });\n\n        describe('instantiated', function() {\n            it('ok', function() {\n                expect(view instanceof Import).to.be.equal(true);\n                expect(view.$el.length !== 0).to.be.equal(true);\n            });\n        });\n\n        describe('Triggers requests', function() {\n\n            it('`export` on `importExport` channel', function(done) {\n                Radio.replyOnce('importExport', 'export', function() {\n                    done();\n                });\n                view.ui.exportData.trigger('click');\n            });\n\n            /*\n            it('`import` on `importExport` channel', function(done) {\n                Radio.replyOnce('importExport', 'import', function() {\n                    done();\n                });\n                view.ui.importData.trigger('change', {target: {files: ['1']}});\n            });\n            */\n\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/settings/show/views/profiles.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'jquery',\n    'collections/configs',\n    'apps/settings/show/views/profiles'\n], function($, Configs, View) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Profiles settings view', function() {\n        var view,\n            configs;\n\n        before(function(done) {\n            var profiles;\n            configs = new Configs();\n            configs.resetFromJSON(Configs.prototype.configNames);\n\n            profiles = configs.get('appProfiles');\n            profiles.set('value', JSON.stringify(['notes-db', 'new']));\n\n            view = new View({\n                el: $('<div>'),\n                collection: configs,\n                profiles: profiles\n            });\n\n            view.render();\n            done();\n        });\n\n        describe('is not empty', function() {\n            it('ok', function() {\n                expect(view.$el).not.be.empty();\n                expect(view.$el).to.have('input');\n                console.log(view.options.profiles.get('value'));\n            });\n\n            it('shows profiles list', function() {\n                expect(view.$('table').length !== 0).to.be.equal(true);\n                expect(view.$('tr').length === 4).to.be.equal(true);\n            });\n\n        });\n\n        describe('Triggers events', function() {\n            it('create:profile', function(done) {\n                var e = $.Event('keypress');\n                e.which = 13;\n\n                view.once('create:profile', function(name) {\n                    expect(name).to.be.equal(view.ui.profile.val());\n                    done();\n                });\n\n                view.ui.profile.val('profileI');\n                view.ui.profile.trigger(e);\n            });\n\n            it('remove:profile', function(done) {\n                console.log($('.removeProfile'));\n                view.once('remove:profile', function(name) {\n                    expect(name !== '').to.be.equal(true);\n                    done();\n                });\n\n                view.$('.removeProfile').trigger('click');\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/settings/show/views/shortcuts.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'jquery',\n    'spec/apps/settings/show/formBehavior',\n    'collections/configs',\n    'apps/settings/show/views/keybindings'\n], function($, settingsBehavior, Configs, Shortcuts) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Shortcuts settings view', function() {\n        var configs,\n            view;\n\n        before(function(done) {\n            configs = new Configs();\n            configs.resetFromJSON(Configs.prototype.configNames);\n\n            view = new Shortcuts({\n                el: $('<div>'),\n                collection: configs\n            });\n\n            view.render();\n            done();\n        });\n\n        describe('Instantiated', function() {\n            it('ok', function() {\n                expect(view instanceof Shortcuts).to.be.equal(true);\n                expect(view.collection instanceof Configs).to.be.equal(true);\n                expect(view.collection.length > 0).to.be.equal(true);\n            });\n\n            it('has behaviors', function() {\n                expect(typeof view.behaviors).to.be.equal('object');\n                expect(view.behaviors.hasOwnProperty('FormBehavior')).to.be.equal(true);\n            });\n\n            it('is not empty', function() {\n                expect(view.$el).not.to.be.empty();\n                expect(view.$el).to.have('input');\n            });\n        });\n\n        describe('Triggers events', function() {\n            it('collection:new:value when something has changed', function(done) {\n                settingsBehavior(view, done);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/settings/show/views/showView.js",
    "content": "/* global chai, define, describe, before, it */\ndefine([\n    'underscore',\n    'jquery',\n    'collections/configs',\n    'apps/settings/show/views/showView',\n], function(_, $, Configs, View) {\n    'use strict';\n\n    var expect = chai.expect;\n\n    describe('Settings view', function() {\n        var view,\n            configs;\n\n        before(function() {\n            configs = new Configs();\n            configs.resetFromJSON(Configs.prototype.configNames);\n\n            view = new View({\n                el: $('<div>'),\n                collection : configs,\n                tab: 'general',\n                args: {}\n            });\n\n            view.render();\n        });\n\n        it('should exist', function() {\n            expect(view instanceof View).to.be.equal(true);\n        });\n\n        it('has \"content\" region', function() {\n            expect(typeof view.regions).to.be.equal('object');\n            expect(typeof view.regions.content).to.be.equal('string');\n        });\n\n        it('has \"ContentBehavior\"', function() {\n            expect(typeof view.behaviors).to.be.equal('object');\n            expect(typeof view.behaviors.ContentBehavior).to.be.equal('object');\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/apps/settings/test.js",
    "content": "/* global define */\ndefine([\n    'spec/apps/settings/show/views/basic',\n    'spec/apps/settings/show/views/importExport',\n    'spec/apps/settings/show/views/profiles',\n    'spec/apps/settings/show/views/shortcuts',\n    'spec/apps/settings/show/views/showView'\n], function() {\n    'use strict';\n\n});\n"
  },
  {
    "path": "test/spec/backbone.sync.js",
    "content": "/*jshint expr: true*/\ndefine([\n    'sinon',\n    'underscore',\n    'q',\n    'backbone.sync',\n    'models/note',\n    'collections/notes'\n], function(sinon, _, Q, bSync, Model, Collection) {\n    'use strict';\n\n    describe('backbone.sync', function() {\n        var sandbox,\n            sync;\n\n        before(function() {\n            sync = _.clone(bSync);\n        });\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('object', function() {\n\n            it('is an object', function() {\n                expect(sync).to.be.an('object');\n            });\n\n            it('stores promises', function() {\n                expect(sync.promises).to.be.an('object');\n            });\n\n        });\n\n        describe('.listenToWorker()', function() {\n\n            after(function() {\n                sync.workerPromise = null;\n            });\n\n            it('resolves the worker promise', function() {\n                sync.workerPromise = {resolve: sandbox.stub()};\n                sync.listenToWorker({data: {msg: 'ready'}});\n                expect(sync.workerPromise.resolve).called;\n            });\n\n            it('resolves a request promise', function(done) {\n                sync.promises['test-id'] = Q.defer();\n\n                sync.promises['test-id'].promise.then(function(data) {\n                    expect(data).to.be.equal('Data');\n                    done();\n                });\n\n                sync.listenToWorker({data: {\n                    msg: 'done', promiseId : 'test-id', data: 'Data'\n                }});\n            });\n\n            it('rejects a failed WebWorker', function(done) {\n                sync.promises['test-id'] = Q.defer();\n\n                sync.promises['test-id'].promise.fail(function(data) {\n                    expect(data).to.be.equal('Error');\n                    done();\n                });\n\n                sync.listenToWorker({data: {\n                    msg: 'fail', promiseId: 'test-id', data: 'Error'\n                }});\n            });\n\n        });\n\n        describe('.read()', function() {\n\n            it('it calls .find() if the 1st argument is model', function() {\n                sandbox.stub(sync, 'find');\n                sync.read({id: 'test-id'}, {profile: '1'});\n                expect(sync.find).calledWith({id: 'test-id'}, {profile: '1'});\n            });\n\n            it('it calls .findAll() if the 1st argument is not model', function() {\n                sandbox.stub(sync, 'findAll');\n                sync.read({models: [{id: 'test'}]}, {profile: '1'});\n                expect(sync.findAll).calledWith({models: [{id: 'test'}]}, {profile: '1'});\n            });\n\n        });\n\n        describe('.create()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(sync, 'save');\n            });\n\n            it('calls .save()', function() {\n                sync.create();\n                expect(sync.save).called;\n            });\n\n            it('provides all arguments', function() {\n                sync.create({id: 'test-id-1'}, {profile: '1'});\n                expect(sync.save).calledWith({id: 'test-id-1'}, {profile: '1'});\n            });\n\n        });\n\n        describe('.update()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(sync, 'save');\n            });\n\n            it('calls .save()', function() {\n                sync.update();\n                expect(sync.save).called;\n            });\n\n            it('provides all arguments', function() {\n                sync.update({id: 'test-id-1'}, {profile: '1'});\n                expect(sync.save).calledWith({id: 'test-id-1'}, {profile: '1'});\n            });\n\n        });\n\n        describe('.s4()', function() {\n\n            it('returns a string', function() {\n                expect(sync.s4()).to.be.a('string');\n                expect(sync.s4()).to.have.property('length').equal(4);\n            });\n\n        });\n\n        describe('.guid()', function() {\n\n            it('returns a string', function() {\n                expect(sync.guid()).to.be.a('string');\n                expect(sync.guid()).to.have.property('length').equal((8 * 4) + 4);\n            });\n\n            it('calls .s4() 8 times', function() {\n                sandbox.spy(sync, 's4');\n                sync.guid();\n                expect(sync.s4).callCount(8);\n            });\n\n        });\n\n        describe('.save()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new Model();\n                sandbox.stub(sync, '_emit');\n            });\n\n            it('generates an ID for a model if does not have one', function() {\n                sandbox.spy(sync, 'guid');\n                sandbox.spy(model, 'set');\n\n                sync.save(model, {});\n                expect(sync.guid).called;\n                expect(model.set).calledAfter(sync.guid);\n            });\n\n            it('calls ._emit()', function() {\n                sync.save(model, {});\n                expect(sync._emit).called;\n            });\n\n            it('tells ._emit() to trigger `save` message', function() {\n                sync.save(model, {});\n                expect(sync._emit).calledWithMatch('save', {});\n            });\n\n            it('provides data for saving', function() {\n                model.set('title', 'Test title');\n                sync.save(model, {profile: 'test'});\n\n                expect(sync._emit).calledWithMatch('save', {\n                    id      : model.id,\n                    data    : model.toJSON(),\n                    options : {\n                        profile     : 'test',\n                        storeName   : model.storeName,\n                        encryptKeys : model.encryptKeys\n                    }\n                });\n            });\n\n        });\n\n        describe('.find()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new Model({id: 'test-id'});\n                sandbox.stub(sync, '_emit').returns(Q.resolve({title: 'Test'}));\n            });\n\n            it('tells .emit() to trigger `find` message', function() {\n                sync.find(model, {});\n\n                expect(sync._emit).calledWithMatch('find');\n                expect(sync._emit).calledWithMatch('find', {\n                    id      : model.id,\n                    options : {\n                        profile   : model.profileId,\n                        storeName : model.storeName\n                    }\n                });\n            });\n\n            it('changes the models attributes to fetched values', function() {\n                sandbox.spy(model, 'set');\n                return sync.find(model, {}).then(function() {\n                    expect(model.set).calledWithMatch({title: 'Test'});\n                });\n            });\n\n        });\n\n        describe('.findAll()', function() {\n            var coll;\n\n            beforeEach(function() {\n                coll = new Collection();\n                sandbox.stub(sync, '_emit').returns(Q.resolve([{title: 'Test'}]));\n            });\n\n            it('tells ._emit() to trigger `findAll` message', function() {\n                sync.findAll(coll, {conditions: {t: 1}});\n\n                expect(sync._emit).calledWithMatch('findAll', {\n                    options: {\n                        conditions: {t: 1},\n                        storeName : coll.storeName,\n                        profile   : coll.profileId\n                    }\n                });\n            });\n\n            it('adds data to the collection', function() {\n                sandbox.spy(coll, 'add');\n                return sync.findAll(coll, {}).then(function() {\n                    expect(coll.add).calledWithMatch([{title: 'Test'}]);\n                });\n            });\n\n        });\n\n        describe('._emit()', function() {\n\n            after(function() {\n                delete sync.worker;\n            });\n\n            beforeEach(function() {\n                sync.promises = [];\n                sandbox.stub(sync, 'guid').returns('test-worker-id');\n                sync.worker = {postMessage: sandbox.stub()};\n            });\n\n            it('it generates an ID for the worker promise', function() {\n                sync._emit('test', {});\n                expect(sync.guid).called;\n            });\n\n            it('caches the worker promise', function() {\n                expect(sync.promises['test-worker-id']).to.be.an('undefined');\n                sync._emit('test', {});\n                expect(sync.promises['test-worker-id']).to.be.an('object');\n            });\n\n            it('sends the message to the worker', function() {\n                sync._emit('test', {data: {id: '1'}});\n\n                expect(sync.worker.postMessage).calledWithMatch({\n                    msg       : 'test',\n                    data      : {data : {id : '1'}},\n                    promiseId : 'test-worker-id'\n                });\n            });\n\n            it('returns a promise', function() {\n                expect(sync._emit('test', {})).to.have.property('promiseDispatch');\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/classes/encryption.js",
    "content": "/* global define, requirejs, describe, before, beforeEach, after, afterEach, chai, it */\ndefine(function(require) {\n    'use strict';\n\n    var _       = require('underscore'),\n        Q       = require('q'),\n        Radio   = require('backbone.radio'),\n        sjcl    = require('sjcl'),\n        Encrypt = require('classes/encryption'),\n        expect  = chai.expect,\n        helpers = require('spec/classes/helpers'),\n        configs,\n        encrypt,\n        keys;\n\n    function setKey(callback) {\n        encrypt.saveSecureKey('1')\n        .then(function() {\n            keys = encrypt.keys;\n            callback();\n        })\n        .fail(function(e) {\n            console.error('Error:', e);\n        });\n    }\n\n    function requirePromise(name) {\n        var defer = Q.defer();\n        requirejs([name], function(res) {\n            defer.resolve(res);\n        });\n        return defer.promise;\n    }\n\n    function encryptModel(name) {\n        return requirePromise('models/' + name)\n        .then(function(model) {\n            model = new model({id: 'random'}); // jshint ignore:line\n\n            _.each(model.encryptKeys, function(key) {\n                model.set(key, 'random ' + key);\n            });\n\n            return encrypt.encryptModel(model)\n            .then(function() {\n                _.each(model.encryptKeys, function(key) {\n                    model.set(key, 'not random ' + key);\n                });\n\n                expect(typeof sjcl.json.decode(model.get('encryptedData'))).to.be.equal('object');\n                return model;\n            });\n        });\n    }\n\n    function decryptModel(model) {\n        return encrypt.decryptModel(model)\n        .then(function() {\n            _.each(model.encryptKeys, function(key) {\n                expect(model.get(key)).to.be.equal('random ' + key);\n                expect(model.get(key).match(/^\\{.*\\}$/)).not.to.be.equal(true);\n            });\n        });\n    }\n\n    function encryptModels(name) {\n        return requirePromise('collections/' + name)\n        .then(function(Collection) {\n            var collection = new Collection(),\n                keys       = Collection.prototype.model.prototype.encryptKeys;\n\n            function createData(i) {\n                var data = {};\n                _.each(keys, function(key) {\n                    data[key] = 'Random ' + key + ' ' + i;\n                });\n                return data;\n            }\n\n            for (var i = 0; i < 20; i++) {\n                collection.add(createData(i));\n            }\n\n            return encrypt.encryptModels(collection).thenResolve(collection);\n        })\n        .then(function(collection) {\n            collection.each(function(model) {\n                expect(typeof sjcl.json.decode(model.get('encryptedData'))).to.be.equal('object');\n\n                _.each(keys, function(key) {\n                    model.set(key, '{\"string\": \"string\"}');\n                });\n            });\n            return collection;\n        });\n    }\n\n    function decryptModels(collection) {\n        return encrypt.decryptModels(collection)\n        .then(function() {\n            collection.each(function(model) {\n                _.each(model.encryptKeys, function(key) {\n                    expect(typeof model.get(key)).to.be.equal('string');\n                    expect(model.get(key).match(/^\\{.*\\}$/)).not.to.be.equal(true);\n                });\n            });\n            return;\n        });\n    }\n\n    describe('classes/encryption.js', function() {\n\n        beforeEach(function() {\n            configs = {\n                'encrypt'        : '1',\n                'encryptPass'    : sjcl.hash.sha256.hash('1'),\n                'encryptSalt'    : sjcl.random.randomWords(5, 0),\n                'encryptIter'    : '1000',\n                'encryptTag'     : '64',\n                'encryptKeySize' : '128',\n            };\n\n            Radio.reply('configs', 'get:object', configs);\n            encrypt = new Encrypt();\n        });\n\n        after(function() {\n            Radio.stopReplying('configs', 'get:object');\n        });\n\n        it('is an object', function() {\n            expect(typeof encrypt).to.be.equal('object');\n        });\n\n        it('uses configs', function() {\n            expect(typeof encrypt.configs).to.be.equal('object');\n            expect(_.isEqual(encrypt.configs, configs)).to.be.equal(true);\n        });\n\n        describe('randomize()', function() {\n\n            it('exists', function() {\n                expect(typeof encrypt.randomize).to.be.equal('function');\n            });\n\n            it('returns a string', function() {\n                expect(typeof encrypt.randomize(5, 0)).to.be.equal('string');\n            });\n\n            it('uses \"number\" parameter', function() {\n                expect(encrypt.randomize(1, 0).length).to.be.equal(8);\n                expect(encrypt.randomize(2, 0).length).to.be.equal(8 * 2);\n                expect(encrypt.randomize(5, 0).length).to.be.equal(8 * 5);\n            });\n        });\n\n        describe('changeConfigs()', function() {\n            var old;\n\n            beforeEach(function() {\n                old = _.clone(configs);\n            });\n\n            it('uses new configs', function() {\n                configs.encrypt = 0;\n                encrypt.changeConfigs(configs);\n\n                expect(_.isEqual(old, encrypt.configs)).to.be.equal(false);\n                expect(encrypt.configs.encrypt).to.be.equal(0);\n            });\n\n            it('extends from the previous configs', function() {\n                configs.encrypt     = 1;\n                configs.encryptIter = 2000;\n\n                encrypt.changeConfigs(configs);\n\n                expect(encrypt.configs.encrypt).to.be.equal(1);\n                expect(encrypt.configs.encryptIter).to.be.equal(2000);\n\n                expect(_.isEqual(\n                    _.omit(old, 'encrypt', 'encryptIter'),\n                    _.omit(encrypt.configs, 'encrypt', 'encryptIter')\n                )).to.be.equal(true);\n            });\n\n        });\n\n        describe('checkAuth()', function() {\n\n            afterEach(function() {\n                encrypt.configs = configs;\n            });\n\n            it('returns false if the user is not authorized', function() {\n                encrypt.keys = {};\n                if (window.sessionStorage) {\n                    window.sessionStorage.clear();\n                }\n\n                expect(encrypt.checkAuth()).to.be.equal(false);\n            });\n\n            it('returns true if encryption is disabled', function() {\n                encrypt.configs.encrypt = 0;\n                expect(encrypt.checkAuth()).to.be.equal(true);\n            });\n\n            it('returns true if encryption password is empty', function() {\n                encrypt.configs.encryptPass = '';\n                expect(encrypt.checkAuth()).to.be.equal(true);\n            });\n\n            it('returns {isChanged:true} if encryption settings have changed', function() {\n                encrypt.configs.encryptBackup = {encryptPass: ''};\n\n                var res = encrypt.checkAuth();\n                expect(typeof res).to.be.equal('object');\n                expect(res.isChanged).to.be.equal(true);\n            });\n\n        });\n\n        describe('checkPassword()', function() {\n\n            it('returns a promise', function() {\n                expect(typeof encrypt.checkPassword(1)).to.be.equal('object');\n                expect(typeof encrypt.checkPassword(1).promiseDispatch).to.be.equal('function');\n            });\n\n            it('returns true if the password is correct', function(done) {\n                encrypt.configs.encryptPassword = sjcl.hash.sha256.hash('1');\n\n                encrypt.checkPassword('1')\n                .then(function(res) {\n                    expect(res).to.be.equal(true);\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('returns false if the password is incorrect', function(done) {\n                encrypt.checkPassword('2')\n                .then(function(res) {\n                    expect(res).to.be.equal(false);\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('saveSecureKey()', function() {\n\n            it('returns a promise', function() {\n                expect(typeof encrypt.saveSecureKey(1)).to.be.equal('object');\n                expect(typeof encrypt.saveSecureKey(1).promiseDispatch).to.be.equal('function');\n            });\n\n            it('caches keys in memory', function(done) {\n                encrypt.saveSecureKey('1')\n                .then(function() {\n                    expect(typeof encrypt.keys).to.be.equal('object');\n                    expect(typeof encrypt.keys.key).to.be.equal('object');\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('saves keys in sessionStorage', function(done) {\n                if (!window.sessionStorage) {\n                    return;\n                }\n\n                encrypt.saveSecureKey('1')\n                .then(function() {\n                    return encrypt._getSession();\n                })\n                .then(function(keys) {\n                    expect(typeof keys).to.be.equal('object');\n                    expect(typeof keys.key).to.be.equal('object');\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('saves PBKDF2, not plain text password', function(done) {\n                encrypt.saveSecureKey('1')\n                .then(function() {\n                    expect(encrypt.keys.key).not.to.be.equal('1');\n                    expect(encrypt.keys.hexKey).not.to.be.equal('1');\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n        });\n\n        describe('deleteSecureKey()', function() {\n            var keys;\n\n            before(function() {\n                keys = {key: 'pseudo key'};\n                encrypt.keys = keys;\n            });\n\n            it('removes locally cached key', function() {\n                encrypt.deleteSecureKey();\n                expect(_.isEqual(encrypt.keys, {})).to.be.equal(true);\n                expect(!_.isEqual(encrypt.keys, keys)).to.be.equal(true);\n            });\n\n            it('removes keys from the session storage', function() {\n                if (!window.sessionStorage) {\n                    return;\n                }\n\n                window.sessionStorage.setItem(encrypt._getSessionKey(), keys.key);\n                expect(window.sessionStorage.getItem(encrypt._getSessionKey())).to.be.equal(keys.key);\n\n                encrypt.deleteSecureKey();\n                expect(window.sessionStorage.getItem(encrypt._getSessionKey())).not.to.be.equal(keys.key);\n            });\n        });\n\n        describe('encrypt()', function() {\n\n            before(function(done) {\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n            });\n\n            it('returns a promise', function() {\n                expect(typeof encrypt.encrypt(1)).to.be.equal('object');\n                expect(typeof encrypt.encrypt(1).promiseDispatch).to.be.equal('function');\n            });\n\n            it('resolves with encrypted string', function(done) {\n                encrypt.encrypt('String')\n                .then(function(res) {\n                    expect(typeof res).to.be.equal('string');\n                    expect(res).not.to.be.equal('String');\n                    expect(res.match(/^\\{.*\\}$/).length !== 0).to.be.equal(true);\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('encrypted string is a simple JSON', function(done) {\n                encrypt.encrypt('Hello world')\n                .then(function(res) {\n                    res = sjcl.json.decode(res);\n\n                    expect(typeof res).to.be.equal('object');\n                    expect(_.keys(res).length >= 9).to.be.equal(true);\n\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('generates random IV every time', function(done) {\n                var promises    = [],\n                    previousIvs = [];\n\n                function testIv(res) {\n                    res = sjcl.json.decode(res);\n\n                    expect(_.indexOf(previousIvs, res.iv) === -1).to.be.equal(true);\n                    previousIvs.push(res.iv);\n                    expect(_.indexOf(previousIvs, res.iv) !== -1).to.be.equal(true);\n                }\n\n                for (var i = 0; i < 100; i++) {\n                    promises.push(\n                        encrypt.encrypt('Hello world ' + i)\n                        .then(testIv)\n                    );\n                }\n\n                Q.all(promises)\n                .then(function() {\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('JSON data contains everything', function(done) {\n                encrypt.encrypt('Hello world.')\n                .then(function(res) {\n                    res = sjcl.json.decode(res);\n                    res = _.pick(res, 'cipher', 'ct', 'iter', 'iv', 'ks', 'mode', 'salt', 'ts', 'v');\n\n                    expect(_.keys(res).length >= 9).to.be.equal(true);\n\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('decrypt()', function() {\n\n            before(function(done) {\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n            });\n\n            it('returns a promise', function() {\n                expect(typeof encrypt.decrypt('string')).to.be.equal('object');\n                expect(typeof encrypt.decrypt('string').promiseDispatch).to.be.equal('function');\n            });\n\n            it('resolves with a decrypted string', function(done) {\n                var str = 'Encrypted string ' + sjcl.random.randomWords(10, 0),\n                    encrypted;\n\n                encrypt.encrypt(str)\n                .then(function(res) {\n                    encrypted = res;\n                    return encrypt.decrypt(str);\n                })\n                .then(function(decrypted) {\n                    expect(typeof decrypted).to.be.equal('string');\n                    expect(decrypted).not.to.be.equal(encrypted);\n                    expect(decrypted).to.be.equal(str);\n\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('encryptModel()', function() {\n\n            before(function(done) {\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n            });\n\n            it('returns a promise', function() {\n                var model = require('models/note');\n                model     = new model({id: 'random', title: 'Title'}); // jshint ignore:line\n\n                expect(typeof encrypt.encryptModel(model)).to.be.equal('object');\n                expect(typeof encrypt.encryptModel(model).promiseDispatch).to.be.equal('function');\n            });\n\n            it('creates \"encryptedData\" attribute', function(done) {\n                var model = require('models/note');\n                model     = new model({id: 'random', title: 'Title'}); // jshint ignore:line\n\n                encrypt.encryptModel(model)\n                .then(function() {\n                    var res = sjcl.json.decode(model.get('encryptedData'));\n\n                    expect(typeof res).to.be.equal('object');\n                    expect(typeof model.get('encryptedData')).to.be.equal('string');\n                    expect(_.isEqual(\n                        model.get('encryptedData'),\n                        _.pick(model.attributes, model.encryptKeys)\n                    )).to.be.equal(false);\n\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('resolves with the model', function() {\n                var model = require('models/note');\n                model     = new model({id: 'random', title: 'Title'}); // jshint ignore:line\n\n                encrypt.encryptModel(model)\n                .then(function(m) {\n                    expect(typeof m).to.be.equal('object');\n                    expect(typeof m.get('encryptedData')).to.be.equal('string');\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n\n            it('can encrypt all models', function(done) {\n                var promises = [];\n\n                function testModel(m) {\n                    var defer = Q.defer();\n\n                    requirejs(['models/' + m], function(m) {\n                        m = new m({id: 'random', title: 'Random', name: 'Random'}); // jshint ignore:line\n\n                        encrypt.encryptModel(m)\n                        .then(function(model) {\n                            var res = sjcl.json.decode(model.get('encryptedData'));\n\n                            expect(typeof res).to.be.equal('object');\n                            expect(typeof model.get('encryptedData')).to.be.equal('string');\n\n                            defer.resolve();\n                        });\n                    });\n\n                    return defer.promise;\n                }\n\n                _.each(['note', 'notebook', 'tag'], function(m) {\n                    promises.push(testModel(m));\n                });\n\n                Q.all(promises)\n                .then(function() {\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('decryptModel()', function() {\n            var Model;\n\n            before(function(done) {\n                Model = require('models/note');\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n            });\n\n            it('returns a promise', function() {\n                var model = new Model({id: 'random', title: 'Title'});\n\n                expect(typeof encrypt.decryptModel(model)).to.be.equal('object');\n                expect(typeof encrypt.decryptModel(model).promiseDispatch).to.be.equal('function');\n            });\n\n            it('calls _decryptModel if a model has \"encryptedData\" attribute', function(done) {\n                var fun   = encrypt._decryptModel,\n                    model = new Model({id: 'random', title: 'Title', encryptedData: 'data'});\n\n                encrypt._decryptModel = function() {\n                    encrypt._decryptModel = fun;\n                    done();\n                };\n\n                encrypt.decryptModel(model);\n            });\n\n            it('calls _decryptModelKeys if a model does not have \"encryptedData\" attribute', function(done) {\n                var fun   = encrypt._decryptModelKeys,\n                    model = new Model({id: 'random', title: 'Title'});\n\n                encrypt._decryptModelKeys = function() {\n                    encrypt._decryptModelKeys = fun;\n                    done();\n                };\n\n                encrypt.decryptModel(model);\n            });\n\n            it('decrypts a model\\'s data', function(done) {\n                var promises = [];\n\n                _.each(['note', 'notebook', 'tag'], function(model) {\n                    promises.push(\n                        encryptModel(model)\n                        .then(decryptModel)\n                    );\n                });\n\n                Q.all(promises)\n                .then(function() {\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('decryptModel() decrypts a model from 0.6.x', function() {\n\n            before(function(done) {\n\n                // We used to save salt as a string\n                configs.encryptSalt = configs.encryptSalt.toString();\n                encrypt.configs.encryptSalt = configs.encryptSalt;\n\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n            });\n\n            it('can decrypt models', function(done) {\n                var promises = [];\n\n                function encrypt06(Model) {\n                    var model = new Model({id: 'random'});\n\n                    _.each(model.encryptKeys, function(key) {\n                        var data = helpers.encrypt062(\n                            'random ' + key,\n                            encrypt.keys.key,\n                            configs\n                        );\n                        model.set(key, data);\n                    });\n\n                    return model;\n                }\n\n                _.each(['note', 'notebook', 'tag'], function(model) {\n                    promises.push(\n                        requirePromise('models/' + model)\n                        .then(encrypt06)\n                        .then(decryptModel)\n                    );\n                });\n\n                Q.all(promises)\n                .then(function() { done(); })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('decryptModel() decrypts a model from 0.7.0', function() {\n            var salt;\n\n            before(function(done) {\n\n                // We used to save encryption salt in HEX format in 0.7.0\n                salt = sjcl.codec.hex.fromBits(sjcl.random.randomWords(4, 0));\n                salt = encrypt.sjcl.toUpperCase(salt);\n                encrypt.configs.encryptSalt = salt;\n\n                // Generate new PBKDF2\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n                encrypt.configs.encryptSalt = salt;\n            });\n\n            it('can decrypt a model', function(done) {\n                var promises = [];\n\n                function encrypt07(Model) {\n                    var model = new Model({id: 'random'});\n\n                    _.each(model.encryptKeys, function(key) {\n                        var data = helpers.encrypt070(\n                            'random ' + key,\n                            encrypt.configs.encryptSalt,\n                            encrypt.sjcl.toUpperCase(encrypt.keys.hexKey),\n                            configs\n                        );\n                        model.set(key, data);\n                    });\n\n                    return model;\n                }\n\n                _.each(['note', 'notebook', 'tag'], function(model) {\n                    promises.push(\n                        requirePromise('models/' + model)\n                        .then(encrypt07)\n                        .then(decryptModel)\n                    );\n                });\n\n                Q.all(promises)\n                .then(function() { done(); })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('encryptModels()', function() {\n            var Notes,\n                notes;\n\n            before(function(done) {\n                Notes = require('collections/notes');\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n                notes        = new Notes();\n                notes.add({id: Math.random()});\n            });\n\n            it('returns a promise', function() {\n                expect(typeof encrypt.encryptModels(notes)).to.be.equal('object');\n                expect(typeof encrypt.encryptModels(notes).promiseDispatch).to.be.equal('function');\n            });\n\n            it('resolves a promise if a collection is empty', function(done) {\n                notes.reset([]);\n                expect(notes.length).to.be.equal(0);\n\n                encrypt.encryptModels(notes)\n                .then(function() {\n                    done();\n                });\n            });\n\n            it('resolves a promise if encryption is disabled', function(done) {\n                encrypt.configs.encrypt = 0;\n                expect(notes.length).not.to.be.equal(0);\n\n                encrypt.encryptModels(notes)\n                .then(function() {\n                    done();\n                });\n            });\n\n            it('resolves a promise if there are not any keys', function(done) {\n                encrypt.keys = {};\n                expect(notes.length).not.to.be.equal(0);\n                expect(Number(encrypt.configs.encrypt)).not.to.be.equal(0);\n\n                encrypt.encryptModels(notes)\n                .then(function() {\n                    done();\n                });\n            });\n\n            it('encrypts every model in a collection', function(done) {\n                var promises = [];\n\n                _.each(['notes', 'notebooks', 'tags'], function(name) {\n                    promises.push(\n                        encryptModels(name)\n                    );\n                });\n\n                Q.all(promises)\n                .then(function() {\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n        describe('decryptModels()', function() {\n            var Notes,\n                notes;\n\n            before(function(done) {\n                Notes = require('collections/notes');\n                setKey(done);\n            });\n\n            beforeEach(function() {\n                encrypt.keys = keys;\n                notes        = new Notes();\n                notes.add({id: Math.random()});\n            });\n\n            it('returns a promise', function() {\n                expect(typeof encrypt.decryptModels(notes)).to.be.equal('object');\n                expect(typeof encrypt.decryptModels(notes).promiseDispatch).to.be.equal('function');\n            });\n\n            it('resolves a promise if a collection is empty', function(done) {\n                notes.reset([]);\n                expect(notes.length).to.be.equal(0);\n\n                encrypt.decryptModels(notes)\n                .then(function() {\n                    done();\n                });\n            });\n\n            it('resolves a promise if encryption is disabled', function(done) {\n                encrypt.configs.encrypt = 0;\n                expect(notes.length).not.to.be.equal(0);\n\n                encrypt.decryptModels(notes)\n                .then(function() {\n                    encrypt.configs.encrypt = 1;\n                    done();\n                });\n            });\n\n            it('resolves a promise if PBKDF2 is empty', function(done) {\n                encrypt.keys = {};\n                encrypt.decryptModels(notes)\n                .then(function() {\n                    done();\n                });\n            });\n\n            it('decrypts models in a collection', function(done) {\n                var promises = [];\n\n                _.each(['notes', 'notebooks', 'tags'], function(name) {\n                    promises.push(\n                        encryptModels(name)\n                        .then(decryptModels)\n                    );\n                });\n\n                Q.all(promises)\n                .then(function() {\n                    done();\n                })\n                .fail(function(e) {\n                    console.error('Error:', e);\n                });\n            });\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/classes/helpers.js",
    "content": "/* global define */\ndefine(function(require) {\n    'use strict';\n\n    var sjcl = require('sjcl'),\n        _    = require('underscore');\n\n    return {\n\n        encrypt062: function(str, pass, configs) {\n            var p = {\n                iter : configs.encryptIter,\n                ts   : Number(configs.encryptTag),\n                ks   : Number(configs.encryptKeySize),\n\n                // Random initialization vector every time\n                iv   : sjcl.random.randomWords(4, 0)\n            };\n\n            return sjcl.encrypt(pass.toString(), str, p);\n        },\n\n        encrypt070: function(str, salt, pass, configs) {\n            var p = {\n                mode   : 'ccm',\n                iter   : Number(configs.encryptIter),\n                ts     : Number(configs.encryptTag),\n                ks     : Number(configs.encryptKeySize),\n                salt   : salt,\n                v      : 1,\n                adata  : '',\n                cipher : 'aes',\n\n                // Random initialization vector every time\n                iv     : sjcl.random.randomWords(4, 0)\n            };\n\n            str = sjcl.encrypt(pass.toString(), str, p);\n\n            str = _.pick(JSON.parse(str), 'ct', 'iv');\n            return JSON.stringify(str);\n        },\n\n    };\n\n});\n"
  },
  {
    "path": "test/spec/classes/sjcl.js",
    "content": "/* global define, chai, describe, it, before, after, afterEach, beforeEach */\ndefine(function(require) {\n    'use strict';\n\n    var Encrypt = require('classes/sjcl'),\n        sjcl    = require('sjcl'),\n        _       = require('underscore'),\n        expect  = chai.expect,\n        helpers = require('spec/classes/helpers'),\n        configs;\n\n    configs = {\n        'encrypt'           : '1',\n        'encryptPass'       : sjcl.hash.sha256.hash('1'),\n        'encryptIter'       : '1000',\n        'encryptTag'        : '64',\n        'encryptKeySize'    : '128',\n    };\n\n    describe('classes/sjcl', function() {\n        var encrypt;\n\n        beforeEach(function() {\n            var salt = sjcl.random.randomWords(4, 0);\n            configs.encryptSalt = sjcl.codec.hex.fromBits(salt);\n\n            expect(typeof configs.encryptSalt).to.be.equal('string');\n        });\n\n        before(function() {\n            encrypt = new Encrypt();\n        });\n\n        after(function() {\n        });\n\n        it('hex() can convert string to HEX format', function() {\n            var rand = sjcl.random.randomWords(4, 0),\n                hex  = encrypt.hex(rand);\n\n            expect(typeof rand).to.be.equal('object');\n            expect(typeof hex).to.be.equal('string');\n            expect(rand).not.to.be.equal(hex);\n        });\n\n        it('toUpperCase() can convert string to uppercase', function() {\n            var rand  = encrypt.hex(sjcl.random.randomWords(4, 0)),\n                upper = encrypt.toUpperCase(rand);\n\n            expect(/^[A-F0-9 ]*$/.test(upper)).to.be.equal(true);\n            expect(/^[A-F0-9 ]*$/.test(rand)).to.be.equal(false);\n        });\n\n        it('sha256() can convert a string to SHA256 hash', function(done) {\n            encrypt.sha256('hello')\n            .then(function(hash) {\n                expect(hash).to.be.not.equal('hello');\n                expect(typeof hash).to.be.equal('object');\n                expect(hash.length).to.be.equal(8);\n                done();\n            });\n        });\n\n        it('getConfigs() returns encryption configs', function() {\n            var c = encrypt.getConfigs(configs);\n\n            expect(typeof c).to.be.equal('object');\n\n            expect(c.mode).to.be.equal('ccm');\n            expect(c.iter).to.be.equal(Number(configs.encryptIter));\n            expect(c.ts).to.be.equal(Number(configs.encryptTag));\n            expect(c.ks).to.be.equal(Number(configs.encryptKeySize));\n            expect(c.salt).to.be.equal(configs.encryptSalt);\n            expect(c.v).to.be.equal(1);\n            expect(c.adata).to.be.equal('');\n            expect(c.cipher).to.be.equal('aes');\n        });\n\n        it('deriveKey() generates PBKDF2', function() {\n            var p  = encrypt.deriveKey({\n                password: 'my secret key #1',\n                configs : configs\n            });\n\n            expect(typeof p).to.be.equal('object');\n\n            expect(typeof p.key).to.be.equal('object');\n            expect(p.key.length).to.be.equal(configs.encryptKeySize / 32);\n            expect(typeof p.hexKey).to.be.equal('string');\n        });\n\n        it('deriveKey() generated key is equal to the result returned by sjcl.misc.pbkdf2()', function() {\n            var p  = encrypt.deriveKey({password:'my secret key #1', configs: configs}),\n                p2 = sjcl.misc.pbkdf2('my secret key #1', configs.encryptSalt, Number(configs.encryptIter), Number(configs.encryptKeySize));\n\n            expect(_.isEqual(p.key, p2)).to.be.equal(true);\n            expect(p.hexKey).to.be.equal(sjcl.codec.hex.fromBits(p2));\n        });\n\n        describe('Can decrypt data from the previous versions', function() {\n            var str,\n                encrypted,\n                decrypted;\n\n            beforeEach(function() {\n                str = 'My secret note ' + encrypt.hex(sjcl.random.randomWords(4, 0));\n                for (var i = 0; i < 20; i++) {\n                    str += '\\r\\n ' + encrypt.hex(sjcl.random.randomWords(4, 0));\n                }\n            });\n\n            afterEach(function() {\n                expect(encrypted).to.be.not.equal(str);\n                expect(encrypted).to.be.not.equal(decrypted);\n\n                expect(typeof JSON.parse(encrypted)).to.be.equal('object');\n\n                expect(str).to.be.equal(decrypted);\n                expect(str.length).to.be.equal(decrypted.length);\n            });\n\n            it('can decrypt string from v0.6.2', function() {\n                var p = encrypt.deriveKey({password:'my secret key #1', configs: configs});\n                encrypt.options = p;\n\n                encrypted = helpers.encrypt062(str, p.key, configs);\n                decrypted = encrypt.decryptLegacy({string: encrypted, configs: configs});\n\n                expect(_.size(JSON.parse(encrypted))).to.be.equal(10);\n            });\n\n            it('can decrypt string from v0.7.0', function() {\n                var salt = encrypt.toUpperCase(encrypt.hex(sjcl.random.randomWords(4, 0))),\n                    p;\n\n                configs.encryptSalt = salt;\n                p = encrypt.deriveKey({password: 'my secret key #1', configs: configs});\n                encrypt.keys = p;\n\n                encrypted = helpers.encrypt070(str, salt, encrypt.toUpperCase(p.hexKey), configs);\n                decrypted = encrypt.decryptLegacy({string: encrypted, configs: configs});\n\n                expect(_.size(JSON.parse(encrypted))).to.be.equal(2);\n            });\n        });\n\n        describe('Encryption', function() {\n            var str,\n                encrypted;\n\n            before(function() {\n                var p = encrypt.deriveKey({password: 'my secret key #1', configs: configs});\n                encrypt.keys = p;\n\n                str = 'My string';\n            });\n\n            it('can encrypt', function() {\n                encrypted = encrypt.encrypt({\n                    string  : str,\n                    configs : configs,\n                    iv      : sjcl.random.randomWords(4, 0)\n                });\n\n                expect(encrypted).not.to.be.equal(str);\n                expect(typeof encrypted).to.be.equal('string');\n                expect(_.size(JSON.parse(encrypted))).to.be.equal(10);\n            });\n\n            it('can decrypt', function() {\n                var decrypted = encrypt.decrypt({string: encrypted, configs: configs});\n\n                expect(decrypted).not.to.be.equal(encrypted);\n                expect(typeof decrypted).to.be.equal('string');\n                expect(decrypted.length).to.be.equal(str.length);\n                expect(decrypted).to.be.equal(str);\n            });\n\n            it('returns an original string if it was not encrypted', function() {\n                var decrypted = encrypt.decrypt({string: 'Hello', configs: configs});\n                expect(decrypted).to.be.equal('Hello');\n            });\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/collections/configs.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, beforeEach, before, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/configs'\n], function(sinon, Q, _, Radio, Collection) {\n    'use strict';\n\n    describe('collections/configs', function() {\n        var collection;\n\n        beforeEach(function() {\n            collection = new Collection();\n        });\n\n        describe('.hasNewConfigs()', function() {\n\n            it('.configNames length is not equal to .length', function() {\n                expect(collection.hasNewConfigs()).to.be.equal(true);\n            });\n\n            it('.configNames length is equal to .length', function() {\n                collection.configNames = {};\n                expect(collection.hasNewConfigs()).to.be.equal(false);\n            });\n\n        });\n\n        it('.changeDB()', function() {\n            collection.changeDB('testP');\n            expect(collection.profileId).to.be.equal('testP');\n            expect(collection.model.prototype.profileId).to.be.equal('testP');\n            collection.model.prototype.profileId = 'notes-db';\n        });\n\n        describe('.migrateFromLocal()', function() {\n\n            before(function() {\n                localStorage.setItem(\n                    'vimarkable.configs-testingKey',\n                    JSON.stringify({name: 'testingKey', value: 11})\n                );\n            });\n\n            after(function() {\n                localStorage.removeItem('vimarkable.configs-testingKey');\n            });\n\n            it('changes .configNames[key] if it exists in localStorage', function() {\n                collection.configNames.testingKey = 10;\n\n                collection.migrateFromLocal();\n                expect(collection.configNames.testingKey).not.to.be.equal(10);\n                expect(collection.configNames.testingKey).to.be.equal(11);\n            });\n\n            it('does nothing if it does not exist in localStorage', function() {\n                collection.configNames.testingKey2 = 10;\n                collection.migrateFromLocal();\n                expect(collection.configNames.testingKey2).to.be.equal(10);\n            });\n\n        });\n\n        describe('.createDefault()', function() {\n            var sandbox;\n\n            beforeEach(function() {\n                sandbox = sinon.sandbox.create();\n\n                sandbox.stub(collection.model.prototype, 'save', function() {\n                    return Q.resolve();\n                });\n            });\n\n            afterEach(function() {\n                sandbox.restore();\n            });\n\n            it('returns a promise', function() {\n                expect(collection.createDefault()).to.be.an('object');\n                expect(collection.createDefault()).to.have.property('promiseDispatch');\n            });\n\n            it('creates new models', function() {\n                collection.createDefault();\n                expect(collection.model.prototype.save).to.have.been\n                    .callCount(_.keys(collection.configNames).length);\n            });\n\n        });\n\n        describe('.getConfigs()', function() {\n\n            it('returns object', function() {\n                expect(collection.getConfigs()).to.be.an('object');\n            });\n\n            it('converts current models into key=value', function() {\n                collection.add([{name: 'test', value: 'test'}, {name: 'test2', value: 'test2'}]);\n                var configs = collection.getConfigs();\n\n                expect(_.keys(configs).length).to.be.equal(collection.length + 1);\n                collection.each(function(model) {\n                    expect(model.get('value')).to.be.equal(configs[model.get('name')]);\n                });\n            });\n\n            it('parses .appProfiles', function() {\n                collection.add({name: 'appProfiles', value: JSON.stringify(['notes-db'])});\n                expect(collection.getConfigs().appProfiles).to.be.an('array');\n            });\n\n        });\n\n        describe('.getDefault()', function() {\n\n            it('returns a model', function() {\n                expect(collection.getDefault('firstStart')).to.be.an('object');\n                expect(collection.getDefault('firstStart').get('name')).to.be.equal('firstStart');\n            });\n\n            it('the value of the model is equal to default\\'s', function() {\n                expect(collection.getDefault('firstStart').get('value'))\n                    .to.be.equal(collection.configNames.firstStart);\n            });\n\n        });\n\n        describe('.resetFromJSON()', function() {\n\n            it('creates models from an object', function() {\n                collection.resetFromJSON({test: '1', test2: '2'});\n\n                expect(collection.length).to.be.equal(2);\n                expect(collection.get('test').get('value')).to.be.equal('1');\n                expect(collection.get('test2').get('value')).to.be.equal('2');\n            });\n\n            it('resets the controller', function(done) {\n                collection.once('reset', function() { done(); });\n                collection.resetFromJSON({test: '1', test2: '2'});\n            });\n\n        });\n\n        describe('.shortcuts()', function() {\n\n            beforeEach(function() {\n                collection.resetFromJSON(collection.configNames);\n            });\n\n            it('returns an array', function() {\n                expect(collection.shortcuts()).to.be.an('array');\n                expect(collection.shortcuts().length).to.be.equal(14);\n            });\n\n            it('filters the collection', function() {\n                expect(collection.shortcuts().length).to.be.equal(14);\n                expect(collection.shortcuts().length).not.to.be.equal(collection.length);\n            });\n\n        });\n\n        it('.appShortcuts()', function() {\n            collection.resetFromJSON(collection.configNames);\n            expect(collection.appShortcuts()).to.be.an('array');\n            expect(collection.appShortcuts().length).to.be.equal(3);\n        });\n\n        describe('.filterName()', function() {\n\n            beforeEach(function() {\n                collection.resetFromJSON(collection.configNames);\n            });\n\n            it('searches a string in models\\' names', function() {\n                expect(collection.filterName('action')).to.be.an('array');\n                expect(collection.filterName('action').length).to.be.equal(4);\n                expect(collection.filterName('navigate').length).to.be.equal(2);\n                expect(collection.filterName('jump').length).to.be.equal(5);\n            });\n\n            it('performs case sensitive search', function() {\n                expect(collection.filterName('edit').length).to.be.equal(1);\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/modules/configs.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'sjcl',\n    'backbone.radio',\n    'collections/modules/configs',\n    'collections/modules/module',\n], function(sinon, Q, _, sjcl, Radio, colModule, Module) {\n    'use strict';\n\n    describe('collections/modules/configs', function() {\n        var col,\n            defReplies;\n\n        before(function() {\n\n            // Default replies\n            defReplies = Module.prototype.reply();\n        });\n\n        beforeEach(function() {\n            col = _.clone(colModule);\n            col.collection = new col.Collection();\n            col.collection.resetFromJSON(col.collection.configNames);\n        });\n\n        after(function() {\n            col.destroy();\n        });\n\n        describe('.initialize()', function() {\n\n            it('has .Collection', function() {\n                expect(col.Collection.prototype.storeName).to.be.equal('configs');\n            });\n\n            it('listens on `.col.storeName` channel', function() {\n                expect(col.vent).to.be.an('object');\n                expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);\n            });\n\n            it('listens to requests', function() {\n                _.each(_.union(defReplies, col.reply()), function(reply) {\n                    expect(col.vent._requests[reply]).to.be.an('object');\n                });\n            });\n\n            it('listens to events', function() {\n                expect(col.vent._events['destroy:collection']).to.be.an('array');\n            });\n\n        });\n\n        describe('.reply()', function() {\n\n            it('returns replies', function() {\n                var mReplies = col.reply();\n\n                expect(mReplies).to.be.an('object');\n                expect(_.keys(mReplies).length).to.be.equal(8);\n            });\n\n        });\n\n        describe('.resetEncrypt()', function() {\n\n            it('empties the value of `encryptBackup` model', function() {\n                col.collection.get('encryptBackup')\n                    .set('value', {password: '1'});\n\n                col.resetEncrypt();\n                expect(_.keys(col.collection.get('encryptBackup').get('value')).length)\n                    .to.be.equal(0);\n            });\n\n        });\n\n        it('.createProfile()', function() {\n            var model = col.collection.get('appProfiles'),\n                stub  = sinon.stub(model, 'createProfile');\n\n            col.createProfile(model, 'test');\n            expect(stub).calledWith('test');\n        });\n\n        describe('.removeProfile()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = col.collection.get('appProfiles');\n                sinon.stub(model, 'removeProfile').returns(Q.resolve());\n            });\n\n            afterEach(function() {\n                model.removeProfile.restore();\n            });\n\n            it('returns a promise', function(done) {\n                var res = col.removeProfile(model, 'test---');\n                expect(res).to.have.property('promiseDispatch');\n                res.should.be.fulfilled.and.notify(done);\n            });\n\n            it('triggers `removed:profile` event', function(done) {\n                col.vent.once('removed:profile', function(name) {\n                    expect(name).to.be.equal('test--1');\n                    done();\n                });\n                col.removeProfile(model, 'test--1');\n            });\n\n        });\n\n        describe('.saveModel()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new col.collection.model({\n                    value: 'hello', name: 'encryptPass'\n                });\n            });\n\n            it('returns a promise', function() {\n                expect(col.saveModel(model, {})).to.have.property('promiseDispatch');\n            });\n\n            it('checks if the model stores password', function() {\n                var spy = sinon.spy(model, 'isPassword');\n                col.saveModel(model, {});\n                expect(spy).called;\n            });\n\n            it('hashes the password', function(done) {\n                Radio.replyOnce('encrypt', 'sha256', function(pass) {\n                    expect(pass).to.be.equal('1');\n                    done();\n                });\n                col.saveModel(model, {value: '1'});\n            });\n\n        });\n\n        describe('.saveObjects()', function() {\n\n            beforeEach(function() {\n                sinon.stub(col, 'saveObject');\n            });\n\n            it('returns a promise', function() {\n                expect(col.saveObjects({})).to.have.property('promiseDispatch');\n            });\n\n            it(\n                'saves current encryption configs to a profile\\'s backup if' +\n                '`useDefaultConfigs` has changed',\n                function() {\n                    var spy = sinon.spy(col, '_backupEncrypt');\n                    col.saveObjects({useDefaultConfigs: true}, {profileId: 'testBackup'});\n                    expect(spy).calledWith('testBackup');\n                }\n            );\n\n            it('backs up encryption configs', function() {\n                var spy = sinon.spy(col, '_backupEncryption');\n                col.saveObjects({key: '1', key2: '2'}, {profileId: 'testBackup'});\n                expect(spy).calledWith({key: '1', key2: '2'});\n            });\n\n            it('saves all configs', function() {\n                col.saveObjects({key: {name: 'key'}, key2: {name: 'key2'}}, {profileId: 'testDb'});\n                expect(col.saveObject).callCount(2);\n            });\n\n            it('triggers `changed` event on `configs` channel', function(done) {\n                Radio.once('configs', 'changed', function() { done(); });\n                col.saveObjects({key: {name: 'key'}, key2: {name: 'key2'}}, {profileId: 'testDb'});\n            });\n\n        });\n\n        describe('.saveObject()', function() {\n\n            it('returns a promise', function() {\n                expect(col.saveObject({})).to.have.property('promiseDispatch');\n            });\n\n            it('calls .getModel()', function() {\n                var stub = sinon.stub(col, 'getModel').returns(new Q());\n                col.saveObject({name: 'test'});\n                expect(stub).calledWith({name: 'test'});\n            });\n\n        });\n\n        describe('.getConfig()', function() {\n\n            it('returns the value of a config', function() {\n                expect(col.getConfig('firstStart')).to.be.equal('1');\n            });\n\n            it('returns the second argument if a config was not found', function() {\n                expect(col.getConfig('config404Test', '404')).to.be.equal('404');\n            });\n\n        });\n\n        it('.getObject()', function() {\n            expect(col.getObject()).to.deep.equal(col.collection.getConfigs());\n        });\n\n        describe('.getModel()', function() {\n\n            it('can find by a config name', function() {\n                return col.getModel('appVersion').should.eventually.be.an('object');\n            });\n\n        });\n\n        describe('.getAll()', function() {\n\n            beforeEach(function() {\n            });\n\n            it('returns cached collection if it exists', function() {\n                expect(col.collection).to.be.an('object');\n                expect(col.getAll()).to.have.property('promiseDispatch');\n                return col.getAll().should.eventually.be.equal(col.collection);\n            });\n\n        });\n\n        describe('.useDefaultConfigs()', function() {\n            var model;\n\n            beforeEach(function() {\n                col.getModel = function() { return Q.resolve(model); };\n            });\n\n            it('returns null if `useDefaultConfigs` model doesn\\'t exist', function() {\n                return col.useDefaultConfigs('testDB').should.eventually.be.a('null');\n            });\n\n            it('returns null if the value is `1`', function() {\n                model = {get: function() { return 1; }};\n                return col.useDefaultConfigs('testDB').should.eventually.be.a('null');\n            });\n\n            it('returns the profile name otherwise', function() {\n                model = {get: function() { return 0; }};\n                return col.useDefaultConfigs('testDB').should.eventually.be.equal('testDB');\n            });\n\n        });\n\n        describe('.getProfiles()', function() {\n\n            it('returns backup\\'s profileId if current profile isn\\'t default', function() {\n                col.collection.profileId = 'testDB1';\n                col.collection.get('encryptBackup').profileId = 'testDB1';\n                return col.getProfiles().should.eventually.contain('testDB1');\n            });\n\n            it('returns backup\\'s profileId if backup uses none default profile', function() {\n                col.collection.get('encryptBackup').profileId = 'testDB1';\n                return col.getProfiles().should.eventually.contain('testDB1');\n            });\n\n            it('returns all profiles which use configs from default profile', function(done) {\n                col.getModel = function() { return Q.resolve(); };\n                col._getDefaultProfiles = done;\n                col.getProfiles();\n            });\n\n        });\n\n        describe('._getDefaultProfiles()', function() {\n            var model;\n\n            before(function() {\n                model = new col.collection.model({\n                    name : 'appProfiles',\n                    value: JSON.stringify(['notes-db', 'testDb', 'noDefault'])\n                });\n            });\n\n            beforeEach(function() {\n                sinon.stub(col, 'getModel', function(profile) {\n                    var m = new col.collection.model();\n                    m.set('value', (profile.profile !== 'noDefault' ? 1 : 0));\n                    m.profileId = profile.profile;\n                    return Q.resolve(m);\n                });\n            });\n\n            afterEach(function() {\n                col.getModel.reset();\n            });\n\n            it('returns all profiles which use configs from default profile', function() {\n                return col._getDefaultProfiles(model).should.eventually\n                    .to.include.members(['notes-db', 'testDb'])\n                    .and.not.to.include.members(['noDefault']);\n            });\n\n        });\n\n        describe('._checkBackup()', function() {\n            var model;\n\n            beforeEach(function() {\n                sinon.stub(col, 'getModel', function(options) {\n                    expect(options.name).to.be.equal('encryptBackup');\n                    if (model) {\n                        model.profileId = options.profile;\n                    }\n                    return Q.resolve(model);\n                });\n            });\n\n            afterEach(function() {\n                col.getModel.reset();\n            });\n\n            it('does nothing if it is the default profile', function() {\n                return col._checkBackup('testDb').should.eventually.be.an('undefined');\n            });\n\n            it('does nothing if backup was not found', function() {\n                return col._checkBackup(col.defaultDB).should.eventually.be.an('undefined');\n            });\n\n            it('does nothing if backup\\'s is not value empty', function() {\n                model = new col.collection.model({value: {key: 'value'}});\n                return col._checkBackup('testDb').should.eventually.be.an('undefined');\n            });\n\n            it('returns current profile\\'s encryption backup', function() {\n                model = new col.collection.model({value: {}});\n                return col._checkBackup('testDb').should.eventually\n                    .be.an('object')\n                    .and.have.property('profileId').equal('testDb');\n            });\n\n        });\n\n        describe('._createDefault()', function() {\n\n            it('does nothing if there aren\\'t new configs', function() {\n                expect(col.collection.hasNewConfigs()).to.be.equal(false);\n                return col._createDefault().should.eventually.be.equal(col.collection);\n            });\n\n            it('triggers `collection:empty` event', function(done) {\n                col.collection.reset([]);\n                col.vent.once('collection:empty', done);\n                col._createDefault();\n            });\n\n            it('will try to migrate from localStorage', function() {\n                var stub = sinon.stub(col.collection, 'migrateFromLocal');\n                col.collection.reset([]);\n                col._createDefault();\n                expect(stub).to.be.called;\n            });\n\n            it('will create configs with default values', function(done) {\n                col.collection.reset([]);\n                col.collection.createDefault = done;\n                col._createDefault();\n            });\n\n            it('triggers `reset:all` event on collection', function(done) {\n                col.collection.reset([]);\n                col.collection.createDefault = function() {};\n\n                col.collection.once('reset:all', done);\n                col._createDefault();\n            });\n\n        });\n\n        describe('._getEncryption()', function() {\n\n            it('returns empty array if encryption config was not provided', function() {\n                expect(col._getEncryption({})).to.be.an('array');\n                expect(col._getEncryption({})).to.have.length.below(1);\n            });\n\n            it('returns empty array if encryption is disabled', function() {\n                expect(Number(col.getConfig('encrypt'))).to.be.equal(0);\n                expect(col._getEncryption({encrypt: {value: 0}})).to.be.an('array');\n                expect(col._getEncryption({encrypt: {value: 0}})).to.have.length.below(1);\n            });\n\n            it('disables encryption if password was not provided', function() {\n                var data = {encrypt: {value: 1}};\n\n                expect(col.getConfig('encryptPass')).to.have.length.below(1);\n                expect(col._getEncryption(data)).to.be.an('array');\n                expect(col._getEncryption(data)).to.have.length.below(1);\n                expect(data.encrypt.value).to.be.equal('0');\n            });\n\n            it('disables encryption if the provided password is empty', function() {\n                var data = {encrypt: {value: 1}, encryptPass: {value: ''}};\n                expect(col._getEncryption(data)).to.have.length.below(1);\n                expect(data.encrypt.value).to.be.equal('0');\n            });\n\n            it('returns only encryption configs which changed', function() {\n                var data = {\n                    encrypt     : {value : 1  , name : 'encrypt'},\n                    encryptPass : {value : '1', name : 'encryptPass'},\n                    appLang     : {value : 'e', name : 'appLang'}\n                };\n                expect(col._getEncryption(data)).to.have.length.within(2, 2);\n                expect(_.findWhere(col._getEncryption(data), {name: 'encrypt'})).to.have.property('value').equal(1);\n                expect(_.findWhere(col._getEncryption(data), {name: 'encryptPass'})).to.have.property('value').equal('1');\n            });\n\n        });\n\n        describe('._checkPassChanged()', function() {\n\n            it('returns true if name is not equal `encryptPass`', function() {\n                expect(col._checkPassChanged({name: 'encrypt'})).to.be.equal(true);\n            });\n\n            it('returns false if new and old passwords are equal', function() {\n                col.collection.get('encryptPass').set('value', '1');\n                expect(col._checkPassChanged({name: 'encryptPass', value: '1'})).to.be.equal(false);\n            });\n\n            it('returns false if password hashes are not different', function() {\n                col.collection.get('encryptPass').set('value', sjcl.hash.sha256.hash('1'));\n                expect(col._checkPassChanged({name: 'encryptPass', value: '1'})).to.be.equal(false);\n            });\n\n            it('returns true if password hashes are different', function() {\n                col.collection.get('encryptPass').set('value', sjcl.hash.sha256.hash('2'));\n                expect(col._checkPassChanged({name: 'encryptPass', value: '1'})).to.be.equal(true);\n            });\n\n        });\n\n        describe('._backupEncrypt()', function() {\n\n            it('backs up all current encryption configs', function() {\n                sinon.stub(col, 'saveModel', function(model, data) {\n                    model.set(data);\n                    return model;\n                });\n\n                return col._backupEncrypt('test')\n                .then(function(model) {\n                    expect(model.profileId).to.be.equal('test');\n                    expect(model.get('value'))\n                        .to.deep.equal(_.pick(col.collection.getConfigs(), col.encryptionKeys));\n                });\n            });\n\n        });\n\n        describe('._backupEncryption()', function() {\n\n            it('does nothing if encryption configs haven\\'t changed', function() {\n                expect(col._backupEncryption({})).to.be.an('undefined');\n            });\n\n            it('does nothing if encryption backup is not empty', function() {\n                var data = {encrypt: {value: 1, name: 'encrypt'}};\n                col.collection.get('encryptBackup').set('value', {key: '1'});\n                col.collection.get('encryptPass').set('value', '1');\n\n                expect(col._backupEncryption(data)).to.be.an('undefined');\n                expect(data.encrypt.value).to.be.equal(1);\n            });\n\n            it('saves changed encryption configs to backup', function() {\n                var data = {\n                    encrypt     : {value : 1  , name : 'encrypt'},\n                    encryptPass : {value : '1', name : 'encryptPass'},\n                };\n                expect(col._backupEncryption(data)).to.be.an('object');\n                expect(col._backupEncryption(data).encryptBackup)\n                    .to.have.property('value')\n                    .deep.equal(_.pick(col.getObject(), ['encrypt', 'encryptPass']));\n            });\n\n            it('does not back up encryption password if it has not changed', function() {\n                var data = {\n                    encrypt     : {value : 1  , name : 'encrypt'},\n                    encryptPass : {value : '1', name : 'encryptPass'},\n                };\n                col.collection.get('encryptPass').set('value', '1');\n\n                expect(col._backupEncryption(data).encryptBackup)\n                    .to.have.property('value')\n                    .deep.equal(_.pick(col.getObject(), ['encrypt']));\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/modules/files.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/modules/files',\n    'collections/modules/module',\n], function(sinon, Q, _, Radio, ColModule, Module) {\n    'use strict';\n\n    describe('collections/modules/files', function() {\n        var col,\n            sandbox;\n\n        before(function() {\n            col = new ColModule();\n        });\n\n        after(function() {\n            col.destroy();\n        });\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n            sandbox.stub(col, 'save').returns(Q.resolve());\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('.initialize()', function() {\n\n            it('has .Collection', function() {\n                expect(col.Collection.prototype.storeName).to.be.equal('files');\n            });\n\n            it('listens on `.Collection.storeName` channel', function() {\n                expect(col.vent).to.be.an('object');\n                expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);\n            });\n\n            it('listens to requests', function() {\n                _.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {\n                    expect(col.vent._requests[reply]).to.be.an('object');\n                });\n            });\n\n        });\n\n        describe('.getFiles()', function() {\n            var ids;\n\n            before(function() {\n                ids = [];\n                for (var i = 0; i < 10; i++) {\n                    ids.push('id' + i);\n                }\n            });\n\n            beforeEach(function() {\n                sandbox.stub(col, 'getModel').returns(Q.resolve());\n            });\n\n            it('will fetch all models with the provided IDs', function() {\n                return col.getFiles(ids, {profile: 'test'}).then(function() {\n                    expect(col.getModel).callCount(ids.length);\n                    expect(col.getModel).calledWithMatch({profile: 'test'});\n                });\n            });\n\n        });\n\n        describe('.saveAll()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'saveModel').returns(Q.resolve());\n            });\n\n            it('saves all models', function() {\n                var data = [{id: 1}, {id: 2}];\n                return col.saveAll(data, {profile: 'test'}).then(function() {\n                    expect(col.saveModel).callCount(data.length);\n                    expect(col.saveModel).calledWithMatch({profileId: 'test'});\n                });\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/modules/module.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/modules/module',\n    'collections/notes'\n], function(sinon, Q, _, Radio, Module, Collection) {\n    'use strict';\n\n    describe('collections/modules/module', function() {\n        var col,\n            models,\n            collection,\n            sandbox,\n            itStub;\n\n        before(function() {\n            Module.prototype.Collection = Collection;\n            col = new Module();\n\n            models = [];\n            for (var i = 0; i < 10; i++) {\n                models.push({id: 'id-' + i});\n            }\n            collection = new col.Collection(models);\n            col.collection = collection.clone();\n        });\n\n        after(function() {\n            col.destroy();\n        });\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n            itStub  = sandbox.stub();\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('.initialize()', function() {\n\n            it('the default database is `notes-db`', function() {\n                expect(col.defaultDB).to.be.equal('notes-db');\n            });\n\n            it('listens on `.Collection.storeName` channel', function() {\n                expect(col.vent).to.be.an('object');\n                expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);\n            });\n\n            it('listens to requests', function() {\n                var keys = _.keys(col.vent._requests);\n                _.each(col.reply(), function(reply, key) {\n                    expect(keys).to.contain(key);\n                });\n            });\n\n            it('listens to `destroy:collection` event', function() {\n                expect(_.keys(col.vent._events)).to.be.contain('destroy:collection');\n            });\n\n        });\n\n        describe('.changeDatabase', function() {\n\n            after(function() {\n                col.changeDatabase(col.defaultDB);\n            });\n\n            it('changes profileId of a collection', function() {\n                expect(col.changeDatabase({profile: 'test'}).prototype.profileId).to.be.equal('test');\n            });\n\n            it('changes profileId of a model', function() {\n                expect(col.changeDatabase({profile: 'test1'}).prototype.model.prototype.profileId)\n                    .to.be.equal('test1');\n            });\n\n        });\n\n        describe('.onReset()', function() {\n\n            beforeEach(function() {\n                col.collection = collection.clone();\n            });\n\n            it('resets the collection', function(done) {\n                col.collection.once('reset', function() { done(); });\n                col.onReset();\n            });\n\n            it('calls collection.removeEvents() if it exists', function() {\n                var stub = sandbox.stub(col.collection, 'removeEvents');\n                col.onReset();\n                expect(stub).called;\n            });\n\n        });\n\n        describe('.save()', function() {\n            var model;\n\n            before(function() {\n                model = collection.at(0).clone();\n            });\n\n            beforeEach(function() {\n                sandbox.stub(col, 'encryptModel').returns(model);\n                sandbox.stub(model, 'save');\n            });\n\n            it('triggers `invalid` event if validation fails', function() {\n                model.once('invalid', itStub);\n\n                col.save(model, {title: ''});\n                expect(itStub).calledWithMatch(model, ['title']);\n            });\n\n            it('rejects the promise if validation fails', function() {\n                return col.save(model, {title: ''}).should.eventually.be.rejected;\n            });\n\n            it('calls model.setEscape() if it exists', function() {\n                sandbox.spy(model, 'setEscape');\n\n                col.save(model, {title: 'Hello', content: 'World'});\n                expect(model.setEscape).calledWith({title: 'Hello', content: 'World'});\n            });\n\n            it('encrypts the model before saving', function() {\n                col.save(model, {title: 'Hello', content: 'World'});\n                expect(col.encryptModel).called;\n            });\n\n            it('saves the model', function() {\n                return col.save(model, {title: 'hello'}).then(function() {\n                    expect(model.save).calledWithMatch({title: 'hello'}, {validate: false});\n                });\n            });\n\n        });\n\n        describe('.saveModel()', function() {\n            var model;\n\n            before(function() {\n                model = new col.Collection.prototype.model();\n            });\n\n            beforeEach(function() {\n                sandbox.stub(col, 'save', function(m, data) {\n                    m.set(data);\n                    return Q.resolve(m);\n                });\n\n                sandbox.stub(col, 'decryptModel').returns(model);\n            });\n\n            it('changes dates of model', function() {\n                expect(model.attributes.updated).to.be.equal(0);\n                expect(model.attributes.created).to.be.equal(0);\n\n                return col.saveModel(model, {title: 'Test'}).then(function() {\n                    expect(model.attributes.updated).not.to.be.equal(0);\n                    expect(model.attributes.created).not.to.be.equal(0);\n                });\n            });\n\n            it('triggers `sync:model` event', function() {\n                col.vent.once('sync:model', itStub);\n\n                return col.saveModel(model, {title: 'Test'}).then(function() {\n                    expect(itStub).calledWith(model);\n                });\n            });\n\n            it('triggers `update:model` event', function() {\n                col.vent.once('update:model', itStub);\n\n                return col.saveModel(model, {title: 'Test'}).then(function() {\n                    expect(itStub).calledWith(model);\n                });\n            });\n\n        });\n\n        describe('.saveCollection()', function() {\n\n            before(function() {\n                col.collection = collection.clone();\n            });\n\n            beforeEach(function() {\n                sandbox.stub(Q, 'invoke').returns(Q.resolve());\n            });\n\n            it('saves all changes in a collection', function() {\n                col.saveCollection();\n                expect(Q.invoke).callCount(col.collection.length);\n                expect(Q.invoke).calledWithMatch({}, 'save', {trash: 0});\n            });\n\n            it('triggers `saved:collection` event', function() {\n                col.vent.once('saved:collection', itStub);\n\n                return col.saveCollection().then(function() {\n                    expect(itStub).called;\n                });\n            });\n\n        });\n\n        describe('.saveRaw()', function() {\n            var model;\n\n            before(function() {\n                model = new col.collection.model({id: 'test-1', title: 'Test'});\n            });\n\n            beforeEach(function() {\n                sandbox.stub(col, 'decryptModel').returns(Q.resolve(model));\n                sandbox.stub(col, 'save').returns(Q.resolve(model));\n            });\n\n            it('instantiates a model with the provided profileId', function() {\n                sandbox.spy(col, 'changeDatabase');\n                col.saveRaw({id: 'test-1', title: 'Test'}, {profile: 'test-db'});\n                expect(col.changeDatabase).calledWith({profile: 'test-db'});\n            });\n\n            it('decrypts data before saving', function() {\n                col.saveRaw({id: 'test-1', title: 'Test'});\n                expect(col.decryptModel).calledBefore(col.save);\n                expect(col.decryptModel).calledWithMatch({id: 'test-1', attributes: {title: 'Test'}});\n            });\n\n            it('validates data before saving', function() {\n                var spy = sandbox.spy(col.Collection.prototype.model.prototype, 'validate');\n\n                return col.saveRaw({id: 'test-1', title: 'Test'}).then(function() {\n                    expect(spy).calledBefore(col.save);\n                });\n            });\n\n            it('saves data', function() {\n                return col.saveRaw({id: 'test-1', title: 'Test'}).then(function() {\n                    expect(col.save).calledWithMatch({id: 'test-1'}, {id: 'test-1', title: 'Test'});\n                });\n            });\n\n            it('triggers `update:model` and `synced:ID` after saving', function() {\n                var stub = sandbox.stub();\n                col.vent.once('synced:test-1', stub);\n                col.vent.once('update:model', itStub);\n\n                return col.saveRaw({id: 'test-1', title: 'Test'}).then(function() {\n                    expect(itStub).calledWithMatch({id: 'test-1'});\n                    expect(stub).calledWithMatch({id: 'test-1'});\n                });\n            });\n\n        });\n\n        describe('.saveAllRaw()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'saveRaw').returns(Q.resolve());\n            });\n\n            it('saves all data', function() {\n                return col.saveAllRaw(models, {profile: 'test-db'}).then(function() {\n                    expect(col.saveRaw).callCount(models.length);\n                    expect(col.saveRaw).calledWithMatch({}, {profile: 'test-db'});\n                });\n            });\n\n        });\n\n        describe('.remove()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'save').returns(Q.resolve());\n            });\n\n            it('changes a model\\'s `trash` status to `2`', function() {\n                var spy = sandbox.spy(col.collection.model.prototype, 'set');\n                col.remove({id: 'test-1'}, {});\n                expect(spy).calledWithMatch({trash: 2});\n            });\n\n            it('changes a model\\'s atributes to default values', function() {\n                var defaults = _.omit(col.collection.model.prototype.attributes, 'id', 'trash', 'updated');\n                defaults     = _.extend(defaults, {trash: 2});\n\n                return col.remove({id: 'test-1'}, {}).then(function() {\n                    expect(col.save).calledWithMatch({id: 'test-1'}, defaults);\n                });\n            });\n\n            it('triggers `destroy:model` event', function() {\n                col.vent.once('destroy:model', itStub);\n\n                return col.remove({id: 'test-1'}, {}).then(function() {\n                    expect(itStub).calledAfter(col.save);\n                });\n            });\n\n        });\n\n        describe('.getModel()', function() {\n\n            before(function() {\n                col.collection.add({id: 'test-1'});\n            });\n\n            beforeEach(function() {\n                sandbox.stub(col.collection.model.prototype, 'fetch').returns(Q.resolve());\n                sandbox.stub(col, 'decryptModel').returns(Q.resolve());\n            });\n\n            it('returns a new model with default values if ID was not provided', function() {\n                return col.getModel({}).should.eventually.contain({id: undefined});\n            });\n\n            it('returns model from cache if it is there', function() {\n                return col.getModel({id: 'test-1'}).should.eventually\n                    .contain({id: 'test-1'}, {profileId: col.defaultDB});\n            });\n\n            it('fetches from database if profileId of a model is different from collection\\'s', function() {\n                return col.getModel({id: 'test-1', profile: 'test-db'})\n                .then(function() {\n                    expect(col.collection.model.prototype.fetch).called;\n                });\n            });\n\n            it('returns null if if fetch rejects with `not found` error', function() {\n                col.collection.model.prototype.fetch.returns(Q.reject('not found'));\n                return col.getModel({id: 'test-1', profile: 'test-db'}).should.eventually\n                    .be.equal(null);\n            });\n\n        });\n\n        describe('.getAll()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'fetch').returns(Q.resolve(collection));\n            });\n\n            it('triggers `destroy:collection`', function() {\n                col.vent.once('destroy:collection', itStub);\n                col.getAll({});\n                expect(itStub).called;\n            });\n\n            it('calls .fetch()', function() {\n                col.getAll({profile: 'test-db'});\n                expect(col.fetch).calledWith({profile: 'test-db'});\n            });\n\n            it('provides filter conditions', function() {\n                col.getAll({profile: 'test-db', filter: 'active'});\n\n                expect(col.fetch).calledWithMatch({\n                    profile    : 'test-db',\n                    conditions : col.Collection.prototype.conditions.active\n                });\n            });\n\n            it('caches collection locally', function() {\n                return col.getAll({profile: 'test-db', filter: 'active'})\n                .then(function() {\n                    expect(col.collection).to.be.equal(collection);\n                    expect(col.collection.conditionFilter).to.be.equal('active');\n                    expect(col.collection.conditionCurrent)\n                        .to.be.equal(col.Collection.prototype.conditions.active);\n                });\n            });\n\n            it('calls .registerEvents() if it exist in a collection', function() {\n                sandbox.stub(col.collection, 'registerEvents');\n                return col.getAll({}).then(function() {\n                    expect(col.collection.registerEvents).called;\n                });\n            });\n\n        });\n\n        describe('.fetch()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'decryptModels').returns(Q.resolve());\n                sandbox.stub(col.Collection.prototype, 'fetch').returns(Q.resolve());\n            });\n\n            it('changes profileId before fetching', function() {\n                sandbox.spy(col, 'changeDatabase');\n                col.fetch({profile: 'test'});\n\n                expect(col.changeDatabase).calledWith({profile: 'test'});\n                expect(col.changeDatabase).calledBefore(col.Collection.prototype.fetch);\n            });\n\n            it('calls collection\\'s .fetch() function', function() {\n                col.fetch({profile: 'test'});\n                expect(col.Collection.prototype.fetch).calledWith({profile: 'test'});\n            });\n\n            it('returns in decrypted format if `encrypt` option is false', function() {\n                return col.fetch({profile: 'test'}).then(function() {\n                    expect(col.decryptModels).called;\n                });\n            });\n\n        });\n\n        describe('._isEncryptEnabled()', function() {\n            var configs,\n                model;\n\n            before(function() {\n                configs = {encrypt: 0, encryptBackup: {}};\n                Radio.reply('configs', 'get:object', function() { return configs; });\n            });\n\n            beforeEach(function() {\n                model = new col.Collection.prototype.model();\n            });\n\n            after(function() {\n                Radio.stopReplying('configs', 'get:object');\n            });\n\n            it('returns false if a collection store name is configs', function() {\n                col.Collection.prototype.storeName = 'configs';\n                expect(col._isEncryptEnabled()).to.be.equal(false);\n                col.Collection.prototype.storeName = 'notes';\n            });\n\n            it('returns false if encryption is disabled', function() {\n                expect(col._isEncryptEnabled(model)).to.be.equal(false);\n            });\n\n            it('returns false if a model does not have encryptKeys', function() {\n                model.encryptKeys = undefined;\n                expect(col._isEncryptEnabled(model)).to.be.equal(false);\n            });\n\n            it('returns true', function() {\n                configs.encrypt = 1;\n                expect(col._isEncryptEnabled(col.collection.model.prototype)).to.be.equal(true);\n            });\n\n        });\n\n        describe('.encryptModel()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, '_isEncryptEnabled').returns(false);\n            });\n\n            it('does nothing if encryption is disabled', function() {\n                return col.encryptModel({clear: true}).should.eventually\n                    .deep.equal({clear: true});\n            });\n\n            it('encrypts if encryption is enabled', function() {\n                col._isEncryptEnabled.returns(true);\n                Radio.replyOnce('encrypt', 'encrypt:model', itStub);\n\n                col.encryptModel({clear: true});\n                expect(itStub).calledWith({clear: true});\n            });\n\n        });\n\n        describe('.decryptModel()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, '_isEncryptEnabled').returns(false);\n            });\n\n            it('does nothing if encryption is disabled', function() {\n                return col.decryptModel({clear: true}).should.eventually\n                    .deep.equal({clear: true});\n            });\n\n            it('encrypts if encryption is enabled', function() {\n                col._isEncryptEnabled.returns(true);\n                Radio.replyOnce('encrypt', 'decrypt:model', itStub);\n\n                col.decryptModel({clear: true});\n                expect(itStub).calledWith({clear: true});\n            });\n\n        });\n\n        describe('.decryptModels()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, '_isEncryptEnabled').returns(false);\n            });\n\n            it('checks if encryption is enabled', function() {\n                col.decryptModels();\n                expect(col._isEncryptEnabled).calledWith(col.collection.model.prototype);\n            });\n\n            it('does nothing if encryption is disabled', function() {\n                return col.decryptModels().should.eventually\n                    .be.equal(col.collection);\n            });\n\n            it('decrypts models if encryption is enabled', function() {\n                col._isEncryptEnabled.returns(true);\n                Radio.replyOnce('encrypt', 'decrypt:models', itStub);\n\n                col.decryptModels();\n                expect(itStub).calledWith(col.collection);\n            });\n\n        });\n\n    });\n\n});\n\n"
  },
  {
    "path": "test/spec/collections/modules/notebooks.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/modules/notebooks',\n    'collections/modules/module',\n], function(sinon, Q, _, Radio, ColModule, Module) {\n    'use strict';\n\n    describe('collections/modules/notebooks', function() {\n        var col,\n            sandbox,\n            collection;\n\n        before(function() {\n            col = new ColModule();\n\n            collection = [];\n            for (var i = 0; i < 10; i++) {\n                collection.push({id: 'id-' + i});\n            }\n            collection = new col.Collection(collection);\n        });\n\n        after(function() {\n            col.destroy();\n        });\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n            sandbox.stub(col, 'save').returns(Q.resolve());\n            sandbox.stub(col, 'getModel').returns(Q.resolve());\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('.initialize()', function() {\n\n            it('has .Collection', function() {\n                expect(col.Collection.prototype.storeName).to.be.equal('notebooks');\n            });\n\n            it('listens on `.Collection.storeName` channel', function() {\n                expect(col.vent).to.be.an('object');\n                expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);\n            });\n\n            it('listens to requests', function() {\n                _.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {\n                    expect(col.vent._requests[reply]).to.be.an('object');\n                });\n            });\n\n        });\n\n        describe('.remove()', function() {\n            var model,\n                notesStub;\n\n            beforeEach(function() {\n                model = new col.Collection.prototype.model({id: 'test-id-1'});\n\n                sandbox.stub(col, 'updateChildren').returns(Q.resolve());\n                sandbox.stub(Module.prototype, 'remove');\n\n                notesStub = sandbox.stub().returns(Q.resolve([]));\n                Radio.reply('notes', 'change:notebookId', notesStub);\n            });\n\n            after(function() {\n                Radio.stopReplying('notes', 'change:notebookId');\n            });\n\n            it('tries to fetch model from database if the first argument is ID', function() {\n                col.remove('test-id', {profile: 'test1'});\n                expect(col.getModel).calledWith({id: 'test-id', profile: 'test1'});\n            });\n\n            it('calls .updateChildren()', function() {\n                return col.remove(model, {profile: 'test1'}).then(function() {\n                    expect(col.updateChildren).calledWithMatch(model);\n                });\n            });\n\n            it('makes `change:notebookId` request on `notes` channel', function() {\n                return col.remove(model, {profile: 'test1'}, true).then(function() {\n                    expect(notesStub).calledWith(model, true);\n                });\n            });\n\n            it('calls parent function', function() {\n                return col.remove(model, {profile: 'test1'}, true).then(function() {\n                    expect(Module.prototype.remove).calledWith(model, {profile: 'test1'});\n                });\n            });\n\n        });\n\n        describe('.updateChildren()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new col.Collection.prototype.model({\n                    id       : 'test-id-1',\n                    parentId : 'test-id-0'\n                });\n\n                sandbox.stub(col, 'getChildren').returns(Q.resolve(new col.Collection()));\n                sandbox.stub(col, 'saveModel').returns(Q.resolve());\n            });\n\n            it('calls .getChildren()', function() {\n                col.updateChildren(model);\n                expect(col.getChildren).calledWith(model.id, {profile: model.profileId});\n            });\n\n            it('updates parentId of each child notebook', function() {\n                col.getChildren.returns(Q.resolve(collection));\n                return col.updateChildren(model).then(function() {\n                    expect(col.saveModel).callCount(collection.length);\n                    expect(col.saveModel).calledWithMatch({}, {parentId: model.get('parentId')});\n                });\n            });\n\n        });\n\n        describe('.getChildren()', function() {\n\n            it('uses this.collection if it exists', function() {\n                col.collection = collection;\n                sandbox.spy(collection, 'clone');\n\n                col.getChildren('test-id-1', {});\n                expect(collection.clone).called;\n                col.collection = null;\n            });\n\n            it('fetches all child notebooks', function() {\n                sandbox.stub(col, 'fetch');\n                col.getChildren('test-id-1', {profile: 'test'});\n                expect(col.fetch).calledWith({\n                    conditions : {parentId : 'test-id-1'},\n                    profile    : 'test'\n                });\n            });\n\n        });\n\n        describe('.getAll()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(Module.prototype, 'getAll').returns(Q.resolve());\n            });\n\n            it('requests sorting direction configs', function() {\n                var stub = sandbox.stub().returns('');\n                Radio.replyOnce('configs', 'get:config', stub);\n\n                col.getAll({});\n                expect(stub).called;\n            });\n\n            it('returns current cached collection if it exists', function() {\n                col.collection = collection;\n                col.collection.profileId = 'testDb';\n\n                return col.getAll({profile: 'testDb'}).should.eventually\n                    .be.equal(collection);\n            });\n\n            it('calls the parent function otherwise', function() {\n                col.collection = null;\n                col.getAll({profile: 'testDb', filter: 'task'});\n                expect(Module.prototype.getAll).calledWith({profile: 'testDb', filter: 'task'});\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/modules/notes.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/modules/notes',\n    'collections/modules/module',\n], function(sinon, Q, _, Radio, ColModule, Module) {\n    'use strict';\n\n    describe('collections/modules/notes', function() {\n        var col,\n            sandbox,\n            collection;\n\n        before(function() {\n            col = new ColModule();\n\n            collection = [];\n            for (var i = 0; i < 10; i++) {\n                collection.push({id: 'id-' + i});\n            }\n            collection = new col.Collection(collection);\n        });\n\n        after(function() {\n            col.destroy();\n        });\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n            sandbox.stub(col, 'save').returns(Q.resolve());\n            sandbox.stub(col, 'getModel').returns(Q.resolve());\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('.initialize()', function() {\n\n            it('has .Collection', function() {\n                expect(col.Collection.prototype.storeName).to.be.equal('notes');\n            });\n\n            it('listens on `.Collection.storeName` channel', function() {\n                expect(col.vent).to.be.an('object');\n                expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);\n            });\n\n            it('listens to requests', function() {\n                _.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {\n                    expect(col.vent._requests[reply]).to.be.an('object');\n                });\n            });\n\n        });\n\n        describe('.reply()', function() {\n\n            it('returns replies', function() {\n                expect(col.reply()).to.be.an('object');\n                expect(_.keys(col.reply()).length).to.be.equal(3);\n            });\n\n        });\n\n        describe('.saveModel()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(Module.prototype, 'saveModel');\n            });\n\n            it('will try to add tags', function(done) {\n                Radio.replyOnce('tags', 'add', function(tags, options) {\n                    expect(tags).to.deep.equal(['tag1', 'tag2']);\n                    expect(options.profile).to.be.equal('test');\n                    done();\n                });\n                col.saveModel({profileId: 'test'}, {tags: ['tag1', 'tag2']});\n            });\n\n            it('saves the model afterwards', function() {\n                return col.saveModel({profileId: 'test'}, {tags: ['tag1']})\n                .then(function() {\n                    expect(Module.prototype.saveModel)\n                        .calledWith({profileId: 'test'}, {tags: ['tag1']});\n                });\n            });\n\n        });\n\n        describe('.remove()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new col.Collection.prototype.model();\n            });\n\n            it('tries to fetch model from database if only ID was provided', function() {\n                col.remove('test-id', {profile: 'test1'});\n                expect(col.getModel).calledWith('test-id', {profile: 'test1'});\n            });\n\n            it('removes the model instead of putting to trash if it is already there', function() {\n                var stub = sandbox.stub(Module.prototype, 'remove');\n                model.set('trash', 1);\n\n                return col.remove(model, {profile: 'test1'})\n                .then(function() {\n                    expect(stub).to.be.calledWith(model, {profile: 'test1'});\n                });\n            });\n\n            it('changes `trash` status to `1`', function() {\n                return col.remove(model, {profile: 'test1'})\n                .then(function() {\n                    expect(col.save).calledWithMatch(model, {trash: 1});\n                });\n            });\n\n            it('triggers `destroy:model` event', function(done) {\n                col.vent.once('destroy:model', function() { done(); });\n                col.remove(model, {profile: 'test1'});\n            });\n\n        });\n\n        describe('.restore()', function() {\n            var model;\n\n            before(function() {\n                model = new col.Collection.prototype.model({trash: 1});\n            });\n\n            it('tries to fetch model from database if only ID was provided', function() {\n                col.restore('test-id-2', {profile: 'test2'});\n                expect(col.getModel).calledWith('test-id-2', {profile: 'test2'});\n            });\n\n            it('changes `trash` status to `0`', function() {\n                return col.restore(model, {profile: 'test2'})\n                .then(function() {\n                    expect(col.save).calledWithMatch(model, {trash: 0});\n                });\n            });\n\n            it('triggers `restore:model` event', function(done) {\n                col.vent.once('restore:model', function() { done(); });\n                col.restore(model, {profile: 'test2'});\n            });\n\n        });\n\n        describe('.onNotebookRemove()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'fetch').returns(Q.resolve(collection));\n                sandbox.stub(col, 'saveModel').returns(Q.resolve());\n            });\n\n            it('fetches all notes which are attached to the notebook', function() {\n                return col.onNotebookRemove({id: 'test-id-3', profileId: 'test3'})\n                .then(function() {\n                    expect(col.fetch).calledWith({\n                        conditions: {notebookId: 'test-id-3'},\n                        profile   : 'test3'\n                    });\n                });\n            });\n\n            it('changes notes\\' notebookId to `0`', function() {\n                return col.onNotebookRemove({id: 'test-id-3', profileId: 'test3'})\n                .then(function() {\n                    expect(col.saveModel).callCount(collection.length);\n                    expect(col.saveModel).calledWithMatch({}, {notebookId: 0});\n                });\n            });\n\n            it('changes notes\\' trash status to `1` if remove argument is true', function() {\n                return col.onNotebookRemove({id: 'test-id-3', profileId: 'test3'}, true)\n                .then(function() {\n                    expect(col.saveModel).callCount(collection.length);\n                    expect(col.saveModel).calledWithMatch({}, {notebookId: 0, trash: 1});\n                });\n            });\n\n            it('does nothing if no notes were found', function() {\n                col.fetch.returns(Q.resolve([]));\n                return col.onNotebookRemove({id: 'test-id-3'}).should.eventually\n                    .be.equal(undefined);\n            });\n\n        });\n\n        describe('.getAll()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(Module.prototype, 'getAll').returns(Q.resolve(collection));\n            });\n\n            it('uses `active` filter if no filter is provided', function() {\n                col.getAll({});\n                expect(Module.prototype.getAll).calledWith({filter: 'active'});\n            });\n\n            it('calls ._filterOnFetch()', function() {\n                sandbox.spy(col, '_filterOnFetch');\n                return col.getAll({filter: 'task', query: 'hello'}).then(function() {\n                    expect(col._filterOnFetch)\n                        .calledWithMatch({}, {filter: 'task', query: 'hello'});\n                });\n            });\n\n        });\n\n        describe('._filterOnFetch()', function() {\n\n            it('calls collection.filterList()', function() {\n                var options = {filter: 'task', query: 'hello'};\n                sandbox.spy(collection, 'filterList');\n\n                col._filterOnFetch(collection, options);\n                expect(collection.filterList).calledWith(options.filter, options);\n            });\n\n        });\n\n        describe('.getModelFull()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new col.Collection.prototype.model({\n                    id         : 'test-id-4',\n                    notebookId : 'test-notebook-id',\n                    files      : ['file-id-1']\n                });\n                col.getModel.returns(Q.resolve(model));\n            });\n\n            it('fetches a model', function() {\n                col.getModelFull({id: 'test-id-4'});\n                expect(col.getModel).calledWith({id: 'test-id-4'});\n            });\n\n            it('requests the notebook attached to a note', function(done) {\n                Radio.replyOnce('notebooks', 'get:model', function(data) {\n                    expect(data.id).to.be.equal(model.get('notebookId'));\n                    done();\n                });\n\n                col.getModelFull({id: 'test-id-4', profile: 'test-db'});\n            });\n\n            it('requests files attached to a note', function(done) {\n                Radio.replyOnce('files', 'get:files', function(files) {\n                    expect(files).to.be.equal(model.get('files'));\n                    done();\n                });\n\n                col.getModelFull({id: 'test-id-4', profile: 'test-db'});\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/modules/tags.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, after, afterEach, it */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone.radio',\n    'collections/modules/tags',\n    'collections/modules/module',\n], function(sinon, Q, _, Radio, ColModule, Module) {\n    'use strict';\n\n    describe('collections/modules/tags', function() {\n        var col,\n            sandbox,\n            stubSha;\n\n        before(function() {\n            col = new ColModule();\n        });\n\n        after(function() {\n            col.destroy();\n            Radio.stopReplying('encrypt', 'sha256');\n        });\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n            sandbox.stub(col, 'save').returns(Q.resolve());\n            sandbox.stub(col, 'getModel').returns(Q.resolve());\n\n            stubSha = sandbox.stub().returns('tag'.split(''));\n            Radio.reply('encrypt', 'sha256', stubSha);\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('.initialize()', function() {\n\n            it('has .Collection', function() {\n                expect(col.Collection.prototype.storeName).to.be.equal('tags');\n            });\n\n            it('listens on `.Collection.storeName` channel', function() {\n                expect(col.vent).to.be.an('object');\n                expect(col.vent.channelName).to.be.equal(col.Collection.prototype.storeName);\n            });\n\n            it('listens to requests', function() {\n                _.each(_.union(Module.prototype.reply(), col.reply()), function(reply) {\n                    expect(col.vent._requests[reply]).to.be.an('object');\n                });\n            });\n\n        });\n\n        describe('.addTags()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(col, 'addTag').returns(Q.resolve());\n            });\n\n            it('does nothing if tags array is empty', function() {\n                return col.addTags([]).should.eventually.be.an('undefined');\n            });\n\n            it('adds all tags from array', function() {\n                return col.addTags(['tag', 'tag2', 'tag3']).then(function() {\n                    expect(col.addTag).callCount(3);\n                });\n            });\n\n        });\n\n        describe('.addTag()', function() {\n            var model;\n\n            before(function() {\n                model = new col.Collection.prototype.model({id: 'test-id-tag'});\n            });\n\n            beforeEach(function() {\n                sandbox.stub(col, 'saveModel');\n            });\n\n            it('requests sha256 salt of a tag', function() {\n                col.addTag('tag');\n                expect(stubSha).calledWith('tag');\n            });\n\n            it('will check if model with the same ID exists in database', function() {\n                return col.addTag('tag', {}).then(function() {\n                    expect(col.getModel).calledWithMatch({id: 'tag'});\n                    expect(col.saveModel).calledWithMatch({}, {name: 'tag'});\n                });\n            });\n\n            it('will save it if model exists but its name is empty', function() {\n                col.getModel.returns(model);\n\n                return col.addTag('tag', {}).then(function() {\n                    expect(col.saveModel).calledWithMatch({}, {name: 'tag'});\n                });\n            });\n\n            it('will not create a new model if a tag already exists', function() {\n                model.set('name', 'tag-name');\n                col.getModel.returns(model);\n                return col.addTag('tag', {}).should.eventually.equal(model);\n            });\n\n        });\n\n        describe('.saveModel()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new col.Collection.prototype.model({id: 'test-id-1'});\n                sandbox.stub(Module.prototype, 'saveModel');\n            });\n\n            it('throws an error if validation fails', function() {\n                return col.saveModel(model, {name: ''}).should.be.rejected;\n            });\n\n            it('triggers `invalid` event on model', function(done) {\n                model.once('invalid', function() { done(); });\n                col.saveModel(model, {name: ''});\n            });\n\n            it('generates ID from SHA salted name of the tag to avoid duplicates', function() {\n                col.saveModel(model, {name: 'testTag'});\n                expect(stubSha).calledWith('testTag');\n            });\n\n            it('removes model if it has ID to avoid duplicates', function() {\n                sandbox.stub(col, 'remove').returns(Q.resolve());\n\n                return col.saveModel(model, {name: 'testTag'}).then(function() {\n                    expect(col.remove).calledWith(model, {profile: model.profileId});\n                });\n            });\n\n            it('calls the parent function to save changes', function() {\n                return col.saveModel(model, {name: 'testTag'}).then(function() {\n                    expect(Module.prototype.saveModel)\n                        .calledWithMatch(model, {name: 'testTag'});\n                });\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/notebooks.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, it, beforeEach, before */\ndefine([\n    'sinon',\n    'underscore',\n    'backbone.radio',\n    'collections/notebooks'\n], function(sinon, _, Radio, Collection) {\n    'use strict';\n\n    describe('collections/notebooks', function() {\n        var collection,\n            models;\n\n        before(function() {\n            models = [];\n            for (var i = 0; i < 10; i++) {\n                models.push({\n                    id       : 'id-' + i,\n                    name     : 'Notebook' + i,\n                    parentId : (i < 5 && i !== 0 ? 'id-' + (i - 1) : '0'),\n                    trash    : 0\n                });\n            }\n        });\n\n        beforeEach(function() {\n            collection = new Collection(models);\n        });\n\n        describe('.sortItOut()', function() {\n\n            beforeEach(function() {\n                sinon.stub(collection, 'getTree').returns([collection.at(0)]);\n            });\n\n            it('calls .getTree()', function() {\n                collection.sortItOut();\n                expect(collection.getTree).to.have.been.called;\n            });\n\n            it('overwrites .models with what .getTree() returns', function() {\n                collection.sortItOut();\n                expect(collection.models.length).to.be.equal(1);\n                expect(collection.models[0]).to.be.equal(collection.at(0));\n            });\n\n        });\n\n        describe('.sortFullCollection()', function() {\n\n            beforeEach(function() {\n                sinon.stub(collection, 'sortItOut', function() {\n                    this.models = [collection.at(0)];\n                });\n            });\n\n            it('calls .sortItOut()', function() {\n                collection.sortFullCollection();\n                expect(collection.sortItOut).to.have.been.called;\n            });\n\n            it('resets the collection', function() {\n                var spy = sinon.spy(collection, 'reset');\n                collection.sortFullCollection();\n                expect(spy).to.have.been.called;\n            });\n\n        });\n\n        describe('._onAddItem()', function() {\n\n            beforeEach(function() {\n                sinon.stub(collection, 'sortFullCollection');\n                sinon.stub(collection, '_navigateOnRemove');\n            });\n\n            it('removes a model from collection if it does not meet condition', function() {\n                collection.conditionCurrent = {id: 'id-2'};\n                collection._onAddItem(collection.at(0));\n\n                expect(collection._navigateOnRemove).to.have.been\n                    .calledWith(collection.at(0));\n            });\n\n            it('removes a model from collection if trash === 0', function() {\n                collection.at(1).set('trash', 1);\n                collection._onAddItem(collection.at(1));\n\n                expect(collection._navigateOnRemove).to.have.been\n                    .calledWith(collection.at(1));\n            });\n\n            it('if the model was found, it updates its attributes', function() {\n                var model = collection.at(2).clone();\n                model.set({name: 'Hello World'});\n\n                expect(model.get('name')).not.to.be.equal(collection.at(2).get('name'));\n                collection._onAddItem(model);\n                expect(model.get('name')).to.be.equal('Hello World');\n            });\n\n            it('if the model was not found, it adds it to the collection', function() {\n                var model = new Collection.prototype.model({name: 'Test', id: 'id-test-1'});\n\n                expect(collection.get(model.id)).to.be.an('undefined');\n                collection._onAddItem(model);\n                expect(collection.get(model.id)).to.be.an('object');\n            });\n\n            it('triggers `model:navigate` event', function(done) {\n                Radio.once('notebooks', 'model:navigate', function(model) {\n                    expect(model).to.be.equal(collection.at(0));\n                    done();\n                });\n\n                collection._onAddItem(collection.at(0));\n            });\n\n        });\n\n        it('.rejectTree()', function() {\n            for (var i = 0; i < 10; i++) {\n\n                // 5 first models are linked in the mockup\n                var l = (i < 5 ? (5 - i) : 1);\n\n                expect(collection.rejectTree(collection.at(i).id).length)\n                    .to.be.equal(collection.length - l);\n            }\n        });\n\n        describe('.getTree()', function() {\n\n            it('returns array', function() {\n                expect(collection.getTree()).to.be.an('array');\n            });\n\n        });\n\n        it('.getChildren()', function() {\n            expect(collection.getChildren('id-0')).to.be.an('array');\n            expect(collection.getChildren('id-0').length).to.be.equal(1);\n            expect(collection.getChildren('id-1').length).to.be.equal(1);\n        });\n\n        it('.getRoots()', function() {\n            expect(collection.getRoots()).to.be.an('array');\n            expect(collection.getRoots().length).to.be.equal(collection.length - 4);\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/notes.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, beforeEach, before, it */\ndefine([\n    'sinon',\n    'underscore',\n    'collections/notes'\n], function(sinon, _, Collection) {\n    'use strict';\n\n    /*jshint multistr: true */\n    var lorem = 'Lorem Ipsum is simply dummy text of the printing and \\\n        typesetting industry. Lorem Ipsum has been the industry\\'s standard \\\n        dummy text ever since the 1500s, when an unknown printer took a galley\\\n        of type and scrambled it to make a type specimen book.';\n\n    describe('collections/notes', function() {\n        var collection;\n\n        beforeEach(function() {\n            collection = new Collection();\n        });\n\n        describe('.conditions', function() {\n\n            it('.notebook returns {notebookId: `query`, trash: 0}', function() {\n                var res = collection.conditions.notebook({query: 'tasks'});\n\n                expect(res.notebookId).to.be.equal('tasks');\n                expect(res.trash).to.be.equal(0);\n            });\n\n        });\n\n        describe('.filterList()', function() {\n\n            it('does nothing if filter argument was not provided', function() {\n                expect(collection.filterList()).to.be.an('undefined');\n            });\n\n            it('does nothing if a filter method does not exist', function() {\n                expect(collection.filterList('all404')).to.be.an('undefined');\n            });\n\n            it('executes filter method', function() {\n                var spy = sinon.spy(collection, 'taskFilter');\n                collection.filterList('task', {query: 'testing'});\n                expect(spy).to.have.been.calledWith('testing');\n            });\n\n            it('resets the collection', function(done) {\n                collection.add([{isOk: false}, {isOk: true}]);\n                sinon.stub(collection, 'taskFilter').returns([collection.at(0)]);\n\n                collection.once('reset', function() {\n                    expect(collection.length).to.be.equal(1);\n                    done();\n                });\n                collection.filterList('task', {});\n            });\n\n        });\n\n        it('.taskFilter()', function() {\n            collection.add([\n                {taskCompleted: 1, taskAll: 10},\n                {taskCompleted: 9, taskAll: 10},\n                {taskCompleted: 20, taskAll: 20},\n            ]);\n\n            expect(collection.taskFilter().length).to.be.equal(2);\n        });\n\n        describe('.tagFilter()', function() {\n\n            it('requires notes to have tags', function() {\n                collection.add([{id: '1'}, {id: '2'}]);\n                expect(collection.tagFilter('tag').length).to.be.equal(0);\n            });\n\n            it('returns notes which are tagged with the tag', function() {\n                collection.add([\n                    {trash: 0, tags: ['test', 'test2']},\n                    {trash: 1, tags: ['test']},\n                    {trash: 0, tags: ['test2', 'test3']},\n                    {trash: 0, tags: ['test2', 'test3', 'test']},\n                ]);\n\n                expect(collection.tagFilter('test').length).to.be.equal(2);\n            });\n\n        });\n\n        describe('.searchFilter()', function() {\n            var models;\n\n            before(function() {\n                models = [];\n                for (var i = 0; i < 20; i++) {\n                    models.push({\n                        'id': i,\n                        'title': 'Test note number title ' + i,\n                        'content': lorem + ' ' + i + ' content test content' + i\n                    });\n                }\n            });\n\n            beforeEach(function() {\n                collection.add(models);\n            });\n\n            it('does nothing if letters argument is empty', function() {\n                expect(collection.searchFilter().length).to.be.equal(models.length);\n                expect(collection.searchFilter('').length).to.be.equal(models.length);\n            });\n\n            it('can find a string in the title', function() {\n                expect(collection.searchFilter('title 10').length).to.be.equal(1);\n            });\n\n            it('can find a string in the content', function() {\n                expect(collection.searchFilter('content19').length).to.be.equal(1);\n            });\n\n            it('can perform case-insensetive search', function() {\n                expect(collection.searchFilter('test').length).to.be.equal(models.length);\n                expect(collection.searchFilter('NotE').length).to.be.equal(models.length);\n                expect(collection.searchFilter('NUMBER tITle 11').length).to.be.equal(1);\n            });\n\n        });\n\n        it('.fuzzySearch()', function() {\n            collection.add([\n                {id: 1, title: 'The Great Gatsby. Test'},\n                {id: 2, title: 'The DaVinci Code. Test'},\n                {id: 3, title: 'Angels & Demons'},\n            ]);\n            collection.fullCollection = collection.clone();\n\n            expect(collection.fuzzySearch('gaby').length).to.be.equal(1);\n            expect(collection.fuzzySearch('gaby')[0].id).to.be.equal(1);\n            expect(collection.fuzzySearch('tst').length).to.be.equal(2);\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/pageable.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, before, beforeEach, afterEach, it */\ndefine([\n    'sinon',\n    'underscore',\n    'backbone.radio',\n    'collections/pageable'\n], function(sinon, _, Radio, Pageable) {\n    'use strict';\n\n    describe('collections/pageable', function() {\n        var page,\n            vent,\n            models;\n\n        before(function() {\n            vent   = Radio.channel('notes');\n            models = [];\n            for (var i = 0; i < 20; i++) {\n                models.push({id: i, title: 'hello'});\n            }\n        });\n\n        beforeEach(function() {\n            page = new Pageable(models);\n            page.storeName = 'notes';\n            page.fullCollection = page.clone();\n        });\n\n        describe('.registerEvents()', function() {\n\n            beforeEach(function() {\n                page.registerEvents();\n            });\n\n            afterEach(function() {\n                page.stopListening();\n            });\n\n            it('channel name for events is `storeName`', function() {\n                expect(page.vent).to.be.an('object');\n                expect(page.vent.channelName).to.be.equal(page.storeName);\n            });\n\n            it('starts listening to events', function() {\n                page.stopListening();\n\n                expect((page._events || {})['change:isFavorite']).to.be.an('undefined');\n                expect((page.vent._events || {})['update:model']).to.be.an('undefined');\n\n                page.registerEvents();\n                expect(page._events).to.be.an('object');\n                expect(page.vent).to.be.an('object');\n            });\n\n            it('listens to events triggered to itself', function() {\n                expect(page._events['change:isFavorite']).to.be.an('array');\n                expect(page._events.reset).to.be.an('array');\n            });\n\n            it('listens to events triggered to .vent', function() {\n                expect(page.vent._events['update:model']).to.be.an('array');\n                expect(page.vent._events['destroy:model']).to.be.an('array');\n                expect(page.vent._events['restore:model']).to.be.an('array');\n            });\n\n        });\n\n        describe('.removeEvents()', function() {\n\n            it('stops listening to events', function() {\n                page.registerEvents().removeEvents();\n\n                expect((page._events || {})['change:isFavorite']).to.be.an('undefined');\n                expect((page.vent._events || {})['update:model']).to.be.an('undefined');\n            });\n\n            it('resets .fullCollection', function() {\n                var spy = sinon.spy(page.fullCollection, 'reset');\n                page.removeEvents();\n                expect(spy).to.have.been.called;\n            });\n\n        });\n\n        describe('.getNextPage()', function() {\n\n            it('resets itself', function() {\n                var spy = sinon.spy(page, 'reset');\n                page.getNextPage();\n                expect(spy).to.have.been.called;\n            });\n\n            it('increases current page number', function() {\n                var initial = page.state.currentPage;\n                page.getNextPage();\n                expect(page.state.currentPage).to.be.equal(initial + 1);\n            });\n\n        });\n\n        describe('.getPreviousPage()', function() {\n\n            it('resets itself', function() {\n                var spy = sinon.spy(page, 'reset');\n                page.getPreviousPage();\n                expect(spy).to.have.been.called;\n            });\n\n            it('increases current page number', function() {\n                var initial = page.state.currentPage;\n                page.getPreviousPage();\n                expect(page.state.currentPage).to.be.equal(initial - 1);\n            });\n\n        });\n\n        describe('.getPage()', function() {\n\n            it('returns an array', function() {\n                expect(page.getPage(0)).to.be.an('array');\n            });\n\n            it('changes currentPage state', function() {\n                page.getPage(25);\n                expect(page.state.currentPage).to.be.equal(25);\n            });\n\n            it('overwrites .models', function() {\n                var length = page.models.length;\n                page.getPage(1);\n                expect(page.models.length).to.be.equal(page.state.pageSize);\n                expect(page.models.length).to.be.below(length);\n            });\n\n        });\n\n        describe('.getOffset()', function() {\n\n            it('offset is equal page number * page size', function() {\n                expect(page.getOffset(2)).to.be.equal(2 * page.state.pageSize);\n            });\n\n            it('uses different formula if first page does not start from 0', function() {\n                page.state.firstPage = 1;\n                expect(page.getOffset(2)).to.be.equal((2 - 1) * page.state.pageSize);\n            });\n\n        });\n\n        it('.hasPreviousPage()', function() {\n            expect(page.hasPreviousPage()).to.be.equal(false);\n            page.state.currentPage = 1;\n            expect(page.hasPreviousPage()).to.be.equal(true);\n        });\n\n        it('.hasNextPage()', function() {\n            page.state.totalPages = models.length;\n\n            expect(page.hasNextPage()).to.be.equal(true);\n            page.state.currentPage = models.length;\n            expect(page.hasNextPage()).to.be.equal(true);\n        });\n\n        describe('.sortFullCollection()', function() {\n\n            it('does nothing if .fullCollection does not exist', function() {\n                page.fullCollection = null;\n                page.sortFullCollection();\n                expect(page.models.length).to.be.equal(models.length);\n            });\n\n            it('sorts .fullCollection', function() {\n                var spy = sinon.spy(page.fullCollection, 'sortItOut');\n                page.sortFullCollection();\n                expect(spy).to.have.been.called;\n            });\n\n            it('calls ._updateTotalPages()', function() {\n                var spy = sinon.spy(page, '_updateTotalPages');\n                page.sortFullCollection();\n                expect(spy).to.have.been.called;\n            });\n\n            it('calls .getPage()', function() {\n                var spy = sinon.spy(page, 'getPage');\n                page.sortFullCollection();\n                expect(spy).to.have.been.calledWith(page.state.currentPage);\n            });\n\n            it('resets itself', function(done) {\n                page.once('reset', function() { done(); });\n                page.sortFullCollection();\n            });\n\n        });\n\n        describe('.getNextItem()', function() {\n\n            beforeEach(function() {\n                page.registerEvents();\n            });\n\n            afterEach(function() {\n                page.removeEvents();\n            });\n\n            it('returns false if the collection is empty', function() {\n                page.reset([]);\n                expect(page.getNextItem()).to.be.equal(false);\n            });\n\n            it('triggers `model:navigate` event', function(done) {\n                page.vent.once('model:navigate', function(model) {\n                    expect(model).to.be.equal(page.at(1));\n                    done();\n                });\n\n                page.getNextItem(page.at(0));\n            });\n\n            it('returns the first model if ID is incorrect', function(done) {\n                page.vent.once('model:navigate', function(model) {\n                    expect(model.id).to.be.equal(page.at(0).id);\n                    done();\n                });\n\n                page.getNextItem('hello-world');\n            });\n\n            it('triggers `page:next` if it\\'s the last model on page', function(done) {\n                page.models = page.models.slice(0, page.state.pageSize);\n                page.once('page:next', done);\n                page.getNextItem(page.at(page.state.pageSize - 1));\n            });\n\n            it('triggers `page:end` if it\\'s the last model', function(done) {\n                page.models = page.models.slice(0, page.state.pageSize);\n                page.state  = _.extend(page.state, {\n                    currentPage: 1,\n                    totalPages : 2\n                });\n\n                page.once('page:end', done);\n                page.getNextItem(page.at(page.models.length - 1));\n            });\n\n        });\n\n        describe('.getPreviousPage()', function() {\n\n            beforeEach(function() {\n                page.registerEvents();\n            });\n\n            afterEach(function() {\n                page.removeEvents();\n            });\n\n            it('returns false if the collection is empty', function() {\n                page.reset([]);\n                expect(page.getPreviousItem()).to.be.equal(false);\n            });\n\n            it('triggers `model:navigate` event', function(done) {\n                page.vent.once('model:navigate', function(model) {\n                    expect(model.id).to.be.equal(page.at(2).id);\n                    done();\n                });\n\n                page.getPreviousItem(page.at(3));\n            });\n\n            it('returns the last model if ID is incorrect', function(done) {\n                page.vent.once('model:navigate', function(model) {\n                    expect(model.id).to.be.equal(page.at(page.length - 1).id);\n                    done();\n                });\n\n                page.getPreviousItem('hello-world');\n            });\n\n            it('triggers `page:previous` if it\\'s the first model on the page', function(done) {\n                page.state.currentPage = 2;\n                page.once('page:previous', done);\n                page.getPreviousItem(page.at(0));\n            });\n\n            it('triggers `page:start` if it\\'s the first model', function(done) {\n                page.state.currentPage = page.state.firstPage;\n                page.once('page:start', done);\n                page.getPreviousItem(page.at(0));\n            });\n\n        });\n\n        describe('._navigateOnRemove()', function() {\n\n            it('does nothing if the model isn\\'t in the collection', function() {\n                var model = page.at(0);\n                page.remove(model);\n                expect(page._navigateOnRemove(model)).to.be.equal(false);\n            });\n\n            it('removes the model from the collection', function() {\n                var spy = sinon.spy(page.fullCollection, 'remove');\n                page._navigateOnRemove(page.at(1));\n                expect(spy).called;\n            });\n\n            it('sorts .fullCollection', function() {\n                var spy = sinon.spy(page, 'sortFullCollection');\n                page._navigateOnRemove(page.at(0));\n                expect(spy).to.be.called;\n            });\n\n            it('triggers `model:navigate`', function(done) {\n                vent.once('model:navigate', function(model) {\n                    expect(model.id).to.be.equal(page.at(0).id);\n                    done();\n                });\n\n                page._navigateOnRemove(page.at(0));\n            });\n\n        });\n\n        describe('._onRestore()', function() {\n\n            it('calls ._onAddItem() if .conditionFilter is not equal to `trashed`', function(done) {\n                page._onAddItem = done;\n                page._onRestore();\n            });\n\n            it('.conditionFilter is equal to `trashed` and it\\'s not the last model', function(done) {\n                page.conditionFilter = 'trashed';\n                page._navigateOnRemove = done;\n                page._onRestore();\n            });\n\n        });\n\n        describe('._onAddItem()', function() {\n            var model;\n\n            beforeEach(function() {\n                model = new page.model({id: 'test-on-add-item', title: 'hello', trash: 0});\n            });\n\n            it('does not add models from other profiles', function() {\n                model.profileId = 'test-db';\n                page._onAddItem(model);\n                expect(page.get(model.id)).to.be.an('undefined');\n            });\n\n            it('removes the model from the collection if it does not meet condition', function() {\n                var spy = sinon.spy(page, '_navigateOnRemove');\n                page.conditionCurrent = {title: 'world'};\n                page._onAddItem(model);\n\n                expect(spy).calledWith(model);\n            });\n\n            it('adds a model to the collection if it does not exist', function() {\n                page._onAddItem(model);\n                expect(page.at(0).id).to.be.equal(model.id);\n            });\n\n        });\n\n        describe('._onRemoveItem()', function() {\n\n            it('removes the model from .fullCollection', function() {\n                var spy = sinon.spy(page.fullCollection, 'remove');\n                page._onRemoveItem(page.at(0));\n                expect(spy).to.be.called;\n            });\n\n            it('sorts .fullCollection afterwards', function(done) {\n                page.sortFullCollection = done;\n                page._onRemoveItem(page.at(0));\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/collections/tags.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, beforeEach, before, it */\ndefine([\n    'sinon',\n    'underscore',\n    'backbone.radio',\n    'collections/tags'\n], function(sinon, _, Radio, Collection) {\n    'use strict';\n\n    describe('collections/tags', function() {\n        var collection,\n            models;\n\n        before(function() {\n            models = [];\n            for (var i = 0; i < 40; i++) {\n                models.push({\n                    id: 'id-' + i,\n                    name: 'Tag ' + i,\n                });\n            }\n        });\n\n        beforeEach(function() {\n            collection = new Collection(models);\n            collection.fullCollection = collection.clone();\n        });\n\n        describe('._onAddItem()', function() {\n\n            it('triggers `model:navigate` event', function(done) {\n                Radio.once('tags', 'model:navigate', function(model) {\n                    expect(model).to.be.equal(collection.at(0));\n                    done();\n                });\n\n                collection._onAddItem(collection.at(0));\n            });\n\n        });\n\n        describe('.sortFullCollection()', function() {\n\n            it('does nothing if .fullCollection does not exist', function() {\n                collection.fullCollection = undefined;\n                expect(collection.sortFullCollection()).to.be.an('undefined');\n            });\n\n            it('sorts full collection', function() {\n                var spy = sinon.spy(collection.fullCollection, 'sortItOut');\n                collection.sortFullCollection();\n                expect(spy).to.have.been.called;\n            });\n\n            it('updates pagination state', function() {\n                var spy = sinon.spy(collection, '_updateTotalPages');\n                collection.sortFullCollection();\n                expect(spy).to.have.been.called;\n            });\n\n            it('resets the collection', function(done) {\n                collection.once('reset', function() {\n                    expect(collection.length).not.to.be.equal(models.length);\n                    expect(collection.fullCollection.length).to.be.equal(models.length);\n                    done();\n                });\n\n                collection.sortFullCollection();\n            });\n\n        });\n\n        describe('.getPage()', function() {\n            var firstModels;\n\n            beforeEach(function(done) {\n                firstModels = collection.models.slice(0, collection.state.pageSize);\n                collection.once('reset', function() { done(); });\n                collection.reset(firstModels);\n            });\n\n            it('gets offset number', function() {\n                var spy = sinon.spy(collection, 'getOffset');\n                collection.getPage(0);\n                expect(spy).to.have.been.called;\n            });\n\n            it('saves the page number into .state', function() {\n                collection.getPage(1);\n                expect(collection.state.currentPage).to.be.equal(1);\n            });\n\n            it('adds more models if page number is not 0', function() {\n                var spy = sinon.spy(collection, 'add');\n\n                expect(collection.length).not.to.be.equal(models.length);\n                collection.getPage(1);\n\n                expect(collection.length).to.be.equal(2 * collection.state.pageSize);\n                expect(spy).to.have.been.called;\n            });\n\n            it('resets the collection if page number is 0', function() {\n                var spy = sinon.spy(collection, 'reset');\n                collection.getPage(0);\n                expect(spy).to.have.been.called;\n            });\n\n        });\n\n        it('.hasPreviousPage()', function() {\n            expect(collection.hasPreviousPage()).to.be.equal(false);\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/helpers/db.js",
    "content": "/* global define, describe, it, expect, before, after */\ndefine([\n    'q',\n    'underscore',\n    'helpers/db'\n], function(Q, _, db) {\n    'use strict';\n\n    describe('helpers/db', function() {\n        var options;\n\n        this.timeout(8000);\n\n        before(function() {\n            options = {profile: 'test-lav', storeName: 'notes'};\n        });\n\n        after(function() {\n            db.dbs = {};\n            window.indexedDB.deleteDatabase('test-lav');\n        });\n\n        describe('.getDB()', function() {\n\n            it('returns a localForage instance', function() {\n                var dbInstance = db.getDb(options);\n                expect(dbInstance).to.be.an('object');\n                expect(dbInstance.INDEXEDDB).to.be.equal('asyncStorage');\n            });\n\n            it('caches an instance to `dbs` variable', function() {\n                var dbId = options.profile + '/notebooks';\n                expect(db.dbs[dbId]).to.be.an('undefined');\n                db.getDb({profile: options.profile, storeName: 'notebooks'});\n                expect(db.dbs[dbId]).to.be.an('object');\n            });\n\n        });\n\n        describe('.find()', function() {\n\n            it('returns a promise', function() {\n                expect(db.find({id: 'random', options: options}))\n                    .to.have.property('promiseDispatch');\n            });\n\n            it('fails if an item was not found', function(done) {\n                return db.find({id: '404data', options: options})\n                    .should.be.rejectedWith('not found')\n                    .and.notify(done);\n            });\n\n            it('can find an item', function(done) {\n                new Q(db.getDb(options).setItem('testId', {content: 'hello'}))\n                .then(function() {\n                    return db.find({id: 'testId', options: options});\n                })\n                .then(function(data) {\n                    expect(data).to.be.an('object');\n                    expect(data.content).to.be.equal('hello');\n                    done();\n                });\n            });\n\n        });\n\n        describe('.findAll()', function() {\n\n            before(function(done) {\n                Q.all([\n                    db.getDb(options).setItem('testId-1', {content: 'hello'}),\n                    db.getDb(options).setItem('testId-2', {content: 'hello'})\n                ]).then(function() { done(); });\n            });\n\n            it('returns a promise', function() {\n                expect(db.findAll({options: options})).to.have.property('promiseDispatch');\n            });\n\n            it('resolves with empty array if DB is empty', function() {\n                return db.findAll({options: {profile: options.profile, storeName: 'tags'}})\n                    .should.eventually.have.property('length').equal(0);\n            });\n\n            it('resolves with data if items were found', function() {\n                return db.findAll({options: options}).should.eventually\n                    .have.property('length').least(1);\n            });\n\n        });\n\n        describe('.findByKeys', function() {\n\n            before(function(done) {\n                Q.all([\n                    db.getDb(options).setItem('key1', {key: true, content: 'hello'}),\n                    db.getDb(options).setItem('key2', {key: false, content: 'hello'})\n                ]).then(function() { done(); });\n            });\n\n            it('returns a promise', function() {\n                expect(db.findByKeys(['key1', 'key2'], {options: options}))\n                    .to.have.property('promiseDispatch');\n            });\n\n            it('can find items with provided keys', function() {\n                return db.findByKeys(['key1', 'key2'], {options: options})\n                    .should.eventually.have.property('length').equal(2);\n            });\n\n            it('can filter items', function() {\n                return db.findByKeys(['key1', 'key2'], {options: {\n                    conditions : {key: true},\n                    profile    : options.profile,\n                    storeName  : options.storeName\n                }}).should.eventually.have.property('length').equal(1);\n            });\n\n        });\n\n        describe('.save()', function() {\n            var opt;\n\n            before(function() {\n                opt = _.extend({encryptKeys: ['content', 'secret']}, options);\n            });\n\n            it('returns a promise', function() {\n                expect(db.save({options: options, id: 'save1', data: {}}))\n                    .to.have.property('promiseDispatch');\n            });\n\n            it('saves an item', function(done) {\n                db.save({options: options, id: 'save2', data: {key: 'hello'}})\n                .then(function() {\n                    return db.find({options: options, id: 'save2'});\n                })\n                .then(function(data) {\n                    expect(data.key).to.be.equal('hello');\n                    done();\n                });\n            });\n\n            it('omits all encryptKeys if .encryptedData is not empty', function(done) {\n                var data = {\n                    content       : 'hello',\n                    secret        : 'world',\n                    disclosed     : 'data',\n                    encryptedData : 'encrypted data'\n                };\n\n                db.save({options: opt, id: 'save3', data: data})\n                .then(function() {\n                    return db.find({options: options, id: 'save3'});\n                })\n                .then(function(sData) {\n                    expect(_.keys(sData).length).to.be.equal(2);\n                    expect(sData.content).to.be.an('undefined');\n                    done();\n                });\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/helpers/i18next.js",
    "content": "/*jshint expr: true*/\ndefine([\n    'sinon',\n    'q',\n    'jquery',\n    'underscore',\n    'backbone.radio',\n    'i18next',\n    'i18nextXHRBackend',\n    'helpers/i18next'\n], function(sinon, Q, $, _, Radio, i18n, XHR, helper) {\n    'use strict';\n\n    describe('helpers/i18next', function() {\n        var sandbox;\n\n        beforeEach(function() {\n            sandbox = sinon.sandbox.create();\n        });\n\n        afterEach(function() {\n            sandbox.restore();\n        });\n\n        describe('.init()', function() {\n\n            beforeEach(function() {\n                sandbox.stub(i18n, 'init');\n                sandbox.stub(helper, 'getLang');\n            });\n\n            it('adds i18next to jQuery', function() {\n                helper.init();\n                expect($.t).to.be.a('function');\n            });\n\n            it('uses XHR backend', function() {\n                sandbox.spy(i18n, 'use');\n                helper.init();\n                expect(i18n.use).calledWith(XHR);\n            });\n\n            it('initializes i18next', function() {\n                helper.init();\n                expect(i18n.init).called;\n            });\n\n            it('calls .getLang()', function() {\n                helper.init();\n                expect(helper.getLang).called;\n            });\n\n        });\n\n        describe('.getLang()', function() {\n            var reqStub;\n\n            beforeEach(function() {\n                reqStub = sandbox.stub().returns('');\n                Radio.reply('configs', 'get:config', reqStub);\n            });\n\n            it('requests language from configs', function() {\n                reqStub.returns('en_us');\n                expect(helper.getLang()).to.be.equal('en_us');\n                expect(reqStub).called;\n            });\n\n            it('searches language from browser settings', function() {\n                sandbox.spy(_, 'chain');\n                helper.getLang();\n                expect(_.chain).called;\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/helpers/storage.js",
    "content": "/*jshint expr: true*/\n/* global define, expect, describe, it, beforeEach, afterEach, Modernizr */\ndefine([\n    'sinon',\n    'q',\n    'underscore',\n    'backbone',\n    'backbone.radio',\n    'helpers/storage'\n], function(sinon, Q, _, Backbone, Radio, storage) {\n    'use strict';\n\n    describe('helpers/storage', function() {\n\n        describe('.check()', function() {\n\n            beforeEach(function() {\n                sinon.stub(storage, 'switchDb');\n                sinon.stub(storage, 'testDb');\n\n                Radio.reply('global', 'use:webworkers', function() { return true; });\n            });\n\n            afterEach(function() {\n                storage.switchDb.restore();\n                storage.testDb.restore();\n\n                Modernizr.indexeddb = true;\n            });\n\n            it('uses backbone.noworker.sync if IndexedDB can\\'t be used', function() {\n                Modernizr.indexeddb = false;\n                storage.check();\n                expect(storage.switchDb).to.have.been.calledWith('backbone.noworker.sync');\n            });\n\n            it('uses backbone.noworker.sync if WebWorkers can\\'t be used', function() {\n                Radio.replyOnce('global', 'use:webworkers', function() { return false; });\n                storage.check();\n                expect(storage.switchDb).to.have.been.calledWith('backbone.noworker.sync');\n            });\n\n            it('uses backbone.sync', function() {\n                storage.testDb.returns(Q.resolve());\n\n                return storage.check().then(function() {\n                    expect(storage.switchDb).to.have.been.calledWith('backbone.sync');\n                });\n            });\n\n            it('uses backbone.noworker.sync if .testDb() fails', function() {\n                storage.testDb.returns(Q.reject());\n\n                return storage.check().then(function() {\n                    expect(storage.switchDb).to.have.been.calledWith('backbone.noworker.sync');\n                });\n            });\n\n        });\n\n        describe('.testDb()', function() {\n\n            it('returns a promise', function() {\n                expect(storage.testDb()).to.have.property('promiseDispatch');\n            });\n\n            it('successfully opens the DB', function(done) {\n                return storage.testDb().should.be.fulfilled.and.notify(done);\n            });\n\n        });\n\n        describe('.switchDb()', function() {\n\n            it('overwrites Backbone.sync with the provided adapter', function(done) {\n                var sync = Backbone.sync;\n\n                storage.switchDb('backbone.noworker.sync').then(function() {\n                    expect(Backbone.sync).not.to.be.equal(sync);\n                    Backbone.sync = sync;\n                    done();\n                });\n            });\n\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/helpers/underscore-util.js",
    "content": "/*jshint expr: true*/\n/* global define, describe, it, expect */\ndefine([\n    'sinon',\n    'helpers/underscore-util'\n], function(sinon, _) {\n    'use strict';\n\n    describe('helpers/underscore-util', function() {\n\n        describe('Extends Underscore', function() {\n\n            it('adds functions', function() {\n                expect(_).to.have.property('cleanXSS');\n                expect(_).to.have.property('runTimes');\n                expect(_).to.have.property('stripTags');\n            });\n\n        });\n\n        describe('.cleanXSS()', function() {\n\n            it('calls _.runTimes if `unescape` argument is true', function() {\n                var spy = sinon.spy(_, 'runTimes');\n                _.cleanXSS('string', true);\n\n                expect(spy).to.have.been.calledWith(_.unescape, 2);\n            });\n\n            it('removes all HTML if `stripTags` argument is true', function() {\n                expect(_.cleanXSS('<script></script><b>Hello</b>', false, true))\n                    .to.be.equal('Hello');\n            });\n\n        });\n\n        describe('.runTimes()', function() {\n\n            it('executes the provided function N times', function() {\n                var spy = sinon.spy();\n                _.runTimes(spy, 5);\n\n                expect(spy).to.have.been.callCount(5);\n            });\n\n            it('passes arguments with indexes greater 2', function() {\n                var spy = sinon.spy();\n                _.runTimes(spy, 2, 'hello', 'world');\n\n                expect(spy).to.have.been.calledWith('hello', 'world');\n            });\n\n            it('returns the last result', function() {\n                var i = 0;\n                expect(_.runTimes(function() { i++; return i; }, 4)).to.be.equal(4);\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/helpers/uri.js",
    "content": "/*jshint expr: true*/\n/* global define, expect, describe, it, before, after, afterEach */\ndefine([\n    'sinon',\n    'underscore',\n    'backbone',\n    'helpers/uri'\n], function(sinon, _, Backbone, Uri) {\n    'use strict';\n\n    describe('helpers/uri', function() {\n        var uri;\n\n        before(function() {\n            uri = new Uri();\n\n            // Prevent it from reloading the page\n            $(window).off('hashchange');\n\n            Backbone.history.start();\n            Backbone.history.navigate('', {trigger: false});\n        });\n\n        after(function() {\n            uri.destroy();\n\n            Backbone.history.stop();\n            Backbone.history.navigate('', {trigger: false});\n        });\n\n        afterEach(function() {\n            uri.navigate('/');\n        });\n\n        describe('.navigate()', function() {\n\n            it('changes location hash', function() {\n                uri.navigate('/notebooks');\n                expect(document.location.hash).to.contain('notebooks');\n            });\n\n            it('builds URL to a notes list and changes location hash', function() {\n                uri.navigate({options: {filter: 'notebooks', query: 'id-1', page: 1}});\n                expect(document.location.hash).to.contain('notes/f/notebooks/q/id-1');\n            });\n\n            it('it is not neccessary to provide options.options', function() {\n                uri.navigate({filter: 'notebooks', query: 'id-2', page: 1});\n                expect(document.location.hash).to.contain('notes/f/notebooks/q/id-2');\n            });\n\n            it('adds a profile link to the link and changes the hash', function() {\n                uri.navigate('/p/notes/', {trigger: false});\n                uri.navigate('/notebooks', {includeProfile: true});\n                expect(document.location.hash).to.contain('p/notes/notebooks');\n            });\n\n        });\n\n        describe('.navigateBack()', function() {\n\n            it('navigates back', function() {\n                if (/WebKit/.test(window.navigator.userAgent)) {\n                    console.warn('history.back does not work properly in Chrome');\n                    return;\n                }\n\n                uri.navigate('/p/notes1/');\n                uri.navigate('/p/notes2/');\n                uri.navigateBack();\n\n                expect(document.location.hash).to.contain('p/notes1');\n            });\n\n        });\n\n        describe('.getFileLink()', function() {\n\n            it('generates a pseudo URL', function() {\n                expect(uri.getFileLink({id: 'id1'})).to.be.equal('#file:id1');\n            });\n\n        });\n\n        describe('.getProfileLink()', function() {\n\n            it('adds profile link to the original URL', function() {\n                expect(uri.getProfileLink('/notes', 'default'))\n                    .to.be.equal('/p/default/notes');\n            });\n\n            it('calls .getProfile() if profile name was not provided', function() {\n                var spy = sinon.spy(uri, 'getProfile');\n                uri.getProfileLink('/notes');\n\n                expect(spy).to.have.been.called;\n            });\n\n            it('adds `/` to the beginning', function() {\n                expect(uri.getProfileLink('notes')).to.be.equal('/notes');\n            });\n\n        });\n\n        describe('.getProfile()', function() {\n\n            it('returns null if profile is not in the hash', function() {\n                expect(uri.getProfile()).to.be.equal(null);\n            });\n\n            it('returns profile name', function() {\n                uri.navigate('/p/profileTest/');\n                expect(uri.getProfile()).to.be.equal('profileTest');\n            });\n\n        });\n\n        describe('.getRoute()', function() {\n\n            it('returns location hash', function() {\n                uri.navigate('/test/page');\n                expect(document.location.hash).to.contain(uri.getRoute());\n            });\n\n        });\n\n        describe('.getLink()', function() {\n\n            it('returns a link to notes list', function() {\n                expect(uri.getLink()).to.be.equal('/notes');\n            });\n\n            it('returns a link to filtered notes list', function() {\n                expect(uri.getLink({filter: 'tags', query: 'hello'}))\n                    .to.be.equal('/notes/f/tags/q/hello');\n            });\n\n            it('adds pagination links', function() {\n                expect(uri.getLink({filter: 'tags', query: 'hello', page: 10}))\n                    .to.be.equal('/notes/f/tags/q/hello/p10');\n            });\n\n            it('returns a link to a note', function() {\n                expect(uri.getLink({filter: 'tags', query: 'hello'}, {id: 'model-id'}))\n                    .to.be.equal('/notes/f/tags/q/hello/show/model-id');\n            });\n\n        });\n\n        describe('Replies to requests', function() {\n\n            it('listens on `uri` channel', function() {\n                expect(uri).to.have.property('vent');\n                expect(uri.vent.channelName).to.be.equal('uri');\n            });\n\n            it('has replies', function() {\n                var replies = ['navigate', 'back', 'profile', 'link:profile', 'link:file', 'link', 'get:current'];\n\n                expect(_.keys(uri.vent._requests).length).to.be.equal(replies.length);\n\n                _.each(replies, function(name) {\n                    expect(_.keys(uri.vent._requests)).to.include(name);\n                });\n            });\n\n        });\n\n    });\n});\n"
  },
  {
    "path": "test/spec/init.js",
    "content": "/* global mocha, requirejs */\nrequirejs([\n    'chai',\n    'underscore',\n    'chai-jquery',\n    'chai-promise',\n    'sinon-chai',\n    'helpers/radio.shim',\n], function(chai, _, chaiJquery, chaiAsPromised, sinon) {\n    'use strict';\n\n    // Setup Mocha and Chai\n    mocha.setup('bdd');\n    mocha.bail(false);\n    chai.use(chaiJquery);\n    chai.use(chaiAsPromised);\n    chai.use(sinon);\n\n    // Make `expect` and `should` globally available\n    window.expect = chai.expect;\n    window.should = chai.should();\n\n    requirejs([\n\n        // Core\n        'spec/app',\n        'spec/moduleLoader',\n        'spec/initializers',\n        'spec/backbone.sync',\n\n        // Helpers\n        'spec/helpers/db',\n        'spec/helpers/i18next',\n        'spec/helpers/storage',\n        'spec/helpers/underscore-util',\n        'spec/helpers/uri',\n\n        // Models\n        'spec/models/note',\n        'spec/models/notebook',\n        'spec/models/tag',\n        'spec/models/file',\n        'spec/models/config',\n\n        // Collections\n        'spec/collections/notes',\n        'spec/collections/notebooks',\n        'spec/collections/tags',\n        'spec/collections/configs',\n        'spec/collections/pageable',\n\n        // Collection modules\n        'spec/collections/modules/notes',\n        'spec/collections/modules/notebooks',\n        'spec/collections/modules/tags',\n        'spec/collections/modules/files',\n        'spec/collections/modules/configs',\n        'spec/collections/modules/module',\n\n    ], function() {\n        if (window.__karma__) {\n            return window.__karma__.start();\n        }\n\n        mocha.reporter('html');\n        (window.mochaPhantomJS || mocha).run();\n    });\n\n});\n"
  },
  {
    "path": "test/spec/initializers.js",
    "content": "/*jshint expr: true*/\n/* global define, describe, it, expect, after */\ndefine([\n    'sinon',\n    'underscore',\n    'backbone.radio',\n    'initializers'\n], function(sinon, _, Radio, init) {\n    'use strict';\n\n    describe('initializers', function() {\n\n        after(function() {\n            init._inits = {};\n            Radio.stopReplying('init', 'add', 'start');\n            init.destroy();\n        });\n\n        describe('Object', function() {\n\n            it('initializes itself automatically', function() {\n                expect(init).to.be.an('object');\n            });\n\n            it('has `_inits` variable', function() {\n                expect(init).to.have.property('_inits');\n                expect(init._inits).to.be.an('object');\n            });\n\n        });\n\n        describe('.addInit()', function() {\n\n            it('adds a new initializer', function() {\n                init.addInit('test', function() {});\n                expect(init._inits).to.have.property('test');\n                expect(init._inits.test).to.have.lengthOf(1);\n            });\n\n            it('pushes initializers to the existing key', function() {\n                init.addInit('test', function() {});\n                init.addInit('test', function() {});\n\n                expect(init._inits.test).to.have.lengthOf(3);\n            });\n\n        });\n\n        describe('.executeInits()', function() {\n\n            it('returns a function', function() {\n                expect(init.executeInits('test')).to.be.a('function');\n            });\n\n            it('the function returns a promise', function() {\n                var res = init.executeInits('test')();\n                expect(res).to.be.an('object');\n                expect(res).to.have.property('promiseDispatch');\n            });\n\n            it('can execute several initializers', function() {\n                var spy = sinon.spy();\n\n                init.addInit('test1', spy);\n                init.addInit('test2', spy);\n\n                return init.executeInits('test1 test2')()\n                .then(function() {\n                    expect(spy).to.have.been.calledTwice;\n                });\n            });\n\n        });\n\n        describe('._executeInit()', function() {\n\n            it('returns a promise', function() {\n                expect(init._executeInit('test')).to.have.property('promiseDispatch');\n            });\n\n            it('can execute several functions in an initializer', function(done) {\n                var spy = sinon.spy();\n\n                init.addInit('testInit', spy);\n                init.addInit('testInit', spy);\n\n                return init._executeInit('testInit')\n                .then(function() {\n                    expect(spy).to.have.been.calledTwice;\n                    done();\n                });\n            });\n\n            it('passes arguments to an initializer function', function() {\n                var spy = sinon.spy();\n                init.addInit('testInitArg', spy);\n\n                return init._executeInit('testInitArg', ['hello', 'world'])\n                .then(function() {\n                    spy.should.have.been.calledWith('hello', 'world');\n                });\n            });\n\n        });\n\n        describe('Replies', function() {\n\n            it('`add`', function() {\n                Radio.request('init', 'add', 'testReply', function() {});\n                expect(init._inits).to.have.property('testReply');\n            });\n\n            it('`start`', function(done) {\n                Radio.request('init', 'add', 'testRStart', function() { done(); });\n                Radio.request('init', 'start', 'testRStart')();\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/models/config.js",
    "content": "/*jshint expr: true*/\n/* global expect, define, describe, beforeEach, it */\ndefine([\n    'sinon',\n    'helpers/underscore-util',\n    'models/config'\n], function(sinon, _, Model) {\n    'use strict';\n\n    describe('models/config', function() {\n        var config;\n\n        beforeEach(function() {\n            config = new Model();\n        });\n\n        it('.defaults', function() {\n            expect(config.get('name')).to.be.equal('');\n            expect(config.get('value')).to.be.equal('');\n        });\n\n        describe('.validate()', function() {\n\n            it('name should not be empty', function() {\n                expect(config.validate({name: ''}).length > 0).to.be.equal(true);\n            });\n\n            it('value can be empty', function() {\n                expect(config.validate({name: 'hello', value: ''})).to.be.equal(undefined);\n            });\n\n        });\n\n        it('.changeDB()', function() {\n            expect(config.profileId).to.be.equal('notes-db');\n            config.changeDB('profile');\n            expect(config.profileId).to.be.equal('profile');\n        });\n\n        it('.getValueJSON()', function() {\n            config.set('value', JSON.stringify({content: 'hello'}));\n            expect(config.getValueJSON().content).to.be.equal('hello');\n        });\n\n        describe('.createProfile()', function() {\n\n            beforeEach(function() {\n                config.set('value', JSON.stringify(['p1', 'p2']));\n                sinon.stub(config, 'save');\n            });\n\n            it('does not do anything if name was not provided', function() {\n                expect(config.createProfile()).to.be.equal(undefined);\n            });\n\n            it('adds profile name to the array', function() {\n                config.createProfile('p3');\n                expect(config.save).to.have.been\n                    .calledWith({value: JSON.stringify(['p1', 'p2', 'p3'])});\n            });\n\n            it('does not do anything if profile is in the array', function() {\n                config.createProfile('p1');\n                config.createProfile('p3');\n                expect(config.save).to.have.been.calledOnce;\n            });\n\n        });\n\n        describe('.removeProfile()', function() {\n\n            beforeEach(function() {\n                config.set('value', JSON.stringify(['p1', 'p2']));\n                sinon.stub(config, 'save');\n            });\n\n            it('does not do anything if name was not provided', function() {\n                expect(config.removeProfile()).to.be.equal(undefined);\n            });\n\n            it('removes the name from the array', function() {\n                config.removeProfile('p1');\n                expect(config.save).to.have.been.calledWith({value: JSON.stringify(['p2'])});\n            });\n\n        });\n\n        describe('.isPassword()', function() {\n\n            beforeEach(function() {\n                config.set({name: 'encryptPass', value: 'hello'});\n            });\n\n            it('returns false if `name` is not `encryptPass`', function() {\n                config.set('name', 'null');\n                expect(config.isPassword({})).to.be.equal(false);\n            });\n\n            it('returns true if `data.name` is `encryptPass`', function() {\n                config.set('name', 'null');\n                expect(config.isPassword({name: 'encryptPass'})).to.be.equal(true);\n            });\n\n            it('returns false if `data.value` is an object', function() {\n                expect(config.isPassword({value: []})).to.be.equal(false);\n            });\n\n            it('returns false if `value` is equal to `data.value`', function() {\n                expect(config.isPassword({value: 'hello'})).to.be.equal(false);\n            });\n\n            it('returns true', function() {\n                expect(config.isPassword({value: 'world'})).to.be.equal(true);\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/models/file.js",
    "content": "/* global expect, define, describe, beforeEach, it */\ndefine([\n    'helpers/underscore-util',\n    'models/file'\n], function(_, Model) {\n    'use strict';\n\n    describe('models/file', function() {\n        var file;\n\n        beforeEach(function() {\n            file = new Model();\n        });\n\n        describe('.defaults', function() {\n\n            it('id is undefined', function() {\n                expect(file.get('id')).to.be.an('undefined');\n            });\n\n            it('empty string', function() {\n                _.each(['name', 'src', 'fileType'], function(n) {\n                    expect(file.get(n)).to.be.equal('');\n                });\n            });\n\n            it('equals to 0', function() {\n                _.each(['trash', 'created', 'updated'], function(n) {\n                    expect(file.get(n)).to.be.equal(0);\n                });\n            });\n\n        });\n\n        describe('.validate()', function() {\n\n            it('src should not be empty', function() {\n                expect(file.validate({src: '', fileType: 'img'}).length).to.be.equal(1);\n            });\n\n            it('fileType should not be empty', function() {\n                expect(file.validate({src: 'http://', fileType: ''}).length).to.be.equal(1);\n            });\n\n        });\n\n        describe('.setEscape()', function() {\n\n            it('filters `name`', function() {\n                var str = '<b href=\"javascript:alert(1)\" title=\"javascript:alert(2)\">Hello</b>\\n';\n                file.setEscape({'name': str});\n\n                expect(file.get('name')).not.to.be.equal(str);\n                expect(file.get('name')).to.be.equal(_.cleanXSS(str));\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/models/note.js",
    "content": "/* global expect, define, describe, beforeEach, it */\ndefine([\n    'require',\n    'helpers/underscore-util',\n    'models/note'\n], function(require, _, Note) {\n    'use strict';\n\n    describe('models/note', function() {\n        var note;\n\n        beforeEach(function() {\n            note = new Note();\n        });\n\n        describe('instance', function() {\n\n            it('should be instance of Note Model', function() {\n                expect(note).to.be.instanceof(Note);\n            });\n\n        });\n\n        describe('default values', function() {\n\n            it('\"id\" is undefined', function() {\n                expect(note.get('id')).to.be.an('undefined');\n            });\n\n            it('\"title\" and \"content\" are be empty strings', function() {\n                expect(note.get('title')).to.be.equal('');\n                expect(note.get('content')).to.be.equal('');\n            });\n\n            it('\"notebookId\" is equal to \"0\"', function() {\n                expect(note.get('notebookId')).to.be.equal('0');\n            });\n\n            it('should be equal to 0', function() {\n                _.each(['taskAll', 'taskCompleted', 'created', 'updated', 'isFavorite', 'trash'], function(prop) {\n                    expect(note.get(prop)).to.be.equal(0);\n                });\n            });\n\n            it('tags and files are arrays', function() {\n                expect(note.get('tags')).to.be.an('array');\n                expect(note.get('files')).to.be.an('array');\n            });\n\n        });\n\n        describe('can change it\\'s values', function() {\n\n            it('it can change \"title\" property', function() {\n                note.set('title', 'New title');\n                expect(note.get('title')).to.be.equal('New title');\n            });\n\n            it('it can change \"content\" property', function() {\n                note.set('content', 'New content');\n                expect(note.get('content')).to.be.equal('New content');\n            });\n\n        });\n\n        describe('.validate()', function() {\n\n            it('\"title\" shouldn\\'t be empty', function() {\n                expect(note.validate({title: ''}).length > 0).to.be.equal(true);\n                expect(note.validate({title: ''})).to.include.members(['title']);\n            });\n\n            it('doesn\\'t validate if \"trash\" is equal to 2', function() {\n                expect(note.validate({title: '', trash: 2})).to.be.equal(undefined);\n                expect(note.validate({title: '', trash: 2})).not.to.be.an('object');\n            });\n\n        });\n\n        describe('.toggleFavorite()', function() {\n\n            it('toggles favourite status', function() {\n                note.set('isFavorite', 0);\n                expect(note.toggleFavorite().isFavorite).to.be.equal(1);\n\n                note.set('isFavorite', 1);\n                expect(note.toggleFavorite().isFavorite).to.be.equal(0);\n            });\n\n        });\n\n        describe('.setEscape()', function() {\n            var data;\n\n            beforeEach(function() {\n                data = {\n                    title   : '<script>alert(\"yes\")</script>',\n                    content : '<b href=\"javascript:alert(1)\" title=\"javascript:alert(2)\"></b>\\n'\n                };\n            });\n\n            it('sanitizes data to prevent XSS', function() {\n                note.setEscape(_.extend({}, data));\n\n                _.each(['title', 'content'], function(name) {\n                    expect(note.get(name)).to.be.equal(_.cleanXSS(data[name]));\n                    expect(note.get(name)).not.to.contain(data[name]);\n                });\n            });\n\n            it('does not escape characters over and over', function() {\n                var sData     = _.extend({}, data);\n\n                for (var i = 0; i < 10; i++) {\n                    sData.title   = _.cleanXSS(sData.title);\n                    sData.content = _.cleanXSS(sData.content);\n                }\n\n                note.setEscape(sData);\n\n                _.each(['title', 'content'], function(name) {\n                    expect(note.get(name)).to.be.equal(_.cleanXSS(data[name]));\n                    expect(note.get(name)).not.to.contain(data[name]);\n                });\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/models/notebook.js",
    "content": "/* global expect, define, describe, beforeEach, it */\ndefine([\n    'helpers/underscore-util',\n    'models/notebook'\n], function(_, Model) {\n    'use strict';\n\n    describe('models/notebook', function() {\n        var notebook;\n\n        beforeEach(function() {\n            notebook = new Model();\n        });\n\n        describe('instance', function() {\n\n            it('should be instance of Notebook Model', function() {\n                expect(notebook).to.be.instanceof(Model);\n            });\n\n            it('converts `id` and `parentId` to string on start', function() {\n                var n = new Model({id: 2, parentId: 1});\n                expect(n.get('id')).to.be.a('string');\n                expect(n.get('parentId')).to.be.a('string');\n            });\n\n        });\n\n        describe('default values', function() {\n\n            it('id is undefined', function() {\n                expect(notebook.get('id')).to.be.equal(undefined);\n            });\n\n            it('parentId is equal to \"0\"', function() {\n                expect(notebook.get('parentId')).to.be.equal('0');\n            });\n\n            it('name is an empty string', function() {\n                expect(notebook.get('name')).to.be.equal('');\n            });\n\n            it('is equal to 0', function() {\n                _.each(['count', 'trash', 'created', 'updated'], function(name) {\n                    expect(notebook.get(name)).to.be.equal(0);\n                });\n            });\n\n        });\n\n        describe('can change it\\'s values', function() {\n\n            it('it can change \"name\" property', function() {\n                notebook.set('name', 'New notebook');\n                expect(notebook.get('name')).to.be.equal('New notebook');\n            });\n\n            it('it can change \"parentId\" property', function() {\n                notebook.set('parentId', '1');\n                expect(notebook.get('parentId')).to.be.equal('1');\n            });\n\n        });\n\n        describe('.validate()', function() {\n\n            it('\"name\" shouldn\\'t be empty', function() {\n                expect(notebook.validate({name: ''}).length > 0).to.be.equal(true);\n                expect(notebook.validate({name: ''})).to.contain('name');\n            });\n\n            it('doesn\\'t validate if \"trash\" is equal to 2', function() {\n                expect(notebook.validate({name: '', trash: 2})).to.be.equal(undefined);\n                expect(notebook.validate({name: '', trash: 2})).not.to.be.an('object');\n            });\n\n            it('it can\\'t have itself as parent', function(done) {\n                notebook.once('invalid', function(m, error) {\n                    expect(error).to.contain('parentId');\n                    done();\n                });\n                notebook.set({id: '1', parentId: '1'});\n                notebook.save();\n            });\n\n        });\n\n        describe('.setEscape()', function() {\n\n            it('sanitizes data to prevent XSS', function() {\n                var data = {name: '<b href=\"javascript:alert(1)\" title=\"javascript:alert(2)\">Hello</b>\\n'};\n                notebook.setEscape(_.extend({}, data));\n\n                expect(notebook.get('name')).to.be.equal(_.cleanXSS(data.name));\n                expect(notebook.get('name')).not.to.contain(data.name);\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/models/tag.js",
    "content": "/* global expect, define, describe, beforeEach, it */\ndefine([\n    'helpers/underscore-util',\n    'models/tag'\n], function(_, Model) {\n    'use strict';\n\n    describe('models/tag', function() {\n        var tag;\n\n        beforeEach(function() {\n            tag = new Model();\n        });\n\n        describe('default values', function() {\n\n            it('id is undefined', function() {\n                expect(tag.get('id')).to.be.equal(undefined);\n            });\n\n            it('name is an empty string', function() {\n                expect(tag.get('name')).to.be.equal('');\n            });\n\n            it('is equal to 0', function() {\n                _.each(['trash', 'created', 'updated'], function(name) {\n                    expect(tag.get(name)).to.be.equal(0);\n                });\n            });\n\n        });\n\n        describe('.validate()', function() {\n\n            it('\"name\" shouldn\\'t be empty', function() {\n                expect(tag.validate({name: ''}).length > 0).to.be.equal(true);\n                expect(tag.validate({name: ''})).to.contain('name');\n            });\n\n            it('doesn\\'t validate if \"trash\" is equal to 2', function() {\n                expect(tag.validate({name: '', trash: 2})).to.be.equal(undefined);\n                expect(tag.validate({name: '', trash: 2})).not.to.be.an('object');\n            });\n\n        });\n\n        describe('.setEscape()', function() {\n\n            it('sanitizes data to prevent XSS', function() {\n                var data = {name: '<b href=\"javascript:alert(1)\" title=\"javascript:alert(2)\">Hello</b>\\n'};\n                tag.setEscape(_.extend({}, data));\n\n                expect(tag.get('name')).to.be.equal(_.cleanXSS(data.name));\n                expect(tag.get('name')).not.to.contain(data.name);\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/moduleLoader.js",
    "content": "/*jshint expr: true*/\n/* global define, describe, it, expect, before, after */\ndefine([\n    'sinon',\n    'underscore',\n    'backbone.radio',\n    'moduleLoader'\n], function(sinon, _, Radio, Loader) {\n    'use strict';\n\n    describe('moduleLoader', function() {\n\n        after(function() {\n            Radio.request('init', 'add', 'load:modules', function() {});\n        });\n\n        describe('Object', function() {\n\n            it('is an object', function() {\n                expect(Loader).to.be.an('object');\n            });\n\n            it('has functions', function() {\n                _.each(['init', 'load', 'get'], function(name) {\n                    expect(Loader).to.have.property(name);\n                    expect(Loader[name]).to.be.a('function');\n                });\n            });\n\n        });\n\n        describe('.init()', function() {\n\n            it('calls load function', function() {\n                var load = sinon.stub(Loader, 'load');\n                Loader.init();\n                expect(load).to.have.been.called;\n            });\n\n            it('replies to `modules` request on global channel', function() {\n                expect(Radio.request('global', 'modules')).to.be.an('array');\n            });\n\n        });\n\n        describe('.get()', function() {\n            var configs;\n\n            before(function() {\n                configs = {modules: ['mathjax', 'fuzzySearch']};\n\n                Radio.reply('configs', 'get:config', function(key) {\n                    return configs[key];\n                });\n            });\n\n            after(function() {\n                Radio.stopReplying('configs', 'get:config');\n            });\n\n            it('returns array of modules', function() {\n                expect(Loader.get()).to.be.an('array');\n                expect(Loader.get().length).to.be.equal(configs.modules.length);\n            });\n\n            it('includes sync adapter', function() {\n                _.each(['dropbox', 'remotestorage'], function(adapter) {\n                    configs.cloudStorage = adapter;\n\n                    expect(Loader.get().length).to.be.equal(configs.modules.length + 1);\n                    expect(Loader.get()).to.include.members(['modules/' + adapter + '/module']);\n                });\n            });\n\n            it('does not load none existent module', function() {\n                configs.cloudStorage = null;\n                configs.modules.push('module404');\n\n                expect(Loader.get().length).not.to.be.equal(configs.modules.length);\n                expect(Loader.get().length).to.be.equal(configs.modules.length - 1);\n            });\n\n        });\n\n    });\n\n});\n"
  },
  {
    "path": "test/spec/test.js",
    "content": "/* global requirejs */\nvar dir = {\n    base  : (window.__karma__ ? '/base/' : '../'),\n    other : (window.__karma__ ? '/base/app/' : '../'),\n    test  : (window.__karma__ ? '/base/' : '../../'),\n};\n\nrequirejs.config({\n    baseUrl : dir.base + 'app/scripts',\n    urlArgs : 'bust=' + (new Date()).getTime(),\n    deps    : ['modernizr'],\n\n    paths   : {\n        'modernizr'    : dir.other + 'bower_components/modernizr/modernizr',\n        'chai'         : dir.test + 'test/bower_components/chai/chai',\n        'chai-jquery'  : dir.test + 'test/bower_components/chai-jquery/chai-jquery',\n        'chai-promise' : dir.test + 'test/bower_components/chai-as-promised/lib/chai-as-promised',\n        'sinon-chai'   : dir.test + 'test/bower_components/sinon-chai/lib/sinon-chai',\n        'sinon'        : dir.test + 'test/bower_components/sinonjs/sinon',\n        'spec'         : dir.test + 'test/spec',\n        'init'         : dir.test + 'test/spec/init',\n    },\n\n    map: {\n        '*': {\n            'classes/sjcl.worker' : 'classes/sjcl'\n        }\n    },\n\n    shim: {\n        'chai-jquery': ['jquery'],\n    },\n\n});\n\n/**\n * Just include main.js and it will include init script\n */\nrequirejs(['main']);\n"
  },
  {
    "path": "test/spec-ui/commands/addNote.js",
    "content": "'use strict';\nexports.command = function(item) {\n    this\n    .urlHash('notes/add')\n    .expect.element('#editor--input--title').to.be.visible.before(50000);\n\n    this\n    .setValue('#editor--input--title', item.title)\n    .expect.element('#editor--input--title').value.to.contain(item.title).before(2000);\n\n    this\n    .click('.CodeMirror-lines')\n    .keys(item.content);\n\n    if (item.notebook) {\n        this\n        .click('.addNotebook')\n        .expect.element('.modal--input[name=name]').to.be.visible.before(1000);\n\n        this\n        .setValue('.modal--input[name=name]', [item.notebook, this.Keys.ENTER])\n        .pause(1000);\n    }\n\n    this\n    .click('.editor--save')\n    .expect.element('.layout--body.-form').not.to.be.present.before(2000);\n\n    return this;\n};\n"
  },
  {
    "path": "test/spec-ui/commands/addNotebook.js",
    "content": "'use strict';\n\nexports.command = function(item) {\n    this\n    .urlHash('notebooks/add')\n    .expect.element('#modal input[name=\"name\"]').to.be.present.before(5000);\n\n    this.expect.element('#modal input[name=\"name\"]').to.be.visible.before(5000);\n    // this.expect.element('#modal .form-group').to.be.visible.before(5000);\n\n    this.setValue('#modal input[name=\"name\"]', item.name);\n\n    this.perform(function(client, done) {\n        client.execute(function(filter) {\n            var ops = document.querySelectorAll('#modal select[name=\"parentId\"] option');\n            for (var i = 0, len = ops.length; i < len; i++) {\n                if (filter && ops[i].text.indexOf(filter) > -1) {\n                    document\n                    .querySelector('#modal select[name=\"parentId\"]')\n                    .selectedIndex = ops[i].index;\n                }\n            }\n        }, [item.parentId], function() {\n            done();\n        });\n    });\n\n    this.keys(this.Keys.ENTER);\n    this.expect.element('#modal input[name=\"name\"]').to.be.not.present.before(5000);\n\n    return this;\n};\n"
  },
  {
    "path": "test/spec-ui/commands/addTag.js",
    "content": "'use strict';\n\nexports.command = function(item) {\n    this\n    .urlHash('notebooks')\n    .pause(100)\n    .urlHash('tags/add')\n    .expect.element('#modal .form-group').to.be.present.before(5000);\n\n    // this.expect.element('#modal .form-group').to.be.visible.before(5000);\n\n    this.setValue('#modal input[name=\"name\"]', [item.name, this.Keys.ENTER]);\n\n    return this;\n};\n"
  },
  {
    "path": "test/spec-ui/commands/changeEncryption.js",
    "content": "'use strict';\n/**\n * This Nightwatch command changes encryption settings.\n */\nexports.command = function(data) {\n    this\n    .urlHash('settings/encryption')\n    .expect.element('.-tab-encryption').to.be.present.before(5000);\n\n    this.getAttribute('input[name=encrypt]', 'checked', function(res) {\n        if (data.use && res.value === null) {\n            this.click('input[name=\"encrypt\"]');\n        }\n    });\n\n    this\n    .clearValue('input[name=\"encryptPass\"]')\n    .setValue('input[name=\"encryptPass\"]', data.password)\n    .click('#randomize')\n    .click('.settings--save')\n    .pause(1000)\n    .click('.settings--cancel');\n\n    return this;\n};\n"
  },
  {
    "path": "test/spec-ui/commands/closeWelcome.js",
    "content": "'use strict';\n\nexports.command = function() {\n    this\n    .urlHash('/notes')\n    .expect.element('#welcome--page').to.be.present.before(100000);\n\n    this\n    .expect.element('.modal-header .close').to.be.present.before(5000);\n\n    this\n    .click('.modal-header .close')\n    .keys(this.Keys.ESCAPE)\n    .expect.element('#welcome--page').not.to.be.present.before(10000);\n\n    return this;\n};\n"
  },
  {
    "path": "test/spec-ui/commands/findAll.js",
    "content": "exports.command = function(selector, attr, callback) {\n    this.execute(function(selector, attr) {\n        var els = document.querySelectorAll(selector),\n            param = [];\n\n        for (var i = 0, len = els.length; i < len; i++) {\n            if (attr) {\n                param.push(els[i].getAttribute(attr));\n            } else {\n                param.push(els[i]);\n            }\n        }\n\n        return param;\n    }, [selector, attr], function(res) {\n        callback(res.value);\n    });\n\n    return this;\n};\n"
  },
  {
    "path": "test/spec-ui/modules/remotestorage/auth.js",
    "content": "/* global it */\n'use strict';\n\n/**\n * It tests if it's possible to login to a RemoteStorage server.\n */\nit('creates a new user on RemoteStorage server', function(client) {\n    client\n    .url('http://localhost:9100/signup')\n    .expect.element('input[type=\"password\"]').to.be.present.before(50000);\n\n    client\n    .setValue('#username', 'test')\n    .setValue('#email', 'test@example.com')\n    .setValue('#password', ['1', client.Keys.ENTER])\n    .pause(300);\n});\n\nit('shows RemoteStorage widget', function(client) {\n    client\n    .urlHash('notes')\n    .expect.element('.remotestorage-initial').to.be.present.before(50000);\n});\n\nit('can login to a RemoteStorage server', function(client) {\n    client\n    .click('.rs-bubble')\n    .setValue('.remotestorage-initial input[name=\"userAddress\"]', 'test@localhost:9100')\n    .click('.remotestorage-initial .connect')\n    .expect.element('input[type=\"password\"]').to.be.present.before(10000);\n\n    client.assert.urlContains('localhost:9100');\n\n    client\n    .setValue('#password', ['1', client.Keys.ENTER])\n    .expect.element('.remotestorage-connected').to.be.present.before(50000);\n});\n"
  },
  {
    "path": "test/spec-ui/modules/remotestorage/client1.js",
    "content": "/* global describe, before, after, it */\n'use strict';\nvar exec = require('child_process').exec;\n\n/**\n * Remove reStore's files.\n */\ntry {\n    exec('rm -rf /tmp/reStore/rs.sync');\n} catch (e) {\n    console.log('Unable to remove reStore files', e);\n}\n\ndescribe('RemoteStorage: client 1', function() {\n    var data = {};\n\n    before(function(client, done) {\n        data = {\n            note     : {title: 'Note from client 1'},\n            notebook : {name: 'Notebook from client 1'},\n            tag      : {name: 'Tag from client 1'}\n        };\n\n        done();\n    });\n\n    after(function(client, done) {\n        client.end(function() {\n            done();\n        });\n    });\n\n    it('wait', function(client) {\n        client\n        .urlHash('notes')\n        .expect.element('.list').to.be.present.before(50000);\n    });\n\n    it('creates new data', function(client) {\n        client\n        .addNote(data.note)\n        .pause(500)\n        .addNotebook(data.notebook)\n        .pause(500)\n        .addTag(data.tag);\n\n        client.pause(1000);\n        setTimeout(function() {\n            console.log('start client 2');\n        }, 500);\n    });\n\n    // Try to login to a RemoteStorage first\n    require('./auth.js');\n\n    it('fetches notes from Remotestorage', function(client) {\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.present.before(50000);\n\n        client.expect\n        .element('#sidebar--content').to.have.text.that.contains('Note from client 2')\n        .before(50000);\n    });\n\n    it('fetches notebooks & tags from Remotestorage', function(client) {\n        client.urlHash('notebooks');\n\n        client.expect\n        .element('#notebooks').text.to.contain('Notebook from client 2')\n        .before(50000);\n\n        client.expect\n        .element('#tags').text.to.contain('Tag from client 2')\n        .that.contains('Tag from client 1')\n        .before(50000);\n    });\n\n});\n"
  },
  {
    "path": "test/spec-ui/modules/remotestorage/client2.js",
    "content": "/* global describe, before, after, it */\n'use strict';\n\ndescribe('RemoteStorage: client 2', function() {\n\n    before(function(client, done) {\n        done();\n    });\n\n    after(function(client, done) {\n        client.end(function() {\n            done();\n        });\n    });\n\n    it('wait', function(client) {\n        client\n        .urlHash('notes')\n        .expect.element('.list').to.be.present.before(50000);\n    });\n\n    it('creates new data', function(client) {\n        client\n        .addNote({title: 'Note from client 2'})\n        .pause(500)\n        .addNotebook({name: 'Notebook from client 2'})\n        .pause(500)\n        .addTag({name: 'Tag from client 2'});\n    });\n\n    // Try to login to a RemoteStorage first\n    require('./auth.js');\n\n    it('fetches notes from Remotestorage', function(client) {\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.present.before(50000);\n\n        client.expect\n        .element('#sidebar--content').to.have.text.that.contains('Note from client 1')\n        .before(50000);\n    });\n\n    it('fetches notebooks & tags from Remotestorage', function(client) {\n        client.urlHash('notebooks');\n\n        client.expect\n        .element('#notebooks').text.to.contain('Notebook from client 1')\n        .before(50000);\n\n        client.expect\n        .element('#tags').text.to.contain('Tag from client 1')\n        .that.contains('Tag from client 1')\n        .before(50000);\n    });\n\n});\n"
  },
  {
    "path": "test/spec-ui/tests/apps/encryption/encrypt.js",
    "content": "'use strict';\nvar notes = [];\n\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'wait': function(client) {\n        client\n        .urlHash('notes')\n        .expect.element('.list').to.be.present.before(50000);\n    },\n\n    'shows encryption page': function(client) {\n        for (var i = 0; i < 8; i++) {\n            notes.push({\n                title   : 'Encrypted title ' + i,\n                content : 'Encrypted content ' + i\n            });\n            client.addNote(notes[i]);\n        }\n\n        client\n        .changeEncryption({password: '1', use: true})\n        .expect.element('.container.-auth').to.be.present.before(5000);\n    },\n\n    'asks for a new password': function(client) {\n        client.expect.element('input[name=\"password\"]').to.be.present.before(5000);\n\n        client\n        .pause(100)\n        .click('input[name=\"password\"]')\n        .keys('1')\n        // .setValue('input[name=\"password\"]', '1')\n        .click('[type=\"submit\"]');\n    },\n\n    'shows backup': function(client) {\n        client.expect.element('.-backup').to.be.present.before(50000);\n        client.expect.element('#btn--next').to.be.present.before(5000);\n\n        client\n        .pause(300)\n        .click('#btn--next')\n        .pause(200)\n        .expect.element('.container.-auth').not.to.be.present.before(6000);\n    },\n\n    'asks for a new password again after encrypting': function(client) {\n        client.expect.element('.container.-auth').to.be.present.before(5000);\n\n        client\n        .setValue('input[name=\"password\"]', '1')\n        .click('[type=\"submit\"]')\n        .expect.element('.container.-auth').not.to.be.present.before(5000);\n    },\n\n    'shows notes in unencrypted format': function(client) {\n        client.pause(100);\n        notes.forEach(function(note) {\n            client.expect.element('.list').text.to.contain(note.title).before(5000);\n            client.expect.element('.list').text.to.contain(note.content).before(5000);\n        });\n    },\n\n    'shows encryption page again if encryption settings are changed': function(client) {\n        client\n        .pause(1000)\n        .changeEncryption({password: '2', use: true})\n        .expect.element('.container.-auth').to.be.present.before(5000);\n    },\n\n    'asks the old and new passwords': function(client) {\n        client.expect.element('input[name=\"oldpass\"]').to.be.present.before(5000);\n        client.expect.element('input[name=\"password\"]').to.be.present.before(5000);\n    },\n\n    're-encrypts everything': function(client) {\n        client\n        .setValue('input[name=oldpass]', '1')\n        .setValue('input[name=password]', '2')\n        .click('[type=\"submit\"]');\n\n        client.expect.element('.-backup').to.be.present.before(50000);\n        client.expect.element('#btn--next').to.be.present.before(5000);\n\n        client\n        .pause(300)\n        .click('#btn--next')\n        .expect.element('.container.-auth').not.to.be.present.before(5000);\n    },\n\n    'asks for a new password again after re-encrypting': function(client) {\n        client.expect.element('.container.-auth').to.be.present.before(5000);\n\n        client\n        .setValue('input[name=\"password\"]', '2')\n        .click('[type=\"submit\"]')\n        .expect.element('.container.-auth').not.to.be.present.before(5000);\n    },\n\n    'shows notes in decrypted format': function(client) {\n        notes.forEach(function(note) {\n            client.expect.element('.list').text.to.contain(note.title).before(5000);\n            client.expect.element('.list').text.to.contain(note.content).before(5000);\n        });\n    },\n\n    'is possible to disable encryption entirely': function(client) {\n        client\n        .urlHash('settings/encryption')\n        .expect.element('.-tab-encryption').to.be.present.before(5000);\n\n        client\n        .click('input[name=\"encrypt\"]')\n        .click('.settings--save')\n        .pause(1000)\n        .click('.settings--cancel')\n        .expect.element('.container.-auth').to.be.present.before(5000);\n\n        client\n        .setValue('input[name=oldpass]', '2')\n        .click('[type=\"submit\"]');\n\n        client.expect.element('.-backup').to.be.present.before(50000);\n        client.expect.element('#btn--next').to.be.present.before(5000);\n\n        client\n        .pause(200)\n        .click('#btn--next')\n        .expect.element('.container.-auth').not.to.be.present.before(5000);\n\n        notes.forEach(function(note) {\n            client.expect.element('.list').text.to.contain(note.title).before(5000);\n            client.expect.element('.list').text.to.contain(note.content).before(5000);\n        });\n    },\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/navbar/navbar.js",
    "content": "'use strict';\n\n/**\n * Add notebook form test\n//  |)}>#\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n\n        client\n        .urlHash('notes')\n        .expect.element('#header--add').to.be.present.before(50000);\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'can show current title and add button': function(client) {\n        client.expect.element('#header--title').to.have.text.that.equals('All notes');\n        client.expect.element('#header--add').to.be.present.before(5000);\n        client.expect.element('#header--add').to.be.visible.before(5000);\n\n        client.getTitle(function(title) {\n            this.assert.equal(title, 'All notes - Laverna');\n        });\n    },\n\n    'can change title in notebooks page': function(client) {\n        client.urlHash('notebooks');\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Notebooks & Tags')\n        .before(5000);\n\n        client.expect.element('#header--add').to.be.present.before(5000);\n        client.expect.element('#header--add').to.be.visible.before(5000);\n\n        client.getTitle(function(title) {\n            this.assert.equal(title, 'Notebooks & Tags - Laverna');\n        });\n    },\n\n    'can change title in trashed notes page': function(client) {\n        client.urlHash('notes/f/trashed');\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Trashed')\n        .before(5000);\n\n        client.expect.element('#header--add').to.be.present.before(5000);\n        client.expect.element('#header--add').to.be.visible.before(5000);\n\n        client.getTitle(function(title) {\n            this.assert.equal(title, 'Trashed - Laverna');\n        });\n    },\n\n    'can change title in favourite notes page': function(client) {\n        client.urlHash('notes/f/favorite');\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Favourites')\n        .before(5000);\n\n        client.expect.element('#header--add').to.be.present.before(5000);\n        client.expect.element('#header--add').to.be.visible.before(5000);\n\n        client.getTitle(function(title) {\n            this.assert.equal(title, 'Favourites - Laverna');\n        });\n    },\n\n    'search button shows input': function(client) {\n        client.expect.element('#header--search').to.be.not.visible.before(5000);\n        client.click('#header--sbtn');\n        client.expect.element('#header--search').to.be.visible.before(5000);\n    },\n\n    'hitting ESCAPE hides search form': function(client) {\n        client.pause(500);\n        client.setValue('#header--search--input', client.Keys.ESCAPE);\n        client.expect.element('#header--search').to.be.not.visible.before(5000);\n    },\n\n    'can show navbar sidemenu on click on #header--title': function(client) {\n        client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);\n        client.click('#header--title');\n        client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.present.before(5000);\n    },\n\n    'all urls are correct': function(client) {\n        client\n        .expect.element('#sidebar--navbar a[href=\"#/notes\"]')\n        .to.be.visible.before(5000);\n\n        client\n        .expect.element('#sidebar--navbar a[href=\"#/notes/f/favorite\"]')\n        .to.be.visible.before(5000);\n\n        client\n        .expect.element('#sidebar--navbar a[href=\"#/notes/f/trashed\"]')\n        .to.be.visible.before(5000);\n\n        client\n        .expect.element('#sidebar--navbar a[href=\"#/notebooks\"]')\n        .to.be.visible.before(5000);\n\n        client\n        .expect.element('#sidebar--navbar a[href=\"#/settings\"]')\n        .to.be.visible.before(5000);\n    },\n\n    'can be closed with a click on the close button': function(client) {\n        client.click('#sidebar--navbar .sidemenu--close');\n        client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);\n    },\n\n    'can be closed with ESCAPE': function(client) {\n        client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);\n        client.click('#header--title');\n        client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.present.before(5000);\n\n        client.keys(client.Keys.ESCAPE);\n        client.expect.element('#sidebar--navbar .sidemenu.-show').to.be.not.present.before(5000);\n    },\n};\n*/\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notebooks/form.js",
    "content": "'use strict';\nvar expect = require('chai').expect;\n\n/**\n * Add notebook form test\n */\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    /**\n     * Saves a notebook\n     */\n    'can show notebook form when button is clicked': function(client) {\n        client\n        .urlHash('notebooks')\n        .expect.element('#header--add').to.be.present.before(50000);\n\n        client.click('#header--add')\n        .expect.element('#modal .form-group').to.be.visible.before(2000);\n    },\n\n    'can change title of a notebook': function(client) {\n        client\n        .expect.element('#modal .form-group').to.be.visible.before(50000);\n\n        client.setValue('#modal input[name=\"name\"]', ['Nightwatch'])\n        .expect.element('#modal input[name=\"name\"]').value.to.contain('Nightwatch');\n    },\n\n    'can save notebooks': function(client) {\n        client\n        .expect.element('#modal .ok').text.to.contain('Save');\n\n        client\n        .click('#modal .ok')\n        .expect.element('#modal .form-group').not.to.be.present.before(5000);\n    },\n\n    'saved notebooks appear in list': function(client) {\n        client\n        .expect.element('#notebooks .list--item.-notebook').text.to.contain('Nightwatch').before(5000);\n    },\n\n    'redirects to notebooks list on save': function(client) {\n        client\n        .pause(500)\n        .url(function(data) {\n            expect(data.value).to.contain('#notebooks');\n            expect(data.value).not.to.contain('/add');\n        });\n\n    },\n\n    'saved notebooks appear in the add form': function(client) {\n        client.click('#header--add')\n        .expect.element('#modal select[name=\"parentId\"]').text.to.contain('Nightwatch').before(2000);\n    },\n\n    'can add nested notebooks': function(client) {\n        client\n        .click('#header--add')\n        .expect.element('#modal .form-group').to.be.present.before(5000);\n\n        client\n        .setValue('#modal input[name=\"name\"]', ['Sub-notebook'])\n        // Change parentId of a notebook\n        .perform((client, done) => {\n            client.execute(function(filter) {\n                var ops = document.querySelectorAll('#modal select[name=\"parentId\"] option'),\n                    res = false;\n\n                for (var i = 0, len = ops.length; i < len; i++) {\n                    if (ops[i].text.indexOf(filter) > -1) {\n                        document\n                        .querySelector('#modal select[name=\"parentId\"]')\n                        .selectedIndex = ops[i].index;\n\n                        res = true;\n                    }\n                }\n\n                return res;\n            }, ['Nightwatch'], function(res) {\n                expect(res.value).to.be.equal(true);\n                done();\n            });\n        })\n        .setValue('#modal input[name=\"name\"]', [client.Keys.ENTER]);\n    },\n\n    'doesn\\'t save if title is empty': function(client) {\n        client\n        .urlHash('notebooks')\n        .urlHash('notebooks/add')\n        .expect.element('#modal .form-group').to.be.visible.before(2000);\n\n        client\n        .clearValue('#modal input[name=\"name\"]')\n        .click('#modal .ok');\n\n        client.expect.element('#notebooks .list--item').to.have.text.that.contains('Nightwatch');\n    },\n\n    'closes modal window on escape': function(client) {\n        client\n        .urlHash('notebooks')\n        .urlHash('notebooks/add')\n        .expect.element('#modal .form-group').to.be.visible.before(2000);\n\n        client\n        .setValue('#modal input[name=\"name\"]', ['Doesn\\'t save', client.Keys.ESCAPE])\n        .expect.element('#notebooks').text.not.to.contain('Doesn\\'t save').before(2000);\n    },\n\n    'closes modal window on cancel button click': function(client) {\n        client\n        .urlHash('notebooks')\n        .urlHash('notebooks/add')\n        .expect.element('#modal .form-group').to.be.visible.before(2000);\n\n        client\n        .setValue('#modal input[name=\"name\"]', ['Doesn\\'t save'])\n        .click('#modal .cancelBtn')\n        .expect.element('#notebooks').text.not.to.contain('Doesn\\'t save').before(2000);\n    },\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notebooks/formEdit.js",
    "content": "'use strict';\nvar expect = require('chai').expect,\n    ids    = [];\n\n/**\n * Edit notebook form test\n */\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'load': function(client) {\n        client.addNotebook({name: 'notebook 1', parentId: 0});\n        client.addNotebook({name: 'Sub-notebook', parentId: 'notebook 1'});\n\n        client.perform(function(client, done) {\n            client\n            .urlHash('notebooks');\n\n            // Get all rendered notebooks\n            client.findAll('#notebooks .list--item', 'data-id', (res) => {\n                expect(typeof res).to.be.equal('object');\n                expect(res.length).to.be.equal(2);\n                ids = res;\n                done();\n            });\n        });\n    },\n\n    'shows notebook edit form': function(client) {\n        client\n        .urlHash(`notebooks/edit/${ids[0]}`)\n        .expect.element('#modal .form-group').to.be.present.before(2000);\n\n        client\n        .clearValue('#modal input[name=\"name\"]')\n        .pause(200)\n        .setValue('#modal input[name=\"name\"]', 'Changed-Title')\n        .expect.element('#modal input[name=\"name\"]').to.have.value.that.contains('Changed-Title');\n    },\n\n    'list re-renders notebooks with new values': function(client) {\n        client\n        .setValue('#modal input[name=\"name\"]', [client.Keys.ENTER])\n        .pause(500)\n        .expect.element('#notebooks').text.to.contain('Changed-Title');\n\n        client.expect.element('#notebooks').text.to.contain('Sub-notebook');\n    },\n\n    'shows notebook form with updated data': function(client) {\n        client\n        .urlHash(`notebooks/edit/${ids[1]}`)\n        .expect.element('#modal .form-group').to.be.visible.before(5000);\n\n        client.expect.element('#modal input[name=\"name\"]').value.to.contain('Sub-notebook');\n        client.expect.element('#modal select[name=\"parentId\"]').text.to.contain('Changed-Title');\n    },\n\n    'shows notebook form with correct notebookId': function(client) {\n        client\n        .expect.element('#modal select[name=\"parentId\"]').to.have.value.that.equals(ids[0]);\n    },\n\n    'can update sub-notebooks': function(client) {\n        client\n        .clearValue('#modal input[name=\"name\"]')\n        .setValue('#modal input[name=\"name\"]', ['Sub-CT', client.Keys.ENTER]);\n    },\n\n    're-renders notebooks list with updated data': function(client) {\n        client\n        .expect.element('#notebooks').text.to.contain('Sub-CT');\n        client\n        .expect.element('#notebooks').text.to.contain('Changed-Title');\n    },\n\n    'doesn\\'t update if title is empty': function(client) {\n        client\n        .urlHash('notebooks')\n        .urlHash(`notebooks/edit/${ids[0]}`)\n        .expect.element('#modal .form-group').to.be.visible.before(2000);\n\n        client\n        .clearValue('#modal input[name=\"name\"]')\n        .setValue('#modal input[name=\"name\"]', [client.Keys.ENTER]);\n\n        client\n        .expect.element('#notebooks').text.to.contain('Changed-Title');\n    },\n\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notebooks/list.js",
    "content": "'use strict';\nvar expect = require('chai').expect,\n    ids;\n\n/**\n * Notebook list test\n */\nmodule.exports = {\n\n    before: function(client, done) {\n        client.closeWelcome();\n\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client\n        .pause(100)\n        .urlHash('notebooks');\n\n        client.addNotebook({name: 'notebook 1', parentId: 0});\n        client.addNotebook({name: 'notebook 4', parentId: 0});\n        client.addNotebook({name: 'notebook 5', parentId: 'notebook 4'});\n\n        client.findAll('#notebooks .list--item', 'data-id', (res) => {\n            ids = res;\n            expect(ids.length).to.be.equal(3);\n            done();\n        });\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'shows notebooks list': function(client) {\n        expect(ids.length).not.to.be.equal(0);\n        client.expect.element('#header--add').to.be.visible.before(5000);\n    },\n\n    'shows a button that shows menu': function(client) {\n        client.expect.element('#notebooks .list--buttons .drop-edit').to.be.visible.before(5000);\n        client.expect.element('#notebooks .list--buttons .dropdown-menu').to.be.not.visible.before(5000);\n    },\n\n    'click on the button shows menu': function(client) {\n        client.click('#notebooks .list--buttons .drop-edit');\n        client.expect.element('#notebooks .list--buttons .dropdown-menu').to.be.visible.before(5000);\n        client.click('#notebooks .list--buttons .drop-edit');\n    },\n\n    'edit button shows notebook form': function(client) {\n        client\n        .click('#notebooks .list--buttons .drop-edit')\n        .click('#notebooks .list--buttons .edit-link');\n\n        client.expect.element('#modal .form-group').to.be.present.before(2000);\n        client.expect.element('#modal .form-group').to.be.visible.before(2000);\n        client.keys(client.Keys.ESCAPE);\n        client.expect.element('#modal .form-group').not.to.be.present.before(5000);\n    },\n\n    'remove button shows dialog': function(client) {\n        client\n        .click('#notebooks .list--buttons .drop-edit')\n        .click('#notebooks .list--buttons .remove-link');\n\n        client.expect.element('#modal .modal-title').to.be.present.before(2000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(2000);\n        client.keys(client.Keys.ESCAPE);\n        client.expect.element('#modal .form-group').not.to.be.present.before(5000);\n        client.click('#notebooks .list--buttons .drop-edit');\n    },\n\n    'add button shows notebook form': function(client) {\n        client\n        .click('#header--add');\n\n        client.expect.element('#modal .modal-title').to.be.present.before(2000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(2000);\n        client\n        .pause(200)\n        .keys(client.Keys.ESCAPE);\n        client.expect.element('#modal .form-group').not.to.be.present.before(5000);\n    },\n\n    'navigation keybindings work': function(client) {\n        client.getValue('#notebooks .list--item.active', function(value) {\n            client.keys('k');\n            client.expect.element('#notebooks .list--item.active').value.not.to.be.equal(value).before(5000);\n        });\n\n        client.getValue('#notebooks .list--item.active', function(value) {\n            client.keys('j');\n            client.expect.element('#notebooks .list--item.active').value.not.to.be.equal(value).before(5000);\n        });\n    },\n\n    // SHIFT+3, c, e and c, SHIFT+3, e\n    'shift+3 shows delete form': function(client) {\n        client\n        .keys([client.Keys.SHIFT, '3'])\n\n        // Hit Shift again to disable it\n        .keys(client.Keys.SHIFT);\n\n        client.expect.element('#modal .modal-title').to.be.present.before(5000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(5000);\n        client.expect.element('#modal .modal').to.have.css('display').which.equals('block').before(5000);\n\n        client.click('#modal .close');\n        client.expect.element('#modal .modal-title').not.to.be.present.before(5000);\n    },\n\n    '`c` shows create form': function(client) {\n        client.pause(200);\n\n        client.keys('c');\n\n        client.expect.element('#modal .modal-dialog').to.be.present.before(6000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(6000);\n        client.keys(client.Keys.ESCAPE);\n        client.expect.element('#modal .modal-dialog').not.to.be.present.before(6000);\n    },\n\n    '`e` shows edit form': function(client) {\n        client.keys('e');\n\n        client.expect.element('#modal .modal-title').to.be.present.before(5000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(2000);\n        client.click('#modal .cancelBtn');\n        client.expect.element('#modal .modal-title').not.to.be.present.before(2000);\n    },\n\n    'can navigate to active notes': function(client) {\n        client.keys(['g', 'i']);\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('All notes')\n        .before(5000);\n    },\n\n    'can navigate to notebooks from notes': function(client) {\n        client.keys(['g', 'n']);\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Notebooks & Tags')\n        .before(5000);\n    },\n\n    'can navigate to favourite notes': function(client) {\n        client.keys(['g', 'f']);\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Favourites')\n        .before(5000);\n\n        client.keys(['g', 'n']);\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Notebooks & Tags')\n        .before(5000);\n    },\n\n    'can navigate to removed notes': function(client) {\n        client.keys(['g', 't']);\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Trashed')\n        .before(5000);\n\n        client.keys(['g', 'n']);\n\n        client\n        .expect.element('#header--title')\n        .to.have.text.that.equals('Notebooks & Tags')\n        .before(5000);\n    },\n\n    'can filter notes by a notebook name': function(client) {\n        client.perform((client, done) => {\n            client.getText('#notebooks .list--item.-notebook', (res) => {\n                client.click('#notebooks .list--item.-notebook');\n\n                client\n                .expect.element('#header--title')\n                .to.have.text.that.matches(new RegExp(res.value, 'gi'))\n                .before(5000);\n\n                client.keys(['g', 'n']);\n\n                client\n                .expect.element('#header--title')\n                .to.have.text.that.equals('Notebooks & Tags')\n                .before(5000);\n\n                client.perform(done);\n            });\n        });\n    },\n\n    'can filter notes by a notebook name with a keybinding': function(client) {\n        client.perform((client, done) => {\n            client.getText('#notebooks .list--item.-notebook', (res) => {\n                client.keys('j');\n                client.expect.element('#notebooks .list--item.active').to.be.present.before(5000);\n\n                client.keys('o');\n\n                client\n                .expect.element('#header--title')\n                .to.have.text.that.matches(new RegExp(res.value, 'gi'))\n                .before(5000);\n\n                client.keys(['g', 'n']);\n\n                client\n                .expect.element('#header--title')\n                .to.have.text.that.equals('Notebooks & Tags')\n                .before(5000);\n\n                client.perform(done);\n            });\n        });\n    },\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notebooks/remove.js",
    "content": "'use strict';\nvar expect = require('chai').expect,\n    notebookCount;\n\n/**\n * Notebook removal test\n */\nmodule.exports = {\n\n    before: function(client, done) {\n        client.closeWelcome();\n\n        client.urlHash('notes')\n        .expect.element('#header--add').to.be.visible.before(50000);\n\n        client.urlHash('notebooks');\n\n        client.expect.element('#header--add').to.be.visible.before(5000);\n        client.elements('css selector', '#notebooks .list--item', (res) => {\n            notebookCount = res.value.length;\n            done();\n        });\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'can remove a notebook': function(client) {\n        // Prepare a notebook to delete\n        client.addNotebook({name: '1.ToRemove', parentId: 0});\n\n        client\n        .urlHash('notes')\n        .pause(100)\n        .urlHash('notebooks');\n\n        client.expect.element('#notebooks').text.to.contain('1.ToRemove').before(5000);\n\n        // Delete notebook\n        client\n        .click('#notebooks .list--buttons .drop-edit')\n        .click('#notebooks .list--buttons .remove-link');\n\n        client.expect.element('#modal .modal-title').to.be.present.before(5000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(5000);\n        client.click('#modal .btn[data-event=\"confirm\"]');\n        client.pause(500);\n\n        client.expect.element('#notebooks').text.not.to.contain('1.ToRemove').before(5000);\n    },\n\n    'deleted notebooks don\\'t re-appear after url change': function(client) {\n        client\n        .urlHash('notes')\n        .pause(1000)\n        .urlHash('notebooks');\n\n        client.expect.element('#notebooks').text.not.to.contain('1.ToRemove').before(5000);\n    },\n\n    'nested notebooks are not removed, but their parentId is changed': function(client) {\n        // Prepare notebooks\n        client.addNotebook({name: '1.ToRemove', parentId: 0});\n        client.addNotebook({name: '1.NestedRemove', parentId: '1.ToRemove'});\n        client.addNotebook({name: '1.RemoveNested', parentId: '1.ToRemove'});\n\n        client\n        .urlHash('notes')\n        .pause(100)\n        .urlHash('notebooks');\n\n        client.expect.element('#notebooks').text.to.contain('1.ToRemove').before(5000);\n\n        // Delete a notebook\n        client\n        .click('#notebooks .list--buttons .drop-edit')\n        .click('#notebooks .list--buttons .remove-link');\n\n        client.expect.element('#modal .modal-title').to.be.present.before(5000);\n        client.expect.element('#modal .modal-title').to.be.visible.before(5000);\n        client.click('#modal .btn[data-event=\"confirm\"]');\n        client.pause(500);\n\n        client.expect.element('#notebooks').text.not.to.contain('1.ToRemove').before(5000);\n        client.expect.element('#notebooks').text.to.contain('1.NestedRemove').before(5000);\n        client.expect.element('#notebooks').text.to.contain('1.RemoveNested').before(5000);\n    },\n\n    'cleanup': function(client) {\n        client\n        .urlHash('notes')\n        .pause(100)\n        .urlHash('notebooks');\n\n        for (var i = 0, len = 2; i <= len; i++) {\n            if (i === 2) {\n                return;\n            }\n\n            client\n            .keys('j')\n            .pause(100)\n            .keys([client.Keys.SHIFT, '3', client.Keys.SHIFT]);\n\n            client.expect.element('#modal .modal-title').to.be.present.before(5000);\n            client.expect.element('#modal .modal-title').to.be.visible.before(5000);\n\n            client.click('#modal .btn[data-event=\"confirm\"]');\n            client.pause(500);\n        }\n    },\n\n    'shows notebooks in the navbar menu': function(client) {\n        client\n        .urlHash('notes')\n        .pause(100)\n        .urlHash('notebooks');\n\n        client.perform((client, done) => {\n            client.elements('css selector', '#notebooks .list--item', (res) => {\n                expect(notebookCount).to.be.equal(res.value.length);\n                done();\n            });\n        });\n    },\n\n    // @TODO add tests for notes behaviour after linked notebooks are deleted\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notes/form.js",
    "content": "'use strict';\n\nvar notebookId;\nmodule.exports = {\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'can change note title': function(client) {\n        client\n        .urlHash('notes')\n        .pause(200)\n        .urlHash('notes/add')\n        .expect.element('#editor--input--title').to.be.visible.before(50000);\n\n        client\n        .setValue('#editor--input--title', ['Night'])\n        .expect.element('#editor--input--title').value.to.contain('Night').before(1000);\n    },\n\n    'shows confirm dialog if title is not empty': function(client) {\n        client\n        .setValue('#editor--input--title', [' Watch'])\n        .click('.editor--cancel')\n        .expect.element('.modal-dialog').to.be.visible.before(1000);\n    },\n\n    'hides confirm dialog on \"Esc\"': function(client) {\n        client\n        .keys([client.Keys.ESCAPE])\n        .click('.layout--body.-form')\n        .waitForElementNotPresent('.modal-dialog', 1000);\n    },\n\n    'closes the form if title is empty': function(client) {\n        client\n        .clearValue('#editor--input--title')\n        .click('.editor--cancel')\n        .expect.element('.layout--body.-form').not.to.be.present.before(5000);\n\n        client.expect.element('.list--group').not.to.be.present.after(1000);\n    },\n\n    'shows notebook add form': function(client) {\n        client\n        .urlHash('notes/add')\n        .expect.element('#editor--input--title').to.be.visible.before(50000);\n\n        client.expect.element('.addNotebook').to.be.visible.before(50000);\n\n        client\n        .click('.addNotebook')\n        .expect.element('.modal--input[name=name]').to.be.present.before(5000);\n    },\n\n    'makes a new notebook active': function(client) {\n        client\n        .setValue('.modal--input[name=name]', ['Nightwatch', client.Keys.ENTER])\n        .pause(1000)\n        .perform(function(client, done) {\n            client.getValue('[name=\"notebookId\"]', function(res) {\n                notebookId = res.value;\n                client.assert.containsText('option[value=\"' + notebookId + '\"]', 'Nightwatch');\n                done();\n            });\n        });\n    },\n\n    'switches into fullscreen mode': function(client) {\n        client.expect.element('a[data-mode=\"fullscreen\"]').to.be.present.before(1000);\n\n        client\n        .click('button[title=\"Mode\"]')\n        .click('a[data-mode=\"fullscreen\"]')\n        .expect.element('body').to.have.attribute('class').which.does.not.contain('-preview').before(2000);\n\n        client.expect.element('body').to.have.attribute('class').which.contains('editor--fullscreen').before(2000);\n    },\n\n    'switches into preview mode': function(client) {\n        client\n        .click('button[title=\"Mode\"]')\n        .click('a[data-mode=\"preview\"]')\n        .expect.element('body').to.have.attribute('class').which.contains('-preview').before(2000);\n    },\n\n    'switches into normal mode': function(client) {\n        client\n        .click('button[title=\"Mode\"]')\n        .click('a[data-mode=\"normal\"]')\n        .expect.element('body').to.have.attribute('class').which.does.not.contain('-preview').before(2000);\n\n        client.expect.element('body').to.have.attribute('class').which.does.not.contain('editor--fullscreen').before(2000);\n    },\n\n    'closes the form': function(client) {\n        client\n        .click('.editor--cancel')\n        .expect.element('.modal-dialog').to.be.visible.before(1000);\n\n        client\n        .click('.modal-dialog button[data-event=\"confirm\"]')\n        .expect.element('.layout--body.-form').not.to.be.present.before(5000);\n    },\n\n    'makes previously selected notebook active': function(client) {\n        client\n        .urlHash('/notes/f/notebook/q/' + notebookId)\n        .expect.element('#sidebar--content').to.be.visible.before(50000);\n\n        client\n        .urlHash('/notes/add')\n        .expect.element('#editor--input--title').to.be.visible.before(50000);\n\n        client\n        .expect.element('[name=\"notebookId\"]').to.have.attribute('value').which.equals(notebookId);\n    },\n\n    'does not save a note if its title is empty': function(client) {\n        client\n        .clearValue('#editor--input--title')\n        .click('.editor--save')\n        .expect.element('.layout--body.-form').to.be.visible.after(1000);\n    },\n\n    'saves a note': function(client) {\n        client\n        .setValue('#editor--input--title', ['Nightwatch'])\n        .expect.element('#editor--input--title').value.to.contain('Nightwatch').before(1000);\n\n        client\n        .click('.CodeMirror-lines')\n        .keys(['Nightwatch test content.']);\n\n        client\n        .click('.editor--save')\n        .expect.element('.layout--body.-form').to.be.not.present.before(5000);\n    },\n\n    'saved the note': function(client) {\n        client\n        .urlHash('/notes/')\n        .expect.element('.list--group').to.be.visible.after(5000);\n\n        client.expect.element('.list').text.to.contain('Nightwatch test content.').before(5000);\n        client.expect.element('.list--group').to.be.present.before(5000);\n        client.expect.element('.list--item').to.be.present.before(5000);\n    },\n\n    'opens an edit page': function(client) {\n        client.getAttribute('.list--item', 'data-id', function(res) {\n            client\n            .urlHash('/notes/edit/' + res.value)\n            .expect.element('.layout--body.-form').to.be.visible.before(1500);\n        });\n    },\n\n    'can change a note': function(client) {\n        client\n        .setValue('#editor--input--title', ['Night Watch'])\n        .expect.element('#editor--input--title').value.to.contain('Night Watch').before(1000);\n\n        client\n        .click('.CodeMirror-lines')\n        .keys(['Added a new content.']);\n\n        client\n        .keys([client.Keys.CONTROL, 's'])\n        .expect.element('.layout--body.-form').to.be.not.present.before(5000);\n    },\n\n    'updated the note': function(client) {\n        client\n        .urlHash('notes')\n        .expect.element('.list--group').to.be.visible.after(1000);\n\n        client.expect.element('#sidebar--content').text.to.contain('Added a new content');\n        client.expect.element('#sidebar--content').text.to.contain('Night Watch');\n    },\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notes/list.js",
    "content": "'use strict';\nvar expect = require('chai').expect;\n\nmodule.exports = {\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'opens #/notes': function(client) {\n        client\n        .urlHash('notes')\n        .expect.element('.list').to.be.present.before(50000);\n    },\n\n    'renders new notes': function(client) {\n        for (var i = 1; i <= 15; i++) {\n            client.addNote({title: i + '. Note', content: 'Nightwatch test content ' + i + '.'});\n        }\n\n        client\n        .urlHash('/notes')\n        .expect.element('.list').to.be.present.before(2000);\n    },\n\n    'shows pagination buttons': function(client) {\n        client\n        .expect.element('.list--pager').to.be.visible.before(2000);\n    },\n\n    'is possible to navigate by pressing \"j\" key': function(client) {\n        client\n        .expect.element('.list--item.active').not.to.be.present.before(500);\n\n        client\n        .pause(300)\n        .keys(['j'])\n        .expect.element('.list--item.active').to.be.visible.before(5000);\n    },\n\n    'is possible to navigate by pressing \"k\" key': function(client) {\n        client\n        .pause(300)\n        .keys(['j'])\n        .expect.element('.list--item:first-child').to.have.attribute('class').which.does.not.contain('active').before(5000);\n\n        client\n        .pause(300)\n        .keys(['k'])\n        .expect.element('.list--item:first-child').to.have.attribute('class').which.contains('active').before(5000);\n    },\n\n    'if it reaches the last note on the page, it navigates to the next page': function(client) {\n        client.expect.element('#prevPage').to.have.attribute('class').which.contains('disabled');\n        client.expect.element('#nextPage').to.have.attribute('class').which.does.not.contain('disabled');\n\n        for (var i = 0; i < 14; i++) {\n            client\n            .pause(300)\n            .keys(['j']);\n        }\n\n        client.expect.element('.list--item.active').to.be.visible.before(2000);\n        client.expect.element('#prevPage').to.have.attribute('class').which.does.not.contain('disabled').before(2000);\n        client.expect.element('#nextPage').to.have.attribute('class').which.contains('disabled').before(2000);\n    },\n\n    'if it reaches the first note on the page, it navigates to the previous page': function(client) {\n        client.expect.element('#prevPage').to.have.attribute('class').which.does.not.contain('disabled').before(2000);\n        client.expect.element('#nextPage').to.have.attribute('class').which.contains('disabled').before(2000);\n\n        for (var i = 0; i < 10; i++) {\n            client\n            .pause(300)\n            .keys(['k']);\n        }\n\n        client.expect.element('.list--item.active').to.be.visible.before(2000);\n        client.expect.element('#prevPage').to.have.attribute('class').which.contains('disabled');\n        client.expect.element('#nextPage').to.have.attribute('class').which.does.not.contain('disabled');\n    },\n\n    // 'can add a new note by pressing \"c\"': function(client) {\n    //     client\n    //     .keys('c')\n    //     .expect.element('.layout--body.-form').to.be.present.before(2000);\n    //\n    //     client\n    //     .keys(client.Keys.ESCAPE)\n    //     .expect.element('.layout--body.-form').not.to.be.present.before(2000);\n    // },\n\n    'changes favourite status of an active note if favourite button is clicked': function(client) {\n        client\n        .click('.list--group .favorite')\n        .expect.element('.list--group .icon-favorite').to.be.present.before(2000);\n\n        client\n        .click('.list--group .favorite')\n        .expect.element('.list--group .icon-favorite').not.to.be.present.before(2000);\n\n        client\n        .click('.list--group .favorite')\n        .waitForElementPresent('.list--group .icon-favorite', 2000);\n    },\n\n    'navigates to favourite page on \"gf\" shortcut': function(client) {\n        client\n        .keys('gf')\n        .pause(300)\n        .assert.urlContains('notes/f/favorite');\n    },\n\n    'navigates to trash page on \"gt\" shortcut': function(client) {\n        client\n        .keys('gt')\n        .pause(300)\n        .assert.urlContains('notes/f/trashed');\n    },\n\n    'navigates to active page on \"gi\" shortcut': function(client) {\n        client\n        .keys('gi')\n        .pause(300)\n        .url(function(url) {\n            expect(url.value.search('notes/f/trashed') !== -1).not.to.be.equal(true);\n            expect(url.value.search('notes/f/favorite') !== -1).not.to.be.equal(true);\n            expect(url.value.search('notes') !== -1).to.be.equal(true);\n        });\n    },\n\n    'navigates to notebook page on \"gn\" shortcut': function(client) {\n        client\n        .keys('gn')\n        .pause(300)\n        .assert.urlContains('notebooks');\n    },\n\n    'can filter notes by a notebook name': function(client) {\n        var note = {\n            title    : 'A note with a notebook:' + Math.floor((Math.random() * 10) + 1),\n            content  : 'A note content with a notebook.',\n            notebook : 'Notebook:' + Math.floor((Math.random() * 10) + 1)\n        };\n\n        client.addNote(note);\n\n        client\n        .keys('gn')\n        .pause(300)\n        .expect.element('.list--item.-notebook').to.be.present.before(50000);\n\n        client.getAttribute('partial link text', note.notebook, 'data-id', function(res) {\n            this.assert.equal(typeof res.value !== 'undefined', true);\n\n            client\n            .urlHash('notes/f/notebook/q/' + res.value)\n            .expect.element('.list').to.be.present.before(50000);\n\n            client.expect.element('#sidebar--content').to.have.text.that.contains(note.title).before(5000);\n            client.elements('css selector', '.list--item', function(res) {\n                this.assert.equal(res.value.length, 1);\n            });\n        });\n    },\n\n    'can filter notes by a tag': function(client) {\n        var note = {\n            title   : 'A note with a tag:' + Math.floor((Math.random() * 10) + 1),\n            content : [client.Keys.SHIFT, '3', client.Keys.SHIFT, 'tagname']\n        };\n\n        client\n        .addNote(note)\n        .keys('gn');\n\n        client\n        .urlHash('notes/f/tag/q/tagname')\n        .expect.element('.list').to.be.present.before(50000);\n\n        client.expect.element('.list--item').to.be.present.before(50000);\n\n        client.expect.element('#sidebar--content').to.have.text.that.contains(note.title).before(5000);\n        client.elements('css selector', '.list--item', function(res) {\n            this.assert.equal(res.value.length, 1);\n        });\n    },\n\n    'can search notes': function(client) {\n        var note = {\n            title   : 'A unique note title:' + Math.floor((Math.random() * 10) + 1),\n            content : 'A unique note content'\n        };\n        client.addNote(note);\n\n        client\n        .pause(300)\n        .urlHash('notes/f/search/q/' + note.title)\n        .expect.element('.list').to.be.present.before(50000);\n\n        client.pause(500);\n        client.expect.element('#sidebar--content').to.have.text.that.contains(note.title).before(5000);\n        client.elements('css selector', '.list--item.-note', function(res) {\n            this.assert.equal(res.value.length, 1);\n        });\n    },\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/notes/show.js",
    "content": "'use strict';\nvar expect = require('chai').expect;\n\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'wait': function(client) {\n        client\n        .urlHash('notes')\n        .expect.element('.list').to.be.present.before(50000);\n    },\n\n    'can open a note from the list': function(client) {\n        client\n        .addNote({title: 'A new note', content: 'Note content\\r[ ] A task\\r[ ] The second task'})\n        .addNote({title: 'A second note', content: 'Note content 2\\r[ ] A task\\r[ ] The second task'})\n        .urlHash('notebooks')\n        .pause(200)\n        .urlHash('notes')\n        .expect.element('.list').to.be.present.before(5000);\n\n        client\n        .keys(['j'])\n        .pause(500)\n        .expect.element('.list--item.active').to.be.visible.before(3000);\n\n        client.expect.element('.layout--body.-note').to.be.present.before(2000);\n        client.assert.urlContains('notes/f/active/show/');\n    },\n\n    'shows edit button': function(client) {\n        client.expect.element('.note--edit').to.be.visible.before(2000);\n    },\n\n    'shows remove button': function(client) {\n        client.expect.element('.note--remove').to.be.visible.before(2000);\n    },\n\n    'shows \"favourite\" button': function(client) {\n        client.expect.element('.btn--favourite').to.be.visible.before(2000);\n    },\n\n    'has tasks': function(client) {\n        client.expect.element('.layout--body.-note').to.be.present.before(5000);\n        client.expect.element('.layout--body.-note .checkbox--input').to.be.present.before(5000);\n    },\n\n    'shows the task progress': function(client) {\n        client.expect.element('.layout--body.-note .progress-bar').to.be.visible.before(200);\n        client.expect.element('.layout--body.-note .progress-bar').to.have.css('width', '0%');\n    },\n\n    'changes a task\\'s status when a checkbox is clicked': function(client) {\n        client\n        .click('.layout--body.-note .task--checkbox')\n        .expect.element('.layout--body.-note .task--checkbox input:checked').to.be.present.before(5000);\n\n        client.pause(500);\n    },\n\n    'changes the task progress %': function(client) {\n        client.expect.element('.layout--body.-note .progress-bar').to.have.css('width').which.not.equal('0%');\n    },\n\n    'opens the edit page if the edit button is clicked': function(client) {\n        client\n        .click('.note--edit')\n        .expect.element('.layout--body.-form').to.be.present.before(4000);\n\n        client\n        .keys([client.Keys.ESCAPE])\n        .expect.element('.layout--body.-form').not.to.be.present.before(4000);\n    },\n\n    'removes a note if the remove button is clicked': function(client) {\n        client\n        .click('.note--remove')\n        .expect.element('.modal-dialog').to.be.present.before(2000);\n\n        client\n        .keys([client.Keys.ESCAPE])\n        .expect.element('.modal-dialog').not.to.be.present.before(2000);\n    },\n\n    'changes favourite status if the button is clicked': function(client) {\n        client\n        .click('.btn--favourite')\n        .pause(300)\n        .expect.element('.btn--favourite--icon').to.have.attribute('class').which.contains('icon-favorite');\n    },\n\n    'opens edit page if \"e\" is pressed': function(client) {\n        client.expect.element('.layout--body.-note').to.be.present.before(2000);\n\n        client\n        .pause(300)\n        .keys(['e'])\n        .expect.element('.layout--body.-form').to.be.present.before(4000);\n\n        client\n        .click('.editor--cancel')\n        .expect.element('.layout--body.-form').not.to.be.present.before(4000);\n    },\n\n    'shows delete dialog on \"Shift+3\" key combination': function(client) {\n        client.expect.element('.layout--body.-note').to.be.present.before(2000);\n\n        client\n        .pause(1000)\n        .keys([client.Keys.SHIFT, '3'])\n\n        // Hit Shift key again to disable it\n        .keys([client.Keys.SHIFT])\n        .expect.element('.modal-dialog').to.be.present.before(2000);\n    },\n\n    'deletes a note': function(client) {\n        client.expect.element('.modal-dialog .btn-success').to.be.present.before(2000);\n\n        client\n        .perform(function(cli, done) {\n            cli.getAttribute('.list--group:first-child .list--item', 'data-id', function(res) {\n                client\n                .pause(500)\n                .click('.modal-dialog .btn-success')\n                .expect.element('.modal-dialog').not.to.be.present.before(5000);\n\n                client.expect.element('.list--group:first-child .list--item').to.be.present.before(2000);\n\n                cli\n                .getAttribute('.list--group:first-child .list--item', 'data-id', function(newRes) {\n                    expect(newRes.value).not.to.be.equal(res.value);\n                    done();\n                });\n            });\n        });\n    },\n\n};\n"
  },
  {
    "path": "test/spec-ui/tests/apps/settings/general.js",
    "content": "'use strict';\n\n/**\n * Settings list test\n//  |)}>#\nmodule.exports = {\n    before: function(client) {\n        client.closeWelcome();\n\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.addNote({title: 'hello', content: 'hello'});\n        client.addNote({title: 'hello2', content: 'hello2'});\n        client.addNote({title: 'hello3', content: 'hello3'});\n\n        client.addNotebook({name: 'AAAAAA', parentId: 0});\n        client.addNotebook({name: 'AAAAAB', parentId: 0});\n\n        client.urlHash('settings');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'general tab is open by default': function(client) {\n        client\n        .expect.element('.list--settings.active').to.have.text.that.contains('General');\n    },\n\n    'can change how many notes should be displayed': function(client) {\n        client.expect.element('input[name=\"pagination\"]').to.be.present.before(5000);\n\n        // Set pagination to 1\n        client.clearValue('input[name=\"pagination\"]');\n        client.setValue('input[name=\"pagination\"]', '1');\n\n        client.click('.settings--save');\n        client.click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.findAll('#sidebar--content .list--item.-note', 'data-id', (res) => {\n            client.assert.equal(res.length, 1);\n        });\n\n        // Set pagination to 10\n        client.urlHash('settings');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);\n        client.expect.element('input[name=\"pagination\"]').to.be.present.before(5000);\n\n        client.clearValue('input[name=\"pagination\"]');\n        client.setValue('input[name=\"pagination\"]', '10');\n\n        client.click('.settings--save');\n        client.click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.findAll('#sidebar--content .list--item.-note', 'data-id', (res) => {\n            client.assert.notEqual(res.length, 1);\n        });\n    },\n\n    'can change default edit mode': function(client) {\n        client.urlHash('settings');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);\n        client.expect.element('select[name=\"editMode\"]').to.be.present.before(5000);\n\n        client.setValue('select[name=\"editMode\"]', 'normal');\n\n        client\n        .click('.settings--save')\n        .pause(500)\n        .click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.urlHash('notes/add');\n        client.expect.element('#editor').to.be.present.before(5000);\n        client.expect.element('#editor').to.be.visible.before(5000);\n\n        // :TODO Nightwatch returns wrong classes\n        // client.pause(1000);\n        // client.expect.element('.editor--fullscreen').to.be.not.present.after(5000);\n        // client.expect.element('.-preview').to.be.not.present.after(5000);\n    },\n\n    'can change sort notebooks settings': function(client) {\n        client.urlHash('settings');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);\n\n        client.expect.element('select[name=\"sortnotebooks\"]').to.be.present.before(5000);\n\n        client.setValue('select[name=\"sortnotebooks\"]', 'created');\n\n        client.click('.settings--save');\n        client.click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.urlHash('notebooks');\n        // :TODO FIX sorting\n        // client.expect.element('#notebooks .list--item.-notebook').to.have.text.that.equals('AAAAAB').before(5000);\n    },\n};\n*/\n"
  },
  {
    "path": "test/spec-ui/tests/apps/settings/import.js",
    "content": "'use strict';\n\n/**\n * Notebook list test\n//  |)}>#\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n\n        client.urlHash('settings/importExport');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    '\"Import & Export\" tab is active in #/settings/importExports': function(client) {\n        client\n        .expect.element('.list--settings.active').to.have.text.that.contains('Import & Export');\n    },\n\n    // @TODO Test importing & exporting json config files\n};\n*/\n"
  },
  {
    "path": "test/spec-ui/tests/apps/settings/keybindings.js",
    "content": "'use strict';\nvar keys;\n\n/**\n * Notebook list test\n//  |)}>#\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n\n        keys = {\n            actionsEdit: 'e',\n            actionsOpen: 'o',\n            actionsRemove: 'shift+3',\n            actionsRotateStar: 's',\n            appCreateNote: 'c',\n            // appKeyboardHelp: '?',\n            // appSearch: '/',\n            jumpFavorite: 'g f',\n            jumpInbox: 'g i',\n            jumpNotebook: 'g n',\n            jumpOpenTasks: 'g o',\n            jumpRemoved: 'g t',\n            navigateBottom: 'j',\n            navigateTop: 'k',\n        };\n\n        client.urlHash('settings/keybindings');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'keybindings tab is active in #/settings/keybindings': function(client) {\n        client\n        .expect.element('.list--settings.active').to.have.text.that.contains('Keybindings');\n    },\n\n    'can change keybindings': function(client) {\n        client.perform(function(client, done) {\n            for (var name in keys) {\n                client\n                .expect.element('[name=\"' + name + '\"]').to.be.present.before(5000);\n\n                client\n                .clearValue('[name=\"' + name + '\"]')\n                .setValue('[name=\"' + name + '\"]', keys[name].toUpperCase());\n            }\n\n            client\n            .clearValue('[name=\"appKeyboardHelp\"]')\n            .setValue('[name=\"appKeyboardHelp\"]', '/');\n\n            client\n            .clearValue('[name=\"appSearch\"]')\n            .setValue('[name=\"appSearch\"]', '?');\n\n            client.perform(() => {done();});\n        });\n    },\n\n    'keybindings are saved': function(client) {\n        client.click('.settings--save');\n        client.click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n        client.urlHash('settings/keybindings');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);\n\n        client.perform(function(client, done) {\n            for (var name in keys) {\n                client.assert.value('[name=\"' + name + '\"]', keys[name].toUpperCase());\n            }\n\n            client.assert.value('[name=\"appKeyboardHelp\"]', '/');\n            client.assert.value('[name=\"appSearch\"]', '?');\n\n            client.perform(() => {done();});\n        });\n    },\n\n    'change to old settings': function(client) {\n        client.perform(function(client, done) {\n            for (var name in keys) {\n                client\n                .clearValue('[name=\"' + name + '\"]')\n                .setValue('[name=\"' + name + '\"]', keys[name].toUpperCase());\n            }\n\n            client\n            .clearValue('[name=\"appKeyboardHelp\"]')\n            .setValue('[name=\"appKeyboardHelp\"]', '/');\n\n            client\n            .clearValue('[name=\"appSearch\"]')\n            .setValue('[name=\"appSearch\"]', '?');\n\n            client.perform(() => {done();});\n        });\n\n        client.click('.settings--save');\n        client.click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n    },\n};\n*/\n"
  },
  {
    "path": "test/spec-ui/tests/apps/settings/profiles.js",
    "content": "'use strict';\n\n/**\n * Notebook list test\n//  |)}>#\nmodule.exports = {\n\n    before: function(client) {\n        client.closeWelcome();\n\n        client.urlHash('settings/profiles');\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(50000);\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'profiles tab is active in #/settings/profiles': function(client) {\n        client\n        .expect.element('.list--settings.active').to.have.text.that.contains('Profiles');\n    },\n\n    'can add new profiles': function(client) {\n        client.expect.element('#profileName').to.be.present.before(5000);\n        client.setValue('#profileName', ['NightwatchProfile', client.Keys.ENTER]);\n        client.assert.value('#profileName', '');\n        client.expect.element('.layout--body.-settings').to.have.text.that.contains('NightwatchProfile');\n    },\n\n    'new profiles are saved': function(client) {\n        client.expect.element('.settings--save').to.be.present.before(5000);\n        client.click('.settings--save');\n        client.click('.settings--cancel');\n\n        client.expect.element('#header--add').to.be.visible.before(50000);\n        client.urlHash('settings/profiles');\n        client.expect.element('.layout--body.-settings').to.be.present.before(5000);\n\n        client.expect.element('.header--title').to.have.text.that.contains('Settings').before(5000);\n        client.expect.element('.layout--body.-settings').to.have.text.that.contains('NightwatchProfile');\n    },\n\n    'new profiles appear in sidemenu': function(client) {\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.click('#header--title');\n        client.expect.element('.sidemenu.-show').to.be.present.before(5000);\n        client.expect.element('.sidemenu.-show').to.be.visible.before(5000);\n\n        client.expect.element('.sidemenu').to.have.text.that.contains('NightwatchProfile');\n    },\n};\n*/\n"
  },
  {
    "path": "test/spec-ui/tests/modules/fuzzySearch/fuzzySearch.js",
    "content": "'use strict';\n\n/**\n * Test fuzzy search module\n */\nmodule.exports = {\n    before: function(client) {\n        client.closeWelcome();\n    },\n\n    after: function(client) {\n        client.end();\n    },\n\n    'wait': function(client) {\n        client.urlHash('notes');\n        client.expect.element('#header--add').to.be.visible.before(50000);\n\n        client.addNote({title: 'note1', content: 'content1'});\n        client.addNote({title: 'note2', content: 'content2'});\n        client.addNote({title: 'note3', content: 'content3'});\n        client.addNote({title: 'Nightwatch', content: 'Nightwatch'});\n    },\n\n    'can show search form': function(client) {\n        client.expect.element('#header--sbtn').to.be.present.before(5000);\n        client.click('#header--sbtn');\n\n        client.expect.element('#header--search--input').to.be.present.before(5000);\n        client.expect.element('#header--search--input').to.be.visible.before(5000);\n    },\n\n    'can search by note\\'s name': function(client) {\n        client.clearValue('#header--search--input');\n        client.expect.element('#sidebar--fuzzy').text.to.be.equal('');\n\n        client\n        .setValue('#header--search--input', 'note')\n        .expect.element('#sidebar--fuzzy').text.to.be.not.equal('').before(5000);\n\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note1');\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note2');\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note3');\n        client.expect.element('#sidebar--fuzzy').text.to.not.contain('Nightwatch');\n    },\n\n    'can close fuzzySearch module on Escape': function(client) {\n        client\n        .setValue('#header--search--input', client.Keys.ESCAPE)\n        .expect.element('#sidebar--fuzzy').text.to.be.equal('').before(5000);\n    },\n\n    'can search by note\\'s content': function(client) {\n        client.click('#header--sbtn');\n        client.expect.element('#header--search--input').to.be.present.before(5000);\n        client.expect.element('#header--search--input').to.be.visible.before(5000);\n\n        client.setValue('#header--search--input', 'content');\n\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note1').before(5000);\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note2');\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note3');\n        client.expect.element('#sidebar--fuzzy').text.to.not.contain('Nightwatch');\n    },\n\n    'after hitting ESCAPE notes are normally rendered': function(client) {\n        client\n        .clearValue('#header--search--input')\n        .pause(1000)\n        .setValue('#header--search--input', client.Keys.ESCAPE)\n        .expect.element('#sidebar--fuzzy').to.be.not.visible.before(5000);\n\n        client.expect.element('#header--search--input').to.be.not.visible.before(5000);\n\n        client.expect.element('#sidebar--content').to.be.visible.before(5000);\n        client.expect.element('#sidebar--content').text.to.contain('note1').before(5000);\n        client.expect.element('#sidebar--content').text.to.contain('note2').before(5000);\n        client.expect.element('#sidebar--content').text.to.contain('note3').before(5000);\n        client.expect.element('#sidebar--content').text.to.contain('Nightwatch').before(5000);\n    },\n\n    'redirects to notes search page on `enter`': function(client) {\n        client.expect.element('#header--sbtn').to.be.visible.before(5000);\n        client.click('#header--sbtn');\n\n        client.expect.element('#header--search--input').to.be.present.before(5000);\n        client.expect.element('#header--search--input').to.be.visible.before(5000);\n\n        client\n        .clearValue('#header--search--input')\n        .setValue('#header--search--input', 'note')\n        .expect.element('#sidebar--fuzzy').text.to.be.not.equal('').before(5000);\n\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note1');\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note2');\n        client.expect.element('#sidebar--fuzzy').text.to.contain('note3');\n        client.expect.element('#sidebar--fuzzy').text.to.not.contain('Nightwatch');\n\n        client\n        .setValue('#header--search--input', client.Keys.ENTER)\n        .assert.urlContains('notes/f/search/q/note');\n    }\n};\n"
  }
]